So beantworten Sie Ihre Frage direkt:
Beim universellen Typ muss die Verwendung von T
den Typparameter enthalten X
. Zum Beispiel T<String>
oder T<Integer>
. Für die existenziellen Typverwendungen von T
enthalten Sie diesen Typparameter nicht, da er unbekannt oder irrelevant ist - verwenden Sie ihn einfach T
(oder in Java)T<?>
).
Weitere Informationen:
Universelle / abstrakte Typen und existenzielle Typen sind eine Dualität der Perspektive zwischen dem Verbraucher / Kunden eines Objekts / einer Funktion und dem Produzenten / der Implementierung desselben. Wenn eine Seite einen universellen Typ sieht, sieht die andere einen existenziellen Typ.
In Java können Sie eine generische Klasse definieren:
public class MyClass<T> {
// T is existential in here
T whatever;
public MyClass(T w) { this.whatever = w; }
public static MyClass<?> secretMessage() { return new MyClass("bazzlebleeb"); }
}
// T is universal from out here
MyClass<String> mc1 = new MyClass("foo");
MyClass<Integer> mc2 = new MyClass(123);
MyClass<?> mc3 = MyClass.secretMessage();
- Aus der Sicht eines Kunden von
MyClass
, T
ist universell, weil Sie jeden Typ ersetzen könnenT
, wenn Sie diese Klasse verwenden , und Sie müssen die tatsächliche Art von T wissen , wann immer Sie eine Instanz verwendenMyClass
- Aus der Sicht der Instanzmethoden an
MyClass
sichT
ist es existenziell, weil es den wirklichen Typ von nicht kenntT
- Stellt in Java
?
den existenziellen Typ dar - wenn Sie sich also innerhalb der Klasse befinden, T
ist dies im Grunde genommen der Fall ?
. Wenn Sie eine Instanz von MyClass
mit T
existential behandeln möchten , können Sie MyClass<?>
wie im secretMessage()
obigen Beispiel deklarieren .
Existenzielle Typen werden manchmal verwendet, um die Implementierungsdetails von etwas zu verbergen, wie an anderer Stelle erläutert. Eine Java-Version davon könnte folgendermaßen aussehen:
public class ToDraw<T> {
T obj;
Function<Pair<T,Graphics>, Void> draw;
ToDraw(T obj, Function<Pair<T,Graphics>, Void>
static void draw(ToDraw<?> d, Graphics g) { d.draw.apply(new Pair(d.obj, g)); }
}
// Now you can put these in a list and draw them like so:
List<ToDraw<?>> drawList = ... ;
for(td in drawList) ToDraw.draw(td);
Es ist etwas schwierig, dies richtig zu erfassen, da ich vorgebe, in einer funktionalen Programmiersprache zu sein, die Java nicht ist. Der Punkt hier ist jedoch, dass Sie eine Art von Status sowie eine Liste von Funktionen erfassen, die in diesem Status ausgeführt werden, und den tatsächlichen Typ des Status-Teils nicht kennen, die Funktionen jedoch, da sie bereits mit diesem Typ abgeglichen wurden .
In Java sind nun alle nicht endgültigen nicht-primitiven Typen teilweise existenziell. Dies mag seltsam klingen, aber weil eine Variable, die als deklariert wurde, Object
möglicherweise eine Unterklasse von sein könnteObject
deklariert wurde, stattdessen nicht den spezifischen Typ deklarieren kann, sondern nur "diesen Typ oder eine Unterklasse". Objekte werden also als Statusbit plus eine Liste von Funktionen dargestellt, die mit diesem Status arbeiten. Welche Funktion aufgerufen werden soll, wird zur Laufzeit durch Nachschlagen bestimmt. Dies ähnelt stark der Verwendung von existentiellen Typen, bei denen Sie einen existenziellen Zustandsteil und eine Funktion haben, die mit diesem Zustand arbeitet.
In statisch typisierten Programmiersprachen ohne Subtypisierung und Casts ermöglichen existenzielle Typen die Verwaltung von Listen unterschiedlich typisierter Objekte. Eine Liste von T<Int>
darf keine enthalten T<Long>
. Eine Liste von T<?>
kann jedoch eine beliebige Variation von enthalten T
, sodass viele verschiedene Datentypen in die Liste aufgenommen und bei Bedarf alle in ein int konvertiert werden können (oder alle in der Datenstruktur bereitgestellten Vorgänge ausgeführt werden können).
Man kann so ziemlich immer einen Datensatz mit einem existenziellen Typ in einen Datensatz konvertieren, ohne Verschlüsse zu verwenden. Ein Abschluss wird auch existenziell typisiert, indem die freien Variablen, über die er geschlossen wird, vor dem Aufrufer verborgen sind. Mit einer Sprache, die Verschlüsse, aber keine existenziellen Typen unterstützt, können Sie daher Verschlüsse erstellen, die denselben verborgenen Zustand aufweisen, den Sie in den existenziellen Teil eines Objekts eingefügt hätten.