2009-08-14 11 views
6

Podczas wykonywania niezbyt ciekawych rzeczy w Javie, natknąłem się na błąd generyczny, który nie był w stanie zrozumieć, dlaczego to nie działa. Kod jest:Błąd kompilatora generycznego Java

package test; 
import java.util.*; 
public class TestClass { 
    public static class A extends C{} 
    public static class B extends C{} 
    public static class C{} 
    public static class D<T>{} 
    public static class E<T>{} 

    public static void main(String args[]){ 
     E<D<? extends C>> a = new E<D<A>>(); 
     E<D<? extends Object>> b = new E<D<? extends C>>(); 
     E<D<? extends A>> c = new E<D<A>>(); 
     E<D<? super A>> d = new E<D<A>>(); 
     D<? extends C> e = new D<A>(); 
     D<? extends A> f = new D<A>(); 
     D<? extends A> g = new D<A>(); 
    } 
} 

Błąd pojawia się podczas kompilacji:

 
test/TestClass.java:11: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> a = new E>(); 
          ^
test/TestClass.java:12: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> b = new E>(); 
           ^
test/TestClass.java:13: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> c = new E>(); 
          ^
test/TestClass.java:14: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> d = new E>(); 
         ^
4 errors 

Jeśli E<D<? extends C>> zostanie znaleziony, które powinny na pewno pasuje E<D<? extends Object>>, prawda? A może coś przeoczyłem?

+3

to jest dobre pytanie dla kolejnej edycji Javy Puzzlers http://www.javapuzzlers.com/ – dfa

+0

Sądzę, że natknąłeś się na przypadek skrajny. Bardzo interesujące. –

+0

Może to być przydatne, jeśli możesz to zrozumieć: http://bit.ly/3RrNV3 – teabot

Odpowiedz

1

Sprawdź typ obiektu zwróconego przez wywołanie Arrays.asList. Chciałbym zgadnąć zwraca listę < D <? rozszerza obiekt C > >, który nie będzie można rzutować na listę < D <? extends Object > >.

+0

Zobacz nowo opublikowany wiersz. I dlaczego nie można tego zrobić? – arnebef

+0

Wszystko sprowadza się do tego, co skomentowałem w odpowiedzi GrzegorzOledzki. Odniesienia pozwolą ci na korzystanie z szerszego zakresu zajęć, które akceptuje twoja pierwotna klasa generyczna. – Zed

2

Może pomogłoby to zrozumieć:

ArrayList<Object> aList = new ArrayList<String>(); 

nie skompilować albo z podobnego błędu.

Edycja: skonsultować się z: http://www.ibm.com/developerworks/java/library/j-jtp01255.html

+0

To nie kompiluje się, ponieważ używając odwołania do aList, będziesz mógł wstawiać Obiekty do listy, ale powinno zawierać tylko ciągi. – Zed

+2

To jest jego cel. –

1

<? extends C> określa górną granicę typu, co oznacza, że ​​typ ma być rozszerzony z C. <? extends Object> jest wyraźnie bardziej ogólnie, co jest niezgodne.

Pomyśl o tym przypadku użycia. Określając górną granicę, oczekujesz implementacji minimalnego interfejsu. Powiedzmy, że masz metodę zadeklarowaną w C. Każda klasa rozszerzająca C ma tę metodę, ale nie każda klasa rozszerzająca Object (To jest każda klasa w Javie).

+0

Masz złe zdanie, próbuję przypisać do arnebef

+0

Być może będziesz musiał pomylić sposób myślenia :) Jest to dokładne przeciwieństwo bezpieczeństwa typu przydziału obiektu. –

+0

Żadne z nich nie zadziała, bez względu na to, czy ma sens, czy nie. Typy ogólne muszą się dokładnie zgadzać, nie tylko poprzez rozszerzanie typów. – Jorn

0

Gorzej niż to. Nie można nawet to zrobić:

List<D<?>> lDQ = Arrays.asList(new D<A>()); 

Jest to bardziej oczywiste, jeśli zmieni swój program w następujący sposób:

