Unterschied zwischen Liste, Liste <?>, Liste <T>, Liste <E> und Liste <Objekt>

194

Was sind die Unterschiede zwischen List, List<?>, List<T>, List<E>, und List<Object>?

1. Liste

List: ist ein Rohtyp, daher nicht typesafe. Es wird nur dann ein Laufzeitfehler generiert, wenn das Casting schlecht ist. Wir möchten einen Fehler bei der Kompilierung, wenn die Besetzung schlecht ist. Nicht zu empfehlen.

2. Liste <?>

List<?>ist eine unbegrenzte Wildcard. Aber ich bin nicht sicher, wofür es ist? Ich kann List<?>ohne Probleme drucken :

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

Warum kann ich keine Elemente zu einem hinzufügen List<?>?

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

3. Listen Sie <T> auf

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

Ich verstehe diese Syntax nicht. Ich habe so etwas gesehen und es funktioniert:

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

Manchmal sehe ich <T>, oder <E>, oder <U>, <T,E>. Sind sie alle gleich oder repräsentieren sie etwas anderes?

4. Listen Sie <Objekt> auf

Dies gibt den Fehler "Die Methode test(List<Object>)ist für das Argument nicht anwendbar List<String>":

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

Wenn ich das versuche, bekomme ich "Kann nicht von List<String>bis gewirkt werden List<Object>":

test((List<Object>) names);

Ich bin verwirrt. Stringist eine Unterklasse von Object, warum ist also keine List<String>Unterklasse von List<Object>?

Thang Pham
quelle

Antworten:

77

1) Richtig

2) Sie können sich diese Liste als "schreibgeschützt" vorstellen, in der Sie sich nicht für den Typ der Elemente interessieren. Könnte zB von einer Methode verwendet werden, die die Länge der Liste zurückgibt.

3) T, E und U sind gleich, aber die Leute neigen dazu, z. B. T für Typ, E für Element, V für Wert und K für Schlüssel zu verwenden. Die kompilierte Methode gibt an, dass ein Array eines bestimmten Typs verwendet wurde und ein Array desselben Typs zurückgegeben wird.

4) Sie können keine Orangen und Äpfel mischen. Sie könnten Ihrer Zeichenfolgenliste ein Objekt hinzufügen, wenn Sie eine Zeichenfolgenliste an eine Methode übergeben könnten, die Objektlisten erwartet. (Und nicht alle Objekte sind Zeichenfolgen)

Kaj
quelle
2
+1 für schreibgeschützte Liste auf 2. Ich schreibe einige Codes, um dies zu demonstrieren 2. tyvm
Thang Pham
Warum sollten die Leute List<Object>für verwenden?
Thang Pham
3
Auf diese Weise können Sie eine Liste erstellen, in der alle Arten von Elementen akzeptiert werden. Sie werden nur selten verwendet.
Kaj
Eigentlich würde ich nicht glauben, dass irgendjemand es benutzen würde, wenn man bedenkt, dass man jetzt überhaupt keine Kennung haben kann.
if_zero_equals_one
1
@if_zero_equals_one Ja, aber Sie würden dann eine Compiler-Warnung erhalten (es würde warnen und sagen, dass Sie Raw-Typen verwenden), und Sie möchten Ihren Code niemals mit Warnungen kompilieren.
Kaj
26

Für den letzten Teil: Obwohl String eine Teilmenge von Object ist, wird List <String> nicht von List <Object> geerbt.

Farshid Zaker
quelle
11
Sehr guter Punkt; Viele gehen davon aus, dass, weil Klasse C von Klasse P erbt, diese Liste <C> auch von Liste <P> erbt. Wie Sie bereits betont haben, ist dies nicht der Fall. Der Grund dafür war, dass wir, wenn wir von List <String> in List <Object> umwandeln konnten, Objekte in diese Liste einfügen konnten, wodurch der ursprüngliche Vertrag von List <String> verletzt wurde, wenn versucht wurde, ein Element abzurufen.
Peter
2
+1. Guter Punkt auch. Warum sollten die Leute List<Object>für verwenden?
Thang Pham
9
Mit List <Object> kann eine Liste von Objekten verschiedener Klassen gespeichert werden.
Farshid Zaker
20

Die Notation List<?>bedeutet "eine Liste von etwas (aber ich sage nicht was)". Da der Code intest für jede Art von Objekt in der Liste funktioniert, funktioniert dies als formaler Methodenparameter.

