Die vorherigen Antworten erläutern die Typparameter (T, E usw.), erklären jedoch nicht den Platzhalter "?" Oder die Unterschiede zwischen ihnen, daher werde ich darauf eingehen.
Um es klar zu machen: Die Platzhalter- und Typparameter sind nicht identisch. Wenn Typparameter eine Art Variable (z. B. T) definieren, die den Typ für einen Bereich darstellt, ist dies beim Platzhalter nicht der Fall: Der Platzhalter definiert nur eine Reihe zulässiger Typen, die Sie für einen generischen Typ verwenden können. Ohne Begrenzung ( extends
oder super
) bedeutet der Platzhalter "hier einen beliebigen Typ verwenden".
Der Platzhalter steht immer in spitzen Klammern und hat nur im Zusammenhang mit einem generischen Typ eine Bedeutung:
public void foo(List<?> listOfAnyType) {...} // pass a List of any type
noch nie
public <?> ? bar(? someType) {...} // error. Must use type params here
oder
public class MyGeneric ? { // error
public ? getFoo() { ... } // error
...
}
Es wird verwirrender, wo sie sich überschneiden. Beispielsweise:
List<T> fooList; // A list which will be of type T, when T is chosen.
// Requires T was defined above in this scope
List<?> barList; // A list of some type, decided elsewhere. You can do
// this anywhere, no T required.
Es gibt viele Überschneidungen bei den Methodendefinitionen. Die folgenden sind funktional identisch:
public <T> void foo(List<T> listOfT) {...}
public void bar(List<?> listOfSomething) {...}
Also, wenn es Überlappungen gibt, warum das eine oder das andere verwenden? Manchmal ist es ehrlich gesagt nur Stil: Einige Leute sagen, wenn Sie keinen Typparameter benötigen , sollten Sie einen Platzhalter verwenden, um den Code einfacher / lesbarer zu machen. Ein Hauptunterschied, den ich oben erklärt habe: Typparameter definieren eine Typvariable (z. B. T), die Sie an anderer Stelle im Bereich verwenden können. der Platzhalter nicht. Ansonsten gibt es zwei große Unterschiede zwischen Typparametern und dem Platzhalter:
Typparameter können mehrere Begrenzungsklassen haben. Der Platzhalter kann nicht:
public class Foo <T extends Comparable<T> & Cloneable> {...}
Der Platzhalter kann Untergrenzen haben. Typparameter können nicht:
public void bar(List<? super Integer> list) {...}
Oben List<? super Integer>
definiert das Integer
als Untergrenze des Platzhalters, was bedeutet, dass der Listentyp Integer oder ein Supertyp von Integer sein muss. Generische Typbegrenzungen gehen über das hinaus, was ich im Detail behandeln möchte. Kurz gesagt, Sie können definieren, welche Typen ein generischer Typ sein kann. Dies ermöglicht die polymorphe Behandlung von Generika. ZB mit:
public void foo(List<? extends Number> numbers) {...}
Sie können einen Pass List<Integer>
, List<Float>
, List<Byte>
etc. für numbers
. Ohne Typbegrenzung funktioniert dies nicht - so sind Generika.
Schließlich ist hier eine Methodendefinition, die den Platzhalter verwendet, um etwas zu tun, von dem ich glaube, dass Sie es nicht anders machen können:
public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) {
numberSuper.add(elem);
}
numberSuper
kann eine Liste der Zahlen oder ein beliebiger Supertyp der Zahl sein (z. B. List<Object>
) und elem
muss Zahl oder ein beliebiger Untertyp sein. Bei allen .add()
Einschränkungen kann der Compiler sicher sein, dass dies typsicher ist.