Grałem około z rodzajowych i stwierdził, ku mojemu zaskoczeniu, następujący kod kompiluje:Dlaczego przy korzystaniu z typów tablic nie jest bezpieczna dla java?
class A {}
class B extends A {}
class Generic<T> {
private T instance;
public Generic(T instance) {
this.instance = instance;
}
public T get(){ return instance; }
}
public class Main {
public static void main(String[] args) {
fArray(new B[1], new Generic<A>(new A())); // <-- No error here
}
public static <T> void fArray(T[] a, Generic<? extends T> b) {
a[0] = b.get();
}
}
spodziewałbym T
należy wnioskować do B
. A
nie obejmuje B
. Dlaczego więc kompilator nie narzeka na to? Wydaje się, że
T
jest wywnioskowane na Object
, ponieważ mogę również przekazać Generic<Object>
.
Ponadto kiedy uruchomiony kod, to rzuca ArrayStoreException
na linii a[0] = b.get();
.
Nie używam żadnych surowych typów ogólnych. Wydaje mi się, że wyjątkowi temu można było uniknąć z powodu błędu czasu kompilacji lub co najmniej ostrzeżenia, jeśli w rzeczywistości został on wywnioskowany jako B
.
Podczas dalszych badań, z równoważnikiem List<...>
:
public static void main(String[] args) {
fList(new ArrayList<B>(), new Generic<A>(new A())); // <-- Error, as expected
}
public static <T> void fList(List<T> a, Generic<? extends T> b) {
a.add(b.get());
}
to jest produkcja i błąd:
The method fList(List<T>, Generic<? extends T>) in the type Main is not applicable for the arguments (ArrayList<B>, Generic<A>)
jak czyni bardziej ogólny przypadek:
public static <T> void fList(List<? extends T> a, Generic<? extends T> b) {
a.add(b.get()); // <-- Error here
}
kompilator poprawnie rozpoznaje, że pierwszy numer ?
może znajdować się dalej w hierarchii dziedziczenia niż drugi ?
.
np. Jeśli pierwsze ?
były B
i drugie ?
były A
, to nie jest bezpieczne.
Dlaczego więc pierwszy przykład nie powoduje podobnego błędu kompilatora? Czy to tylko niedopatrzenie? Czy istnieje techniczne ograniczenie?
Jedynym sposobem mogę produkować błąd jest wyraźnie zapewniając typ:
Main.<B>fArray(new B[1], new Generic<A>(new A())); // <-- Not applicable
naprawdę nie znaleźć niczego, poprzez własne badania, z wyjątkiem this article z 2005 roku (przed rodzajowych) , która mówi o niebezpieczeństwach kowariancji macierzy.
Tablica kowariancji wydaje się wskazywać na wyjaśnienie, ale nie mogę o tym myśleć.
Aktualny JDK jest 1.8.0.0_91
Zastanawiam się, czy możesz wpaść na wymazanie typu Java w generycznych? Po prostu myśl, nie mam czasu, by spojrzeć na bliżej ATM. – Ukko