Przyczyny tego są oparte na jak Java implementuje rodzajowych.
Tablice Przykład
z tablicami można zrobić (tablice są kowariantna)
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
Ale co by się stało, jeśli starają się to zrobić?
Number[0] = 3.14; //attempt of heap pollution
Ta ostatnia linia będzie skompilować w porządku, ale jeśli uruchomić ten kod, można uzyskać ArrayStoreException
. Ponieważ próbujesz umieścić double w tablicy liczb całkowitych (bez względu na to, czy uzyskujesz dostęp za pośrednictwem odnośnika liczbowego).
Oznacza to, że można oszukać kompilator, ale nie można oszukać systemu typu uruchomieniowego. A to dlatego, że tablice są to, co nazywamy możliwymi do odzyskania typami. Oznacza to, że w środowisku wykonawczym Java wie, że ta tablica została faktycznie utworzona jako tablica liczb całkowitych, do której po prostu dostęp uzyskuje się przez odwołanie typu Number[]
.
Tak więc, jak widać, jedną rzeczą jest faktyczny typ obiektu, inną rzeczą jest rodzaj odniesienia, którego używasz, aby uzyskać do niego dostęp, prawda?
Problem z Java rodzajowych
Teraz problem z Java typów generycznych jest to, że informacja o typie jest odrzucana przez kompilator i nie jest dostępna w czasie wykonywania. Ten proces nazywa się type erasure. Istnieją dobre powody dla implementowania takich generycznych w Javie, ale jest to długa historia i ma ona związek z binarną kompatybilnością z wcześniej istniejącym kodem.
Ale ważne jest to, że ponieważ w czasie wykonywania nie ma informacji o typie, nie ma sposobu, aby zapewnić, że nie popełniamy zanieczyszczenia hałdy.
Na przykład
List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap polution
Jeśli kompilator Java nie powstrzyma cię od robienia tego, system typu wykonawczego nie może cię powstrzymać albo, ponieważ nie ma sposobu, w czasie wykonywania, w celu ustalenia, że lista ta była powinna być jedynie listą całkowitą. Środowisko wykonawcze Java pozwoli ci umieścić na liście tę, co chcesz, kiedy powinna zawierać tylko liczby całkowite, ponieważ po jej utworzeniu została zadeklarowana jako lista liczb całkowitych.
W związku z tym projektanci Javy upewnili się, że nie można oszukać kompilatora. Jeśli nie możesz oszukać kompilatora (tak jak możemy to zrobić z tablicami), nie możesz oszukać systemu typu runtime.
Jako takie, mówimy, że typy ogólne to niereifiable.
Najwyraźniej utrudniłoby to polimorfizm. Rozważmy następujący przykład:
static long sum(Number[] numbers) {
long summation = 0;
for(Number number : numbers) {
summation += number.longValue();
}
return summation;
}
Teraz można go używać tak:
Integer[] myInts = {1,2,3,4,5};
Long[] myLongs = {1L, 2L, 3L, 4L, 5L};
Double[] myDoubles = {1.0, 2.0, 3.0, 4.0, 5.0};
System.out.println(sum(myInts));
System.out.println(sum(myLongs));
System.out.println(sum(myDoubles));
Ale jeśli spróbujesz wdrożyć ten sam kod z kolekcji generycznych, nie uda:
static long sum(List<Number> numbers) {
long summation = 0;
for(Number number : numbers) {
summation += number.longValue();
}
return summation;
}
Otrzymasz kompilator eros, jeśli spróbujesz ...
List<Integer> myInts = asList(1,2,3,4,5);
List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L);
List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0);
System.out.println(sum(myInts)); //compiler error
System.out.println(sum(myLongs)); //compiler error
System.out.println(sum(myDoubles)); //compiler error
Rozwiązaniem jest nauczenie się używania dwóch zaawansowanych funkcji generycznych języka Java, znanych jako kowariancja i kontrawariancja.
kowariancji
Z kowariancji można przeczytać przedmioty od struktury, ale nie można napisać nic do niego. Wszystkie te są ważnymi deklaracjami.
List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>()
List<? extends Number> myNums = new ArrayList<Double>()
I można odczytać z myNums
:
Number n = myNums.get(0);
Ponieważ można mieć pewność, że bez względu na rzeczywisty lista zawiera, można upcasted do numeru (w końcu coś, co rozszerza numer jest numerem , prawda?)
Jednak nie wolno włożyć niczego w kowariantną strukturę.
myNumst.add(45L); //compiler error
Nie będzie to dozwolone, ponieważ Java nie może zagwarantować, jaki jest rzeczywisty typ obiektu w ogólnej strukturze. Może to być wszystko, co rozszerza Numer, ale kompilator nie może być tego pewien. Możesz więc czytać, ale nie pisać.
kontrawariancja
Z kontrawariancji można zrobić odwrotnie. Możesz umieścić rzeczy w ogólnej strukturze, ale nie możesz ich odczytać.
List<Object> myObjs = new List<Object();
myObjs.add("Luke");
myObjs.add("Obi-wan");
List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);
W tym przypadku, rzeczywisty charakter obiektu jest lista obiektów, a przez kontrawariancji, można umieścić numery do niego, w zasadzie, ponieważ wszystkie liczby mają przedmiot jako ich wspólnego przodka. Jako takie, wszystkie Liczby są obiektami i dlatego jest to ważne.
Jednak nie można bezpiecznie odczytać niczego z tej sprzecznej struktury, zakładając, że dostaniesz numer.
Number myNum = myNums.get(0); //compiler-error
Jak widać, jeśli kompilator wolno Ci napisać ten wiersz, to dostanie ClassCastException przy starcie.
Get/Put Zasada
Jako takie, należy użyć kowariancji kiedy tylko zamierzają przyjmować wartości rodzajowe z konstrukcji, należy kontrawariancji kiedy tylko zamierzają wprowadzić wartości generycznych w strukturze i używać dokładna generic wpisz, kiedy chcesz zrobić oba.
Najlepszy przykład jaki mam to kopiowanie dowolnych numerów z jednej listy na inną listę. To tylko dostaje przedmiotów ze źródła, a tylko umieszcza przedmiotów w przeznaczeniu.
public static void copy(List<? extends Number> source, List<? super Number> destiny) {
for(Number number : source) {
destiny.add(number);
}
}
Dzięki kompetencji kowariancji i kontrawariancji Działa to na takim wypadku:
List<Integer> myInts = asList(1,2,3,4);
List<Double> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();
copy(myInts, myObjs);
copy(myDoubles, myObjs);
check to http://stackoverflow.com/questions/2745265/is-listdog-a-subclass -of-listanimal-why-arent-javas-generics-implicit – NPKR