Różnica między listą, listą <?>, Listą <T>, listą <E> i listą <Obiekt>

194

Jakie są różnice między List, List<?>, List<T>, List<E>, i List<Object>?

1. Lista

List: jest typem surowym, dlatego nie typesafe. Wygeneruje błąd czasu wykonywania tylko wtedy, gdy rzutowanie jest złe. Chcemy błędu czasu kompilacji, gdy rzutowanie jest złe. Nie zaleca się używania.

2. Lista <?>

List<?>jest nieograniczonym znakiem wieloznacznym. Ale nie jestem pewien, do czego to służy? Mogę wydrukować List<?>bez problemu:

public static void test(List<?> list){
    System.out.println(list);   // Works
}

Dlaczego nie mogę dodać elementów do List<?>?

public static void test(List<?> list){
    list.add(new Long(2));     // Error
    list.add("2");             // Error
    System.out.println(list);
}

3. Lista <T>

public static void test(List<T> list){   // T cannot be resolved
    System.out.println(list);
}

Nie rozumiem tej składni. Widziałem coś takiego i działa:

public <T> T[] toArray(T[] a){
    return a;   
}

Czasami widzę <T>, albo <E>, lub <U>, <T,E>. Czy wszystkie są takie same, czy reprezentują coś innego?

4. Lista <Obiekt>

Daje to błąd „Metoda test(List<Object>)nie ma zastosowania do argumentu List<String>”:

public static void test(List<Object> list){
    System.out.println(list);
}

Jeśli spróbuję tego, otrzymam komunikat „Nie można przesyłać z List<String>do List<Object>”:

test((List<Object>) names);

Jestem zdezorientowany. Stringjest podklasą Object, więc dlaczego nie List<String>jest podklasą List<Object>?

Thang Pham
źródło

Odpowiedzi:

77

1) Prawidłowo

2) Możesz myśleć o tym jak o liście „tylko do odczytu”, w której nie zależy ci na typie elementów. Może być na przykład używany przez metodę zwracającą długość listy.

3) T, E i U są takie same, ale ludzie zwykle używają np. T dla typu, E dla elementu, V dla wartości i K dla klucza. Metoda, która się kompiluje, mówi, że pobrała tablicę określonego typu i zwraca tablicę tego samego typu.

4) Nie możesz mieszać pomarańczy i jabłek. Byłbyś w stanie dodać obiekt do swojej listy ciągów, jeśli możesz przekazać listę ciągów do metody, która oczekuje list obiektów. (I nie wszystkie obiekty są łańcuchami)

Kaj
źródło
2
+1 za listę tylko do odczytu 2. Piszę kilka kodów, aby to zademonstrować 2. tyvm
Thang Pham
Dlaczego ludzie mieliby używać List<Object>?
Thang Pham
3
Jest to sposób na utworzenie listy, która akceptuje dowolne elementy, rzadko z niej korzystasz.
Kaj
Właściwie nie sądziłbym, że ktoś by go użył, biorąc pod uwagę, że w ogóle nie możesz mieć identyfikatora.
if_zero_equals_one
1
@ if_zero_equals_one Tak, ale otrzymasz ostrzeżenie kompilatora (ostrzeże i powie, że używasz typów raw) i nigdy nie będziesz chciał kompilować kodu z ostrzeżeniami.
Kaj
26

Na koniec: Chociaż String jest podzbiorem Object, ale List <String> nie jest dziedziczony z List <Object>.

Farshid Zaker
źródło
11
Bardzo dobry punkt; wielu zakłada, że ​​ponieważ klasa C dziedziczy po klasie P, lista <C> dziedziczy również po liście <P>. Jak zauważyłeś, tak nie jest. Powodem było to, że jeśli moglibyśmy rzutować z Listy <String> na Listę <Obiekt>, wówczas moglibyśmy umieszczać Obiekty na tej liście, naruszając w ten sposób oryginalną umowę z Listą <String> podczas próby pobrania elementu.
Peter
2
+1. Dobra racja również. Dlaczego ludzie mieliby z tego korzystać List<Object>?
Thang Pham
9
List <Object> może służyć do przechowywania listy obiektów różnych klas.
Farshid Zaker
20

Notacja List<?>oznacza „listę czegoś (ale nie mówię co)”. Ponieważ kod testdziała dla dowolnego obiektu na liście, działa to jako parametr metody formalnej.

Użycie parametru type (jak w punkcie 3) wymaga zadeklarowania parametru type. Składnia Java jest umieszczana <T>przed funkcją. Jest to dokładnie analogiczne do zadeklarowania formalnej nazwy parametru metodzie przed użyciem nazw w treści metody.

