Was ist der Unterschied zwischen 'E', 'T' und '?' für Java-Generika?


261

Ich stoße auf Java-Code wie folgt:

public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}

Was ist der Unterschied zwischen allen drei oben genannten und wie nennen sie diese Art von Klassen- oder Schnittstellendeklarationen in Java?


1
Ich bezweifle, dass es einen Unterschied gibt. Ich denke, es ist nur ein Name für diesen Typparameter. Und ist der letzte überhaupt gültig?
CodesInChaos

Antworten:


231

Nun, es gibt keinen Unterschied zwischen den ersten beiden - sie verwenden nur unterschiedliche Namen für den Typparameter ( Eoder T).

Die dritte ist keine gültige Deklaration - ?wird als Platzhalter verwendet, der bei der Bereitstellung eines Typarguments verwendet wird , z. B. List<?> foo = ...bedeutet, dass fooauf eine Liste eines Typs verwiesen wird, aber wir wissen nicht, was.

All dies sind Generika , was ein ziemlich großes Thema ist. Möglicherweise möchten Sie dies anhand der folgenden Ressourcen erfahren, obwohl natürlich weitere verfügbar sind:


1
Es sieht so aus, als ob der Link zum PDF defekt ist. Ich habe hier eine Kopie gefunden , kann aber nicht 100% sicher sein, da ich nicht weiß, wie das Original aussah.
John

2
@ John: Ja, das ist der eine. Wird einen Link in bearbeiten, ob dieser oder ein Oracle ...
Jon Skeet

Gibt es etwas anderes als T, E und? in Generika verwendet? Wenn ja, was sind sie und was bedeuten sie?
sofs1

1
@ sofs1: Es gibt nichts Besonderes an Tund E- sie sind nur Bezeichner. Sie könnten KeyValuePair<K, V>zum Beispiel schreiben . ?hat jedoch eine besondere Bedeutung.
Jon Skeet

215

Es ist mehr Konvention als alles andere.

  • T soll ein Typ sein
  • Esoll ein Element sein ( List<E>: eine Liste von Elementen)
  • Kist der Schlüssel (in a Map<K,V>)
  • V ist Wert (als Rückgabewert oder abgebildeter Wert)

Sie sind vollständig austauschbar (trotz Konflikten in derselben Erklärung).


20
Der Buchstabe zwischen dem <> ist nur ein Name. Was Sie in Ihrer Antwort beschreiben, sind nur Konventionen. Es muss nicht einmal ein einzelner Großbuchstabe sein. Sie können einen beliebigen Namen verwenden, genauso wie Sie Klassen, Variablen usw. einen beliebigen Namen geben können.
Jesper

Eine detailliertere und klarere
fgul

6
Sie haben das Fragezeichen nicht erklärt. Abgestimmt.
Shinzou

129

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 ( extendsoder 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 Integerals 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);
}

numberSuperkann eine Liste der Zahlen oder ein beliebiger Supertyp der Zahl sein (z. B. List<Object>) und elemmuss Zahl oder ein beliebiger Untertyp sein. Bei allen .add()Einschränkungen kann der Compiler sicher sein, dass dies typsicher ist.


"public void foo (Liste <? erweitert Nummer> Nummern) {...}" sollte "erweitert" "super" sein?
1a1a11a

1
Nein. In diesem Beispiel soll eine Signatur angezeigt werden, die eine Liste der Nummern und die Untertypen der Nummern polymorph unterstützt. Hierfür verwenden Sie "Erweitert". Dh "gib mir eine Liste mit Zahlen oder irgendetwas, das die Zahl erweitert" (Liste <Integer>, Liste <Float>, was auch immer). Eine solche Methode kann dann die Liste durchlaufen und für jedes Element "e" ausführen, z. B. e.floatValue (). Es spielt keine Rolle, welchen Untertyp (Erweiterung) von Number Sie übergeben - Sie können immer ".floatValue ()" verwenden, da .floatValue () eine Methode von Number ist.
Hawkeye Parker

In Ihrem letzten Beispiel könnte "List <? Super Number>" einfach "List <Number>" sein, da die Methode nichts allgemeineres zulässt.
Jessarah

@ Jessarah nein. Vielleicht ist mein Beispiel unklar, aber ich erwähne im Beispiel, dass adder () eine Liste <Objekt> annehmen könnte (Objekt ist eine Superklasse von Zahlen). Wenn Sie dies möchten, muss es die Signatur "List <? Super Number>" haben. Das ist genau der Punkt von "super" hier.
Hawkeye Parker

2
Diese Antwort ist sehr, sehr gut, um die Unterschiede zwischen Platzhaltern und Typparametern zu erklären. Diese Antwort sollte eine spezielle Frage enthalten. Ich beschäftige mich in letzter Zeit eingehender mit Generika und diese Antwort hat mir sehr geholfen, Dinge zusammenzustellen, viele präzise Infos, kurz gesagt, danke!
Testo Testini

27

Eine Typvariable <T> kann ein beliebiger nicht primitiver Typ sein, den Sie angeben: ein beliebiger Klassentyp, ein beliebiger Schnittstellentyp, ein beliebiger Array-Typ oder sogar eine andere Typvariable.

Die am häufigsten verwendeten Typparameternamen sind:

  • E-Element (wird häufig vom Java Collections Framework verwendet)
  • K - Schlüssel
  • N - Nummer
  • T - Typ
  • V - Wert

In Java 7 ist es erlaubt, wie folgt zu instanziieren:

Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6

3

Die am häufigsten verwendeten Typparameternamen sind:

E - Element (used extensively by the Java Collections Framework)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types

Diese Namen werden in der gesamten Java SE-API verwendet


2

Der Compiler erstellt für jeden Platzhalter (z. B. Fragezeichen in der Liste) eine Erfassung , wenn er eine Funktion wie die folgende bildet:

foo(List<?> list) {
    list.put(list.get()) // ERROR: capture and Object are not identical type.
}

Ein generischer Typ wie V wäre jedoch in Ordnung und würde ihn zu einer generischen Methode machen :

<V>void foo(List<V> list) {
    list.put(list.get())
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.