W mojej aplikacji mam model oparty głównie na dwóch hierarchiach klas: kontenerach i elementach. Kontenery mają odniesienia do elementów za pośrednictwem list.
Rozważmy na przykład klasę Java Child
to jest podtyp Parent
i ElementChild
to jest podtyp ElementParent
. Elementy są używane jako parametry typulist i chciałbym, aby każdy kontener mógł dodawać, usuwać i pobierać elementy ze swojej listy. Lista ta powinna być jak najbardziej restrykcyjna, tzn. Powinna zawierać elementy umieszczone na tym samym poziomie hierarchii (Child
- ElementChild
).
wiem to List<ElementParent>
i List<ElementChild>
nie są w relacji podtypu, czy istnieje lepsze podejście niż tylko utworzenie dwóch różnych list (List<ElementParent>
w Parent
i List<ElementChild>
w Child
)?
W poniższym przykładzie użyłem odziedziczonej listy, aby pokazać, co mam na myśli, ale oczywiście nie kompiluje się:
public class Parent {
protected List<ElementParent> list;
public Parent () {
list = new ArrayList<ElementParent>();
}
public List<ElementParent> getList() {
return list;
}
public void add(ElementParent element) {
list.add(element);
}
public void remove (ElementParent element) {
list.remove(element);
}
public void setList(List<ElementParent> list) {
this.list = list;
}
}
public class Child extends Parent {
public Child() {
list = new ArrayList<ElementChild>(); // does not compile
}
public List<ElementChild> getList() { //does not compile
return list;
}
public void add(ElementChild element) {
list.add(element);
}
public void remove (ElementChild element) {
list.remove(element);
}
public void setList(List<ElementChild> list) { //does not compile
this.list = list;
}
}
Odpowiedzi:
1 dla odpowiedzi № 1Twoje pytanie pokazuje, dlaczego List<ElementParent>
i List<ElementChild>
nie są w relacji podtypu.
Od Child extends Parent
i Parent::add(ElementParent element)
metoda istnieje, dlatego jest dziedziczona do Child
.
Teraz możesz napisać:
ElementParent ep = ...;
Child child = ...;
child.add(ep); //perfectly legal
Ale co by to zrobiło? Dodaj ElementParent
do listy ElementChild
s I to jest powód dlaczego List<ElementParent>
i List<ElementChild>
nie są w relacji podtypu.
Jak więc rozwiązać problem?
Na przykład mogą tu pomóc symbole wieloznaczne List<ElementParent>
i List<ElementChild>
oba są List<? extends ElementParent>
i umiesz czytać ElementParent
od List<? extends ElementParent>
, ale niestety nie można do tego niczego napisać (oprócz null
).
Podobnie z dolną granicą List<? super ElementChild>
, ale na twoim miejscu ponownie zastanowiłbym się, co dokładnie chcesz osiągnąć i czy można zastosować inne podejście.
2 dla odpowiedzi nr 2
Jest to drażliwy problem znany jako podtyp kowariantny. Krótko mówiąc, oznacza to, że kiedy jesteśrozszerzając (np. podklasę) klasę i nadpisując niektóre z jej metod, nie można zmienić typu parametrów tych metod. Jeśli to dozwolone, kompilator nie będzie w stanie zapewnić gwarancji bezpieczeństwa Java.
AFAIK jedynym rozwiązaniem Twojego problemu jest poleganie w większym stopniu na lekach generycznych. W szczególności musisz zdefiniować klasę, która jest sparametryzowana zarówno dla typu elementu, jak i typu listy:
public class Base<E, L extends List<E>> {
protected L list;
public Base(L l) { list = l; }
public L getList() { return list; }
public void add(E element) { list.add(element); }
public void remove(E element) { list.remove(element); }
public void setList(L list) { this.list = list; }
}
Następnie możesz zdefiniować Parent i Child jako podklasy Base, przekazując rzeczywiste typy dla E
i L
parametry typu:
public class Parent extends Base<ElementParent,List<ElementParent>> {
public Parent() {
super(new ArrayList<ElementParent>());
}
}
public class Child extends Base<ElementChild, List<ElementChild>> {
public Child() {
super(new ArrayList<ElementChild>());
}
}