Jeśli chodzi o List<Object>nieakceptowanie List<String>, ma to sens, ponieważ Stringnie jest Object; jest to podklasa Object. Poprawka polega na zadeklarowaniu public static void test(List<? extends Object> set) .... Ale to extends Objectjest zbędne, ponieważ każda klasa rozszerza bezpośrednio lub pośrednio Object.

Ted Hopp
źródło
Dlaczego ludzie mieliby używać List<Object>?
Thang Pham
10
Myślę, że „lista czegoś” ma lepsze znaczenie, List<?>ponieważ lista jest jakiegoś konkretnego, ale nieznanego typu. List<Object>byłby naprawdę „listą czegokolwiek”, ponieważ rzeczywiście może zawierać wszystko.
ColinD
1
@ColinD - Miałem na myśli „cokolwiek” w znaczeniu „jednej rzeczy”. Ale masz rację; oznacza „listę czegoś, ale nie powiem ci, co”.
Ted Hopp
@ColinD miał na myśli, dlaczego powtórzyłeś jego słowa? tak, zostało napisane trochę innymi słowami, ale znaczenie jest takie samo ...
użytkownik25
14

Powodem, dla którego nie można rzutować List<String>na List<Object>to, że to pozwala naruszać ograniczeniami List<String>.

Pomyśl o następującym scenariuszu: Jeśli mam List<String>, powinien on zawierać tylko obiekty typu String. (Która jest finalklasą)

Jeśli mogę to rzucić na List<Object>, to pozwala mi to dodać Objectdo tej listy, naruszając w ten sposób pierwotną umowę List<String>.

Ogólnie więc, jeśli klasa Cdziedziczy po klasie P, nie można powiedzieć, że GenericType<C>dziedziczy również po GenericType<P>.

NB: Skomentowałem to już w poprzedniej odpowiedzi, ale chciałem ją rozwinąć.

Piotr
źródło
tyvm, przesyłam zarówno twój komentarz, jak i odpowiedź, ponieważ jest to bardzo dobre wytłumaczenie. Teraz gdzie i dlaczego ludzie skorzystaliby List<Object>?
Thang Pham
3
Zasadniczo nie powinieneś używać, List<Object>ponieważ w pewnym sensie pokonuje to cel generycznych. Są jednak przypadki, w których stary kod może Listprzyjmować różne typy, dlatego warto zmodyfikować kod, aby użyć parametryzacji typów, aby uniknąć ostrzeżeń kompilatora dla typów surowych. (Ale funkcjonalność pozostaje niezmieniona)
Peter
5

Radziłbym czytać łamigłówki Java. W deklaracjach całkiem dobrze wyjaśnia dziedziczenie, generyczne, abstrakcje i symbole wieloznaczne. http://www.javapuzzlers.com/

if_zero_equals_one
źródło
5

Porozmawiajmy o nich w kontekście historii Java;

  1. List:

Lista oznacza, że ​​może zawierać dowolny obiekt. Lista była w wersji wcześniejszej niż Java 5.0; Java 5.0 wprowadziła Listę dla kompatybilności wstecznej.

List list=new  ArrayList();
list.add(anyObject);
  1. List<?>:

?oznacza nieznany Obiekt, a nie żaden Obiekt; ?wprowadzenie symboli wieloznacznych służy rozwiązaniu problemu zbudowanego przez Typ ogólny; zobacz symbole wieloznaczne ; ale powoduje to również inny problem:

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error
  1. List< T> List< E>

Oznacza ogólną deklarację przy założeniu braku typu T lub E w projekcie Lib.

  1. List< Object> oznacza ogólną parametryzację.
phil
źródło
5

W trzecim punkcie „T” nie może zostać rozwiązany, ponieważ nie jest zadeklarowany, zwykle kiedy deklarujesz klasę ogólną, możesz użyć „T” jako nazwy parametru typu powiązanego , wiele przykładów online, w tym samouczki Oracle, używa „T” jako nazwa parametru type, powiedzmy na przykład, że deklarujesz klasę jak:

public class FooHandler<T>
{
   public void operateOnFoo(T foo) { /*some foo handling code here*/}

}

mówisz, że FooHandler's operateOnFoometoda oczekuje zmiennej typu „T”, która jest zadeklarowana w samej deklaracji klasy, mając to na uwadze, możesz później dodać inną metodę, taką jak

public void operateOnFoos(List<T> foos)

we wszystkich przypadkach T, E lub U, tam są wszystkie identyfikatory parametru typu, możesz mieć nawet więcej niż jeden parametr typu, który używa składni

public class MyClass<Atype,AnotherType> {}

w twoim czwartym ponint, chociaż skutecznie Sting jest podtypem Object, w klasach generycznych nie ma takiej relacji, List<String>nie jest podtypem, List<Object>ponieważ są to dwa różne typy z punktu widzenia kompilatora, najlepiej to wyjaśnia ten wpis na blogu

