1. Die Einführung des Konzepts der Generika (warum werden Generika benötigt)? (Empfohlen: Java-Video-Tutorial)
Schauen wir uns zunächst den folgenden Kurzcode an:
public class GenericTest { public static void main(String[] args) { List list = new ArrayList(); list.add("qqyumidi"); list.add("corn"); list.add(100); for (int i = 0; i < list.size(); i++) { String name = (String) list.get(i); // 1 System.out.println("name:" + name); } } }
definiert eine Sammlung vom Typ „Liste“ Zwei Werte vom Typ String werden hinzugefügt, gefolgt von einem Wert vom Typ Integer. Dies ist völlig zulässig, da der Standardtyp der Liste „Objekt“ ist.
In nachfolgenden Schleifen können leicht Fehler ähnlich wie //1 auftreten, weil vergessen wurde, Werte vom Typ Integer vorher zur Liste hinzuzufügen, oder aus anderen Codierungsgründen. Da die Kompilierungsphase normal ist, tritt zur Laufzeit jedoch die Ausnahme „java.lang.ClassCastException“ auf. Daher sind solche Fehler beim Codieren schwer zu erkennen.
Während des obigen Codierungsprozesses haben wir festgestellt, dass es zwei Hauptprobleme gibt:
1 Wenn wir ein Objekt in eine Sammlung einfügen, merkt sich die Sammlung nicht den Typ des Objekts Dieses Objekt wird wieder aus der Sammlung entfernt, der kompilierte Typ des Objekts ändert sich in den Objekttyp, aber sein Laufzeittyp ist immer noch ein eigener Typ.
2. Daher ist beim Herausnehmen der Sammlungselemente bei //1 eine künstliche erzwungene Typkonvertierung in einen bestimmten Zieltyp erforderlich, und es kann zu „java.lang.ClassCastException“-Ausnahmen kommen.
Gibt es also eine Möglichkeit, einer Sammlung zu ermöglichen, sich die Elementtypen in der Sammlung zu merken, sodass während der Laufzeit keine „java.lang.ClassCastException“-Ausnahmen auftreten, solange beim Kompilieren keine Probleme auftreten? ? Die Antwort ist die Verwendung von Generika.
2. Was sind Generika?
Generika, also „parametrisierte Typen“. Wenn es um Parameter geht, ist es am bekanntesten, dass beim Definieren einer Methode formale Parameter vorhanden sind und beim Aufruf dieser Methode die tatsächlichen Parameter übergeben werden.
Wie versteht man also parametrisierte Typen? Wie der Name schon sagt, wird der Typ anhand des ursprünglichen spezifischen Typs parametrisiert, ähnlich wie die variablen Parameter in der Methode. Zu diesem Zeitpunkt wird der Typ auch in Form von Parametern (die als Typparameter bezeichnet werden können) definiert Beim Verwenden/Aufrufen des Typs (Typargument) wird ein bestimmter Typ übergeben.
Es scheint etwas kompliziert zu sein. Schauen wir uns zunächst die allgemeine Schreibweise des obigen Beispiels an.
public class GenericTest { public static void main(String[] args) { /* List list = new ArrayList(); list.add("qqyumidi"); list.add("corn"); list.add(100); */ List<String> list = new ArrayList<String>(); list.add("qqyumidi"); list.add("corn"); //list.add(100); // 1 提示编译错误 for (int i = 0; i < list.size(); i++) { String name = list.get(i); // 2 System.out.println("name:" + name); } } }
Nach der Verwendung generischen Schreibens tritt ein Kompilierungsfehler auf, wenn versucht wird, ein Objekt vom Typ Integer unter //1 hinzuzufügen. Durch List
In Kombination mit der obigen generischen Definition wissen wir, dass String in List
public interface List<E> extends Collection<E> { int size(); boolean isEmpty(); boolean contains(Object o); Iterator<E> iterator(); Object[] toArray(); <T> T[] toArray(T[] a); boolean add(E e); boolean remove(Object o); boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean addAll(int index, Collection<? extends E> c); boolean removeAll(Collection<?> c); boolean retainAll(Collection<?> c); void clear(); boolean equals(Object o); int hashCode(); E get(int index); E set(int index, E element); void add(int index, E element); E remove(int index); int indexOf(Object o); int lastIndexOf(Object o); ListIterator<E> listIterator(); ListIterator<E> listIterator(int index); List<E> subList(int fromIndex, int toIndex); }
Wir können sehen, dass nach der Übernahme der generischen Definition in die List-Schnittstelle das E in
ArrayList ist natürlich die Implementierungsklasse der List-Schnittstelle und ihre Definitionsform lautet:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } public E get(int index) { rangeCheck(index); checkForComodification(); return ArrayList.this.elementData(offset + index); } //...省略掉其他具体的定义过程 }
Daraus verstehen wir aus der Quellcode-Perspektive, warum beim Hinzufügen von ein Kompilierungsfehler auftritt Objekt vom Typ Integer bei //1 und der von get() bei //2 erhaltene Typ ist direkt der Typ String.
3. Angepasste generische Schnittstellen, generische Klassen und generische Methoden
Aus dem obigen Inhalt hat jeder den spezifischen Betriebsprozess von Generika verstanden. Wir wissen auch, dass Schnittstellen, Klassen und Methoden auch mithilfe von Generics definiert und entsprechend verwendet werden können. Ja, bei spezifischer Verwendung kann es in generische Schnittstellen, generische Klassen und generische Methoden unterteilt werden.
Benutzerdefinierte generische Schnittstellen, generische Klassen und generische Methoden ähneln List und ArrayList im obigen Java-Quellcode. Im Folgenden betrachten wir die einfachste Definition generischer Klassen und Methoden:
public class GenericTest { public static void main(String[] args) { Box<String> name = new Box<String>("corn"); System.out.println("name:" + name.getData()); } } class Box<T> { private T data; public Box() { } public Box(T data) { this.data = data; } public T getData() { return data; } }
Beim Definieren generischer Schnittstellen, generischer Klassen und generischer Methoden sehen wir häufig T, E, K, V. Parameter derselben form werden häufig zur Darstellung generischer Parameter verwendet, da sie Typargumente empfangen, die von externen Verwendungen übergeben werden. Sind also für verschiedene übergebene Typargumente die Typen der entsprechenden generierten Objektinstanzen gleich?
public class GenericTest { public static void main(String[] args) { Box<String> name = new Box<String>("corn"); Box<Integer> age = new Box<Integer>(712); System.out.println("name class:" + name.getClass()); // com.qqyumidi.Box System.out.println("age class:" + age.getClass()); // com.qqyumidi.Box System.out.println(name.getClass() == age.getClass()); // true } }
Daraus haben wir herausgefunden, dass bei der Verwendung generischer Klassen zwar unterschiedliche generische Argumente übergeben werden, jedoch nicht tatsächlich unterschiedliche Typen generiert werden. Es gibt nur eine generische Klasse im Speicher, nämlich den ursprünglichsten Grundtyp (. Box in diesem Beispiel). Natürlich können wir es logischerweise als mehrere verschiedene generische Typen verstehen.
Der Grund dafür ist, dass der Zweck des Konzepts der Generika in Java darin besteht, dass es nur in der Code-Kompilierungsphase wirkt. Während des Kompilierungsprozesses werden die Generika relevante Informationen sein, nachdem die generischen Ergebnisse korrekt überprüft wurden gelöscht, das heißt, die erfolgreich kompilierte Klassendatei enthält keine generischen Informationen. Allgemeine Informationen gelangen nicht in die Laufzeitphase.
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
四.类型通配符
接着上面的结论,我们知道,Box
为了弄清这个问题,我们继续看下下面这个例子:
public class GenericTest { public static void main(String[] args) { Box<Number> name = new Box<Number>(99); Box<Integer> age = new Box<Integer>(712); getData(name); //The method getData(Box<Number>) in the type GenericTest is //not applicable for the arguments (Box<Integer>) getData(age); // 1 } public static void getData(Box<Number> data){ System.out.println("data :" + data.getData()); } }
我们发现,在代码//1处出现了错误提示信息:The method getData(Box
public class GenericTest { public static void main(String[] args) { Box<Integer> a = new Box<Integer>(712); Box<Number> b = a; // 1 Box<Float> f = new Box<Float>(3.14f); b.setData(f); // 2 } public static void getData(Box<Number> data) { System.out.println("data :" + data.getData()); } } class Box<T> { private T data; public Box() { } public Box(T data) { setData(data); } public T getData() { return data; } public void setData(T data) { this.data = data; } }
这个例子中,显然//1和//2处肯定会出现错误提示的。在此我们可以使用反证法来进行说明。
假设Box
好,那我们回过头来继续看“类型通配符”中的第一个例子,我们知道其具体的错误提示的深层次原因了。那么如何解决呢?总部能再定义一个新的函数吧。
这和Java中的多态理念显然是违背的,因此,我们需要一个在逻辑上可以用来表示同时是Box
类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box>在逻辑上是Box
public class GenericTest { public static void main(String[] args) { Box<String> name = new Box<String>("corn"); Box<Integer> age = new Box<Integer>(712); Box<Number> number = new Box<Number>(314); getData(name); getData(age); getData(number); } public static void getData(Box<?> data) { System.out.println("data :" + data.getData()); } }
有时候,我们还可能听到类型通配符上限和类型通配符下限。具体有是怎么样的呢?
在上面的例子中,如果需要定义一个功能类似于getData()的方法,但对类型实参又有进一步的限制:只能是Number类及其子类。此时,需要用到类型通配符上限。
public class GenericTest { public static void main(String[] args) { Box<String> name = new Box<String>("corn"); Box<Integer> age = new Box<Integer>(712); Box<Number> number = new Box<Number>(314); getData(name); getData(age); getData(number); //getUpperNumberData(name); // 1 getUpperNumberData(age); // 2 getUpperNumberData(number); // 3 } public static void getData(Box<?> data) { System.out.println("data :" + data.getData()); } public static void getUpperNumberData(Box<? extends Number> data){ System.out.println("data :" + data.getData()); } }
此时,显然,在代码//1处调用将出现错误提示,而//2 //3处调用正常。
类型通配符上限通过形如Box extends Number>形式定义,相对应的,类型通配符下限为Box super Number>形式,其含义与类型通配符上限正好相反,在此不作过多阐述了。
更多java知识请关注java基础教程栏目。
Das obige ist der detaillierte Inhalt vonDetaillierte Einführung in Java-Generika. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!