2012-10-19 9 views
5

Kiedy należy używać ogólnych rodzajów polimorficznych takich jak te i jakie są tego konsekwencje?dlaczego i co z polimorficznymi typami generycznymi

1. List<? super Dog> list = new ArrayList<Animal>(); 
2. List<? extends Animal> list = new ArrayList<Dog>(); 
3. List<?> list = new ArrayList<Dog>(); 

Czy ktoś używać coś jak

List<? super Dog> list = new ArrayList<Dog>(); 
List<? extends Animal> list = new ArrayList<Animal>(); 

Uwaga: Rozumiem kiedy ludzie używają List<? super Dog> lub List<? extends Animal> w definicji metod. Ale to, czego nie rozumiem, to polimorficzne generyczne tworzenie obiektów.

+0

check to http://stackoverflow.com/questions/2745265/is-listdog-a-subclass -of-listanimal-why-arent-javas-generics-implicit – NPKR

Odpowiedz

11

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); 
+1

Dzięki Edwin. To jest genialne wytłumaczenie – John

0

Użyliby:

  1. Jeśli potrzebują listę obiektów, które są nadklasą Dog. Na przykład: Animal.

  2. Jeśli potrzebują listy obiektów, które są zwierzętami (podklasa Animal). Na przykład Dog.

  3. Jeśli potrzebują listy obiektów, kropka. Którym może być na przykład lista psów.

+0

czy możesz wyjaśnić dalej? – John

Powiązane problemy