Harima555
źródło
5

Teoria

String[] można rzucić na Object[]

ale

List<String>nie można rzucić na List<Object>.

Ćwiczyć

W przypadku list jest to bardziej subtelne, ponieważ w czasie kompilacji nie jest sprawdzany typ parametru List przekazywany do metody. Definicja metody mogłaby równie dobrze powiedzieć List<?>- z punktu widzenia kompilatora jest równoważna. Dlatego w przykładzie nr 2 OP podano błędy wykonania, a nie błędy kompilacji.

Jeśli List<Object>ostrożnie obchodzisz się z parametrem przekazanym do metody, aby nie wymuszać sprawdzania typu w żadnym elemencie listy, możesz zdefiniować metodę za pomocą, List<Object>ale w rzeczywistości zaakceptować List<String>parametr z kodu wywołującego.

Odp .: Więc ten kod nie spowoduje błędów kompilacji ani błędów w czasie wykonywania i faktycznie (a może zaskakująco?) Zadziała:

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test(argsList);  // The object passed here is a List<String>
}

public static void test(List<Object> set) {
    List<Object> params = new ArrayList<>();  // This is a List<Object>
    params.addAll(set);       // Each String in set can be added to List<Object>
    params.add(new Long(2));  // A Long can be added to List<Object>
    System.out.println(params);
}

B. Ten kod spowoduje błąd w czasie wykonywania:

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test1(argsList);
    test2(argsList);
}

public static void test1(List<Object> set) {
    List<Object> params = set;  // Surprise!  Runtime error
}

public static void test2(List<Object> set) {
    set.add(new Long(2));       // Also a runtime error
}

C. Ten kod spowoduje błąd w czasie wykonywania ( java.lang.ArrayStoreException: java.util.Collections$UnmodifiableRandomAccessList Object[]):

public static void main(String[] args) {
    test(args);
}

public static void test(Object[] set) {
    Object[] params = set;    // This is OK even at runtime
    params[0] = new Long(2);  // Surprise!  Runtime error
}

W B parametr setnie jest wpisywany Listw czasie kompilacji: kompilator widzi go jako List<?>. Wystąpił błąd środowiska wykonawczego, ponieważ w czasie wykonywania setstaje się rzeczywistym obiektem przekazywanym z main(), i jest to List<String>. List<String>Nie mogą być oddane do List<Object>.

W C, parametr setwymaga użycia Object[]. Nie ma błędu kompilacji ani błędu czasu wykonywania, gdy jest wywoływany z String[]obiektem jako parametrem. To dlatego, że String[]rzuca na Object[]. Ale rzeczywisty obiekt otrzymany przez test()pozostaje String[], nie zmienił się. Zatem paramsobiekt staje się również String[]. A elementu 0 String[]nie można przypisać do Long!

(Mam nadzieję, że mam wszystko tutaj, jeśli moje rozumowanie jest błędne, jestem pewien, że społeczność mi o tym poinformuje. ZAKTUALIZOWANO: Zaktualizowałem kod w przykładzie A, tak aby faktycznie się kompilował, jednocześnie pokazując poczynione punkty.)

radfast
źródło
Próbowałem swój przykład A to, że nie działa: List<Object> cannot be applied to List<String>. Nie możesz przejść ArrayList<String>do metody, która się spodziewa ArrayList<Object>.
parsecer
Dzięki, raczej późno, poprawiłem przykład A, aby działał. Główną zmianą było ogólne zdefiniowanie argsList w main ().
radfast
4

Problem 2 jest OK, ponieważ „System.out.println (set);” oznacza „System.out.println (set.toString ());” set jest instancją List, więc kompilator wywoła List.toString ();

public static void test(List<?> set){
set.add(new Long(2)); //--> Error  
set.add("2");    //--> Error
System.out.println(set);
} 
Element ? will not promise Long and String, so complier will  not accept Long and String Object

public static void test(List<String> set){
set.add(new Long(2)); //--> Error
set.add("2");    //--> Work
System.out.println(set);
}
Element String promise it a String, so complier will accept String Object

Problem 3: te symbole są takie same, ale możesz nadać im inną specyfikację. Na przykład:

public <T extends Integer,E extends String> void p(T t, E e) {}

Problem 4: Kolekcja nie pozwala na kowariancję parametrów typu. Ale tablica pozwala na kowariancję.

yoso
źródło
0

Masz rację: String jest podzbiorem Object. Ponieważ String jest bardziej „precyzyjny” niż Object, powinieneś rzucić go, aby użyć go jako argumentu dla System.out.println ().

patapizza
źródło