Für die Verwendung eines Typparameters (wie in Punkt 3) muss der Typparameter deklariert werden. Die Java-Syntax dafür ist, <T>vor die Funktion zu stellen. Dies ist genau analog zum Deklarieren formaler Parameternamen für eine Methode, bevor die Namen im Methodenkörper verwendet werden.

Wenn man a List<Object>nicht akzeptiert List<String>, macht das Sinn, weil a Stringnicht ist Object; es ist eine Unterklasse von Object. Das Update ist zu deklarieren public static void test(List<? extends Object> set) .... Aber dann extends Objectist das überflüssig, weil jede Klasse direkt oder indirekt erweitert wird Object.

Ted Hopp
quelle
Warum sollten die Leute List<Object>für verwenden?
Thang Pham
10
Ich denke, "eine Liste von etwas" ist eine bessere Bedeutung, List<?>da die Liste von einem bestimmten, aber unbekannten Typ ist. List<Object>wäre wirklich "eine Liste von allem", da es tatsächlich alles enthalten kann.
ColinD
1
@ColinD - Ich meinte "alles" im Sinne von "irgendetwas". Aber du hast recht; es bedeutet "eine Liste von etwas, aber ich werde dir nicht sagen, was".
Ted Hopp
@ColinD es war er gemeint, warum hast du seine Worte wiederholt? Ja, es wurde mit ein wenig anderen Worten geschrieben, aber die Bedeutung ist dieselbe ...
user25
14

Der Grund, auf den Sie nicht umwandeln können List<String>, List<Object>ist, dass Sie damit die Einschränkungen des List<String>.

Denken Sie an das folgende Szenario: Wenn ich ein habe List<String>, soll es nur Objekte vom Typ enthalten String. (Die einfinal Klasse)

Wenn ich das in a List<Object>umwandeln kann, kann ich Objectdiese Liste ergänzen und damit gegen den ursprünglichen Vertrag von verstoßenList<String> .

Wenn also Klasse Cvon Klasse erbt P, kann man im Allgemeinen nicht sagen, dass GenericType<C>auch von Klasse erbt GenericType<P>.

NB Ich habe dies bereits in einer früheren Antwort kommentiert, wollte es aber erweitern.

Peter
quelle
tyvm, ich lade sowohl Ihren Kommentar als auch Ihre Antwort hoch, da dies eine sehr gute Erklärung ist. Wo und warum sollten die Leute jetzt verwenden List<Object>?
Thang Pham
3
Im Allgemeinen sollten Sie nicht verwenden, List<Object>da es den Zweck von Generika irgendwie zunichte macht. Es gibt jedoch Fälle, in denen alter Code möglicherweise Listunterschiedliche Typen akzeptiert. Daher möchten Sie den Code möglicherweise nachrüsten, um die Typparametrisierung zu verwenden, um Compiler-Warnungen für Rohtypen zu vermeiden. (Aber die Funktionalität ist unverändert)
Peter
5

Ich würde empfehlen, Java-Puzzler zu lesen. Es erklärt Vererbung, Generika, Abstraktionen und Platzhalter in Deklarationen recht gut. http://www.javapuzzlers.com/

if_zero_equals_one
quelle
5

Lassen Sie uns im Kontext der Java-Geschichte darüber sprechen.

  1. List::

Liste bedeutet, dass es jedes Objekt enthalten kann. Liste war in der Version vor Java 5.0; Java 5.0 führte List aus Gründen der Abwärtskompatibilität ein.

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

?bedeutet unbekanntes Objekt, kein Objekt; Die Wildcard- ?Einführung dient zur Lösung des von Generic Type erstellten Problems. siehe Platzhalter ; Dies verursacht aber auch ein anderes Problem:

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

Bedeutet generische Deklaration unter der Voraussetzung, dass in Ihrem Projekt Lib kein T- oder E-Typ vorhanden ist.

  1. List< Object> bedeutet generische Parametrisierung.
phil
quelle
5

In Ihrem dritten Punkt kann "T" nicht aufgelöst werden, da es nicht deklariert ist. Wenn Sie eine generische Klasse deklarieren, können Sie normalerweise "T" als Namen des gebundenen Typparameters verwenden . Viele Online-Beispiele, einschließlich der Tutorials von Orakel, verwenden "T" als Name des Typparameters, z. B. deklarieren Sie eine Klasse wie:

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

}