List<D<? extends C>> a = Arrays.asList(new D<A>(), new D<B>()); //compiles 
List<D<? extends Object>> b = a; //error 

Zasadniczo, jesteś deklarowania b jako coś, co może ponosić ogólną zawartość (D<dowolna nazwa>), ale lista, do której przypisujesz to coś, co akceptuje tylko bardziej szczegółową zawartość (D<najbliższa wspólna nadklasa A i B>).

Deklarowanie b jako List<D<? extends Object>> oznacza, że ​​można, powiedzmy, b.add(new D<String>()), ale to faktycznie List<D<? extends C>>, tak nie można.

2

Jest to zasadniczo ten sam przypadek, co opublikowany wcześniej. Zasadniczo, w przypadku leków generycznych, jesteś nigdy nie wolno robić tego assigment:

myśleć o tym przykład:

ArrayList<Object> alist = new ArrayList<Number>(); 

nie skompilować, ponieważ nie jest typu bezpieczne. Możesz ewentualnie dodać Strings aList.Próbujesz przypisać listę obiektów, które są gwarantowane jako Numbers, ale mogą to być dowolne liczby, do listy, która gwarantuje tylko, że zawierasz obiekty, ale mogą to być dowolne obiekty. Gdyby kompilator dopuszczał taką sytuację, rozluźniłoby to ograniczenie, które typy obiektów mogą dostać się na listę. Dlatego należy użyć symboli wieloznacznych, takich jak:

ArrayList<? extends Object> alist = new ArrayList<Number>(); 

do kompilatora ArrayList<? extends Object> oznacza „ArrayList jakiegoś konkretnego typu«?» że nie wiem, ale wiem, że rozszerza Object. Ta ArrayList ma zawierać tylko elementy tego nieznanego "?" wpisz i dlatego zawiera tylko obiekty ". W tym przypadku kompilator nie zezwoli na wykonanie alist.add (2). Dlaczego tak jest, ponieważ kompilator nie zna typu elementów listy i nie może zagwarantować, że możesz wstawiać do niego obiekty Integer.

Masz rację sądząc, że D<? extends Object> jest nadtypem D<? extends C>. Jednak List<D<? extends Object>> nie jest podtypem List<D<? extends C>>, powinieneś używać List<? extends D<? extends C>>.

Twoja sprawa jest w zasadzie równoważne

ArrayList<D<? extends Object>> alist = new ArrayList<D<? extends C>>(); 

Masz ten sam problem jak wyżej, na liście po prawej stronie mogą zawierać tylko obiekt klasy D, którego parametr typ jest C, a ty próbujesz przypisz go do listy (po lewej stronie) może zawierać obiekty klasy D, których parametrem typu może być dowolny obiekt.

Jeśli więc kompilator zezwalał na to, że twój kod nie byłby bezpieczny, a poniższe zawiodłyby.

ArrayList<D<? extends Object>> alist = new ArrayList<D<? extends C>>(); //< not type safe 
alist.add(new D<Number>); //< oops 

W skrócie, co jest potrzebne do konkretnego przykładu jest następująca:

// type parameter of left hand side is ? extends subtype 
List<? extends D<? extends Object>> b = Arrays.asList(new D<A>(), new D<B>()); 

// type parameter of left hand side is identical 
List<D<? extends C>> b = Arrays.asList(new D<A>(), new D<B>()); 

// type parameter of left hand side is ? extends subtype 
List<? extends D<? extends C>> c = Arrays.asList(new D<A>()); 

// type parameter of left hand side is identical 
List<D<A>> c = Arrays.asList(new D<A>()); 

Nadzieja to pomaga.

0

Chodzi o to, że List<D<? extends Object>> zasadniczo definiuje nową klasę, podobnie jak List<D<? extends C>>. Nawet jeśli c rozszerza obiekt, co nie oznacza, że ​​List<D<? extends C>> rozciąga się na List<D<? extends Object>> i które byłoby potrzebne, aby przydział zadziałał.

Althoug tym series of articles jest napisane dla platformy .NET opis problemu nadal odnosi się do leków generycznych Java

+0

Musisz uciec z tych nawiasów ostrych. –

0

