2013-02-28 12 views
6

Tak więc czytałem na lekach generycznych, aby ponownie zapoznać się z pojęciami, szczególnie w przypadku symboli wieloznacznych, ponieważ rzadko je używam lub napotykam na nie. Z lektury, którą zrobiłem, nie rozumiem, dlaczego używają symboli wieloznacznych. Jednym z przykładów, który napotykam, jest następujący.Jawne generics: wildcards

void printCollection(Collection<?> c) { 
    for (Object o : c){ 
    System.out.println(o); 
    } 
} 

Dlaczego nie piszesz to jako:

<T> void printCollection(Collection<T> c) { 
    for(T o : c) { 
     System.out.println(o); 
    } 
} 

Kolejny przykład ze strony internetowej Oracle:

public static double sumOfList(List<? extends Number> list) { 
    double s = 0.0; 
    for (Number n : list) 
     s += n.doubleValue(); 
    return s; 
} 

Dlaczego nie jest to napisane jako

public static <T extends Number> double sumOfList(List<T> list) { 
    double s = 0.0; 
    for (Number n : list) 
     s += n.doubleValue(); 
    return s; 
} 

Am Brakuje mi czegoś?

+0

Możliwe [duplikat] (http://stackoverflow.com/questions/10943137/difference-between-generic-type-and-wildcard-type) – Jayamohan

+1

@Jayamohan Nie zgadzam się. –

Odpowiedz

3

Od Oracle:

Jedno pytanie, które nasuwa brzmi: kiedy należy użyć metody rodzajowe, i kiedy należy używać typów wieloznacznych? Aby zrozumieć odpowiedź, przyjrzyjmy się kilku metodom z bibliotek kolekcji.

interface Collection<E> { 
    public boolean containsAll(Collection<?> c); 
    public boolean addAll(Collection<? extends E> c); 
} 

Mogliśmy stosowane metody generyczne tutaj Zamiast:

interface Collection<E> { 
    public <T> boolean containsAll(Collection<T> c); 
    public <T extends E> boolean addAll(Collection<T> c); 
    // Hey, type variables can have bounds too! 
} 

Jednak zarówno containsAll i AddAll parametr typu T jest używany tylko raz. Typ zwracany nie zależy od parametru typu, ani też żaden inny argument metody (w tym przypadku jest tylko jeden argument). To mówi nam, że argument typu jest używany dla polimorfizmu; jego jedynym skutkiem jest umożliwienie użycia różnych rzeczywistych typów argumentów w różnych miejscach wywoływania. W takim przypadku należy użyć symboli wieloznacznych. Symbole wieloznaczne są zaprojektowane do obsługi elastycznego podtytułu, co staramy się tutaj wyrazić.

Tak więc dla pierwszego przykładu dzieje się tak, ponieważ operacja nie zależy od typu.

Po drugie, dzieje się tak tylko dlatego, że zależy tylko od klasy Number.

+0

Dzięki, wszystkie wyjaśnienia były dobre, jednak ten z jakiegoś powodu pomógł mi go zrozumieć. –

3

Dlaczego komplikacje są bardziej skomplikowane niż muszą być? Przykłady pokazują the simplest thing that could possibly work - i przykłady te nie próbują zilustrować ogólnych metod.

Dlaczego nie piszesz to jako:

<T> void printCollection(Collection<T> c) { 
    for(T o : c) { 
     System.out.println(o); 
    } 
} 

Ponieważ System.out.println() może zaakceptować obiektów, więc nic bardziej specyficzne potrzeby.

Dlaczego to nie jest napisane jak

public static <T extends Number> double sumOfList(List<T> list) { 
    double s = 0.0; 
    for (Number n : list) 
     s += n.doubleValue(); 
    return s; 
} 

Ponownie, ponieważ nie trzeba inną parametryzację dla każdego innego T extends Number. Niezwykła metoda akceptująca numer List<? extends Number> jest wystarczająca.

+0

Myślę, że mogę po prostu nie zrozumieć różnicy między tymi dwoma, czy są one takie same? W jakiej sytuacji w ogóle bym się znalazł, nie można go rozwiązać za pomocą T, a nie? Mam przeczucie, że zupełnie nie rozumiem znaczenia symbolu wieloznacznego. –

+2

@ JonTaylor: Nie ma żadnej sytuacji, której nie można rozwiązać za pomocą 'T' zamiast'? ', Ale chodzi o to, że'? 'Nie potrzebuje nazwy i oznacza, że ​​naprawdę cię to nie obchodzi jaki jest typ. –

+0

@LouisWasserman 'Class.forName' zwracający' Class 'może być przykładem tylko odpowiedniego symbolu wieloznacznego. –

3

Prawdą jest, że jeśli typ parametru metody zawiera symbol pierwszego poziomu z górną granicą, można go zastąpić parametrem typu.

przykłady licznika (wieloznaczny nie może być zastąpiony przez parametr typ)

List<?> foo() // wildcard in return type 

    void foo(List<List<?>> arg) // deeper level of wildcard 

    void foo(List<? super Number> arg) // wildcard with lower bound 

Teraz dla przypadków, które mogą być rozwiązane przez każdą ze wieloznacznego lub typu parametru

 void foo1(List<?> arg) 

    <T> void foo2(List<T> arg) 

Powszechnie uważa się, że foo1() jest bardziej stylowy niż foo2(). To chyba trochę subiektywne. Osobiście uważam, że podpis foo1() jest łatwiejszy do zrozumienia. I foo1() jest w przeważającej mierze przyjęty w branży, więc lepiej postępować zgodnie z konwencją.

foo1() traktuje również nieco bardziej abstrakcyjnie jako arg, ponieważ nie można z łatwością wykonać arg.add(something) w foo1(). Oczywiście można to łatwo obejść (tj. Przekazać arg do foo2()!). Powszechną praktyką jest również, że publiczna metoda wygląda tak, jak w przypadku metody publicznej: foo1(), która jest wewnętrznie przekazywana do prywatnego foo2().

Istnieją również przypadki, w których wieloznaczny nie zrobi i potrzebny jest parametr typu:

<T> void foo(List<T> foo1, List<T> foo2); // can't use 2 wildcards. 

Ta dyskusja jest tak daleko o podpis metody. W innych miejscach symbole wieloznaczne mogą być niezbędne, gdy nie można wprowadzić parametrów typu.

+0

+1 Wspomnienie, że parametry typu [nie mogą mieć niższych granic] (http://stackoverflow.com/questions/2800369/bounding-generics- with-super-keyword). –