Sie sagen, dass die FooHandler's operateOnFooMethode eine Variable vom Typ "T" erwartet, die in der Klassendeklaration selbst deklariert ist. Vor diesem Hintergrund können Sie später eine andere Methode wie hinzufügen

public void operateOnFoos(List<T> foos)

In allen Fällen, in denen entweder T, E oder U alle Bezeichner des Typparameters enthalten, können Sie sogar mehr als einen Typparameter haben, der die Syntax verwendet

public class MyClass<Atype,AnotherType> {}

In Ihrem vierten Ponint ist Sting zwar effektiv ein Subtyp von Objekt, in generischen Klassen gibt es jedoch keine solche Beziehung, es List<String>gibt keinen Subtyp, da List<Object>es sich aus Compilersicht um zwei verschiedene Typen handelt. Dies wird am besten in diesem Blogeintrag erklärt

Harima555
quelle
5

Theorie

String[] kann gegossen werden Object[]

aber

List<String>kann nicht gegossen werden List<Object>.

Trainieren

Bei Listen ist dies subtiler, da beim Kompilieren der Typ eines an eine Methode übergebenen List-Parameters nicht überprüft wird. Die Methodendefinition könnte genauso gut sagen List<?>- aus Sicht des Compilers ist sie äquivalent. Aus diesem Grund gibt das Beispiel Nr. 2 des OP Laufzeitfehler und keine Kompilierungsfehler.

Wenn Sie einen List<Object>an eine Methode übergebenen Parameter sorgfältig behandeln, damit Sie keine Typprüfung für ein Element der Liste erzwingen, können Sie Ihre Methode definieren lassen, indem Sie List<Object>einen List<String>Parameter aus dem aufrufenden Code akzeptieren .

A. Dieser Code gibt also keine Kompilierungs- oder Laufzeitfehler und funktioniert tatsächlich (und vielleicht überraschend?):

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. Dieser Code gibt einen Laufzeitfehler aus:

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. Dieser Code gibt einen Laufzeitfehler aus ( 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
}

In B ist der Parameter zur Kompilierungszeit setnicht typisiert List: Der Compiler sieht ihn als List<?>. Es liegt ein Laufzeitfehler vor, da zur Laufzeit setdas eigentliche Objekt übergeben wird, von dem übergeben wird main(), und das ist a List<String>. A List<String>kann nicht besetzt werden List<Object>.

In C seterfordert der Parameter eine Object[]. Es gibt keinen Kompilierungsfehler und keinen Laufzeitfehler, wenn es mit einem String[]Objekt als Parameter aufgerufen wird. Das liegt daran, dass String[]Casts zu Object[]. Aber das tatsächliche Objekt, das von empfangen wird, test()bleibt ein String[], es hat sich nicht geändert. So wird das paramsObjekt auch zu einem String[]. Und Element 0 von a String[]kann nicht a zugewiesen werden Long!

(Hoffentlich habe ich hier alles richtig gemacht, wenn meine Argumentation falsch ist, werde ich es sicher von der Community erfahren. AKTUALISIERT: Ich habe den Code in Beispiel A so aktualisiert, dass er tatsächlich kompiliert wird, während ich immer noch den gemachten Punkt zeige.)

radfast
quelle
Ich habe dein Beispiel A ausprobiert, es funktioniert nicht : List<Object> cannot be applied to List<String>. Sie können nicht passieren ArrayList<String>ein Verfahren , das erwartet ArrayList<Object>.
Parsecer
Vielen Dank, ziemlich spät am Datum habe ich Beispiel A so angepasst, dass es jetzt funktioniert. Die Hauptänderung bestand darin, argsList generisch in main () zu definieren.
radfast
4

Problem 2 ist in Ordnung, weil "System.out.println (set);" bedeutet "System.out.println (set.toString ());" set ist eine Instanz von List, daher ruft Complier List.toString () auf.

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: Diese Symbole sind gleich, aber Sie können ihnen unterschiedliche Spezifikationen geben. Beispielsweise:

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

Problem 4: Die Sammlung erlaubt keine Typparameter-Kovarianz. Aber Array erlaubt Kovarianz.

yoso
quelle
0

Sie haben Recht: String ist eine Teilmenge von Object. Da String "präziser" als Object ist, sollten Sie ihn umwandeln, um ihn als Argument für System.out.println () zu verwenden.

Patapizza
quelle