Nie można dziedziczyć w generyków-parametru. Powiedz, że masz następujące odniesienia:

List<Object> a; 
List<String> b; 

Teraz przypisujesz obie te same listy: a = b;

Jeśli jakiś kod wykonuje następujące operacje:

a.add(new Integer(1)); 

Co się stanie, jeśli ktoś wykonuje następujące operacje:

String s = b.get(0); 

co można uzyskać liczbę całkowitą listę ciągów. To nie powinno działać. Dlatego lista i lista są niekompatybilne, chociaż ciąg może być przypisany do odniesienia do obiektu. Generics nie działają z dziedziczeniem.

-1

Nie.
<?rozszerza C > to nie to samo co <? rozszerza Object >
Jeżeli tak by było, to prowadzić również z założenia (niekomfortową) - c rozciąga obiektów i ołów powiedzieć ...

class C{ public void doSomethingMEaningful(); };

List< ? extends C > allAtSea = new ArrayList<...>(); List< ? extends Object > allObjects = new ArrayList<...>(); allObjects.add(new Integer(88)); ...

allAtSea.addAll(allObjects); ...

allAtSea.get(...).doSomethingMeaningful(...); // Uh-oh.. this finds the Integer 88


C++ FAQ zapewnia przejrzysty przykład dla to na 21.3

+0

Nie sądzę, przykład C++ pomaga w Java pytania. – Jorn

+0

Błagam, aby się różnić, czy lista jest czymś, co jest listą czegoś - w innym przypadku chodzi o zrozumienie dziedziczenia i substytucyjności.
To, IMO, dotyczy fundamentów OO; dlatego odniesienie do C++ powinno być dopuszczalne. – Everyone

+0

Przykład nadal nie jest poprawny (lub nawet pomaga, IMO). Linia allAtSea.addAll() nie kompiluje się, podobnie jak allObjects.add(). – Jorn

0

Myślę, że będzie to bardziej jasne, jeśli przedłużysz twój przykład. Postawmy niektóre funkcje na E i opracowania na pierwszej linii głównej metody:

public static class E<T>{ 
    private final T thing; 

    public void setThing(T thing) { 
     this.thing = thing; 
    } 

    public T getThing() { 
     return thing; 
    } 
} 

public static void main(String[] args) { 
    E<D<? extends C>> a1; 
    E<D<A>> a2 = new E<D<A>>(); 
    a1 = a2; // this won't compile, but why? 

    // these things are permissible on a1: 
    a1.setThing(new D<A>()); 
    a2.setThing(new D<B>()); 

    // now let's try doing the same thing to a2: 
    a2.setThing(new D<A>()); 
    a2.setThing(new D<B>()); // oops 
} 

Ostatnia linia nowej metody głównego dlatego A1 nie może być ustawiony na A2. Jeśli kompilator pozwoli ci to zrobić, będziesz mógł umieścić D <B> w kontenerze, który został zadeklarowany jako posiadający tylko s. D <A>.

Powodem nie jest to oczywiste z oryginalnego przykład dlatego, że nie stworzył osobną zmienną, która odwołuje się do obiektu przy bardziej restrykcyjnym E < D < > > pisania. Miejmy nadzieję, że teraz widzicie, że jeśli takie odniesienie istnieje, jego bezpieczeństwo zostanie naruszone przez zadanie, które próbowaliście zrobić.

1

Podczas przypisywania zmiennej (E<T>) z nie-wieloznaczne ogólną konstrukcję T, obiekt jest przypisany musi dokładnie T jako typu rodzajowego (w tym wszystkich głównych parametrów typowania T, wieloznaczne i nie wieloznaczne) mają. W twoim przypadku T to D<A>, który nie jest tego samego typu co D<? extends C>.

Co można zrobić, ponieważ D<A> jest przypisane do D<? extends C> jest użyć typu wieloznaczny:

E<? extends D<? extends C>> a = new E<D<A>(); 
+0

Myślę, że ta odpowiedź wyjaśnia najlepiej. Możesz zrobić E > e = nowy E >(); ale nie E > e = nowy E >(); – Jorn

Powiązane problemy