2016-10-28 11 views
6

Trudno wyjaśnić słowami, ale Java Generics ma dla mnie nieoczekiwany efekt. Spodziewałem się, że jeśli powiem, że lista jest typu ? extends Object, mogę przechowywać wszystko tam. Dlatego też, jeśli lista typu Wrapper<? extends Object>, mogę przechowywać tam wszelkiego rodzaju Wrapper. I tak dalej. Ma to sens dla mnie. Ale załóżmy, że mamy:Jawne generyczne dziwaczne zachowanie

private static class Wrapper<T> { 
    public Wrapper(T t) { /**/ } 
} 

I chcę coś takiego:

private static final List<Wrapper<Wrapper<? extends Object>>> ls1 = new ArrayList<>(); 

Zauważ, że to daje mi błąd:

public static <T> doit(T t) { 
    Wrapper<Wrapper<T>> l1 = new Wrapper<>(new Wrapper<>(t)); 
    ls1.add(l1); // nok 
    // add (Wrapper<Wrapper<? extends java.lang.Object>>) in List 
    // cannot be applied to (Wrapper<Wrapper<T>> 
} 

ale gdybym owinąć owijki w drugorzędnym Wrapper (rs), a następnie:

private static class C<T> extends Wrapper<Wrapper<T>> { 
    public C(T t) { 
     super(new Wrapper<>(t)); 
    } 
} 

private static final List<C<? extends Object>> ls2 = new ArrayList<>(); 

public static <T> doit(T t) { 
    ls2.add(new C<>(t)); // ok 
} 

Zauważ, że to jest to samo; to nie ma dla mnie sensu.

PS. W moim przypadku nie robię wrappera, ale ThreadLocal klasy ogólnej.

+0

Więc co naprawdę chcesz to 'List >>'? – Kayaman

+0

@Kayaman Tak, ale nie mogę tam przechowywać Listy >. Chcę móc przechowywać dowolnego rodzaju Wrapper >. –

+0

Czy możesz zmienić swój przykład na konkretny, np. "Ciąg", jak w swoim komentarzu? Rzuca mi to, aby zobaczyć deklarację referencyjną typu "T". – Zircon

Odpowiedz

2

myślę

? extends Object ---> equals ? 

Można zrobić tak

List<Wrapper<Wrapper<?>>> ls1 = new ArrayList<>(); 

And 

Wrapper<Wrapper<?>> l1 = new Wrapper<>(new Wrapper<>(t)); 
ls1.add(l1); // OK 
0

Szczerze mówiąc, musiałbym przyjrzeć się temu bardziej szczegółowo, dlaczego to nie działa. Najprawdopodobniej jest to związane z wymazywaniem typu ogólnego. Co mam, to obejście, wiem, że to rozwiązanie traci typ dla obiektu l1. Jeśli to jest w porządku, możesz to wziąć.

Wrapper<Wrapper<? extends Object>> l1 = new Wrapper<>(new Wrapper<>(t)); 
ls1.add(l1); 
0

Zasadniczo trzeba zdecydować, co jest ważniejsze, będąc generic nad tym, co chcesz umieścić do owijki , ponad lub co otrzymasz out of wrapper (nie można bezpiecznie obu). Po podjęciu decyzji będziesz musiał wybrać między extends i super.

Jeśli chcesz zapisać wskaźnik tylko, wystarczy tylko List<Wrapper<Wrapper>>, ale później musisz wykonać rzut.


Podczas używania extends następnie ? extends Foo oznacza dowolny typ danych zwrócone przez struktura podtypem typu Foo (lub Foo siebie).

Na przykład, jeśli masz void doIt1(List< ? extends Mammal> l ){} można przekazać w List<Human>, List<Primate>, List<Simian> lub List<Mammal>.

Jednak podczas korzystania z ? extends nie wiesz, co można bezpiecznie umieścić.

Rozważ to:

void addElephant(List< ? extends Mammal> l ){ 
    l.add(new Elephant()); //wrong! 
} 

Jest to wyraźnie niebezpieczne! Mogłem przejść w List<Human> jako l, a teraz moja lista Human s ma Elephant w tym!

Z drugiej strony trzeba super gdzie ? super Foo oznacza, że ​​pojemnik może wziąć Foo.

Więc dla void doIt2(List< ? extends Human> l ){} jak można przejść w List<Human>, List<Primate>, List<Simian>, List<Mammal> albo List<Object>.

void (List< ? super Human> l ){ 
    l.add(new Human()); 
} 

I to jest w porządku, ale nie możesz zagwarantować niczego, co dostaniesz z pojemnika.

void (List< ? super Human> l ){ 
    Human h = l.get(0); //wrong! 
} 

To dlatego, że jeśli l było naprawdę List<Primate> może mieć mieszankę Human s oraz Gorilla s i nie ma gwarancji, że pierwszy element będzie Human.

W tym celu można uzyskać skrót P.E.C.S = "[a] P roducer ** E xtends [ale] C onsumer S zamykania drzwi".

Jeśli pojemnik jest powrót obiektu do metody (np get), to jesteś „produkcji” (a więc używać extends), w przeciwnym razie, jeśli elementy (np add) do pojemnika przyjmująca pojemnik jest "konsumowanie" obiektu, więc użyj "super". (Nb: Podatne części do zapamiętania o PECS jest to, że jest to z punktu widzenia kontenera i , a nie kodu wywołującego!).

Jeśli chcesz zarówno produkować i konsumować trzeba mieć konkretny wykaz typu List<Primate> gdzie można dodać Human S i odzyskać Primate s.


Zobacz także:

+0

Szczerze mówiąc, wydaje się, że wkładasz w to dużo wysiłku, ale on po prostu chce zachować bezpieczeństwo typu l1 i napisać esej na temat metody. –

+0

@ mh-dev tak, chciałem wyjaśnić, co było nie tak z naiwnym podejściem do jego pytania _i_ jako odniesienie, aby móc wskazać naszych stażystów i młodszych programistów. – ArtB