Java-Generika ist eine der wichtigsten Funktionen, die in Java 5 eingeführt wurden. Wenn Sie an Java-Sammlungen arbeiten und Version 5 oder höher verwenden, bin ich sicher, dass Sie es verwendet haben. Generika in Java mit Sammlungsklassen ist sehr einfach, aber es bietet viel mehr Funktionen als nur das Erstellen des Typs der Sammlung. Wir werden versuchen, die Funktionen von Generika in diesem Artikel zu erlernen. Das Verständnis von Generika kann manchmal verwirrend sein, wenn wir mit Fachbegriffen arbeiten. Daher werde ich versuchen, es einfach und leicht verständlich zu halten.
Wir werden uns die folgenden Themen der Generika in Java ansehen.
1. Generics in Java
Generics wurde in Java 5 hinzugefügt, um eine Überprüfung der Typen zur Kompilierzeit bereitzustellen und das Risiko einer ClassCastException zu entfernen, das beim Arbeiten mit Sammlungsklassen üblich war. Das gesamte Sammlungsframework wurde neu geschrieben, um Generics für die Typsicherheit zu verwenden. Lassen Sie uns sehen, wie Generics uns helfen, Sammlungsklassen sicher zu verwenden.
List list = new ArrayList();
list.add("abc");
list.add(new Integer(5)); //OK
for(Object obj : list){
// Typumwandlung führt zur ClassCastException zur Laufzeit
String str=(String) obj;
}
Der obige Code kompiliert gut, wirft aber zur Laufzeit eine ClassCastException, weil wir versuchen, ein Objekt in der Liste in einen String umzuwandeln, während eines der Elemente vom Typ Integer ist. Nach Java 5 verwenden wir Sammlungsklassen wie unten gezeigt.
List list1 = new ArrayList(); // java 7 ? List list1 = new ArrayList<>();
list1.add("abc");
//list1.add(new Integer(5)); // Compilerfehler
for(String str : list1){
// Keine Typumwandlung erforderlich, vermeidet ClassCastException
}
Beachten Sie, dass wir bei der Erstellung der Liste angegeben haben, dass der Typ der Elemente in der Liste String sein wird. Wenn wir also versuchen, einen anderen Typ von Objekt in die Liste einzufügen, wird das Programm einen Kompilierfehler auslösen. Beachten Sie auch, dass wir in der for-Schleife keine Typumwandlung des Elements in der Liste benötigen, was zur Entfernung der ClassCastException zur Laufzeit führt.
2. Java Generische Klasse
Wir können unsere eigenen Klassen mit generischen Typen definieren. Ein generischer Typ ist eine Klasse oder ein Interface, das über Typen parametrisiert ist. Wir verwenden spitze Klammern (<>), um den Typparameter anzugeben. Um den Nutzen zu verstehen, nehmen wir an, wir haben eine einfache Klasse wie folgt:
package com.journaldev.generics;
public class GenericsTypeOld {
private Object t;
public Object get() {
return t;
}
public void set(Object t) {
this.t = t;
}
public static void main(String args[]){
GenericsTypeOld type = new GenericsTypeOld();
type.set("Pankaj");
String str = (String) type.get(); //type casting, error prone and can cause ClassCastException
}
}
Beachten Sie, dass bei der Verwendung dieser Klasse Typumwandlungen durchgeführt werden müssen und dies zu einer ClassCastException zur Laufzeit führen kann. Jetzt werden wir eine generische Java-Klasse verwenden, um dieselbe Klasse wie unten gezeigt neu zu schreiben.
package com.journaldev.generics;
public class GenericsType<T> {
private T t;
public T get(){
return this.t;
}
public void set(T t1){
this.t=t1;
}
public static void main(String args[]){
GenericsType<String> type = new GenericsType<>();
type.set("Pankaj"); //valid
GenericsType type1 = new GenericsType(); //raw type
type1.set("Pankaj"); //valid
type1.set(10); //valid and autoboxing support
}
}
Beachten Sie die Verwendung der GenericsType-Klasse in der main-Methode. Wir müssen keine Typumwandlungen durchführen und können ClassCastException zur Laufzeit vermeiden. Wenn wir den Typ beim Erstellen nicht angeben, gibt der Compiler eine Warnung aus, dass „GenericsType ein Raw-Typ ist. Verweise auf den generischen Typ GenericsType<T> sollten parametrisiert sein“. Wenn wir den Typ nicht angeben, wird der Typ zu Object
und erlaubt daher sowohl String- als auch Integer-Objekte. Aber wir sollten das immer vermeiden, weil wir bei der Arbeit mit dem Raw-Typ Typumwandlungen verwenden müssen, die Laufzeitfehler verursachen können.
Tipp: Wir können die Annotation @SuppressWarnings("rawtypes")
verwenden, um die Compilerwarnung zu unterdrücken, siehe Java-Annotationen-Tutorial.
Beachten Sie auch, dass es Java-Autoboxing unterstützt.
3. Java Generisches Interface
Das Comparable-Interface ist ein großartiges Beispiel für Generics in Interfaces und wird wie folgt geschrieben:
package java.lang;
import java.util.*;
public interface Comparable<T> {
public int compareTo(T o);
}
Auf ähnliche Weise können wir generische Interfaces in Java erstellen. Wir können auch mehrere Typparameter haben, wie im Map-Interface. Auch einem parametrisierten Typ können wir einen parametrisierten Wert zuweisen, zum Beispiel new HashMap<String, List<String>>();
ist gültig.
4. Java Generischer Typ
Die Namenskonvention für generische Typen in Java hilft uns, den Code leichter zu verstehen, und das Festlegen einer Namenskonvention ist eine der besten Praktiken der Java-Programmiersprache. Daher gibt es auch für Generics eigene Namenskonventionen. Üblicherweise sind die Namen der Typparameter einzelne Großbuchstaben, um sie leicht von Java-Variablen unterscheidbar zu machen. Die am häufigsten verwendeten Namen für Typparameter sind:
- E – Element (used extensively by the Java Collections Framework, for example ArrayList, Set etc.)
- K – Key (Used in Map)
- N – Number
- T – Type
- V – Value (Used in Map)
- S,U,V etc. – 2nd, 3rd, 4th types
5. Java Generische Methode
Manchmal möchten wir nicht, dass die gesamte Klasse parametrisiert wird. In diesem Fall können wir eine Java-Generics-Methode erstellen. Da der Konstruktor eine besondere Art von Methode ist, können wir auch Generics-Typen in Konstruktoren verwenden. Hier ist eine Klasse, die ein Beispiel für eine Java-Generics-Methode zeigt.
package com.journaldev.generics;
public class GenericsMethods {
//Java-Generic-Methode
public static boolean isEqual(GenericsType g1, GenericsType g2){
return g1.get().equals(g2.get());
}
public static void main(String args[]){
GenericsType g1 = new GenericsType<>();
g1.set("Pankaj");
GenericsType g2 = new GenericsType<>();
g2.set("Pankaj");
boolean isEqual = GenericsMethods.isEqual(g1, g2);
//obige Anweisung kann einfach geschrieben werden als
isEqual = GenericsMethods.isEqual(g1, g2);
//Diese Funktion, bekannt als Typinferenz, ermöglicht es Ihnen, eine generische Methode wie eine gewöhnliche Methode aufzurufen, ohne einen Typ zwischen spitzen Klammern anzugeben.
//Der Compiler wird den benötigten Typ ableiten
}
}
Beachten Sie die Signatur der isEqual-Methode, die die Syntax zur Verwendung von generischen Typen in Methoden zeigt. Beachten Sie auch, wie man diese Methoden in unserem Java-Programm verwendet. Wir können den Typ beim Aufruf dieser Methoden angeben oder wir können sie wie eine normale Methode aufrufen. Der Java-Compiler ist klug genug, um den Typ der zu verwendenden Variablen zu bestimmen. Diese Funktion wird als Typinferenz bezeichnet.
6. Begrenzte Typparameter in Java-Generics
Wir möchten den Typ der Objekte einschränken, die im parameterisierten Typ verwendet werden können, zum Beispiel in einer Methode, die zwei Objekte vergleicht, und sicherstellen möchten, dass die akzeptierten Objekte Comparable sind. Um einen begrenzten Typparameter zu deklarieren, geben Sie den Namen des Typparameters an, gefolgt vom extends-Schlüsselwort, gefolgt von seiner oberen Grenze, ähnlich wie bei der folgenden Methode.
public static <T extends Comparable<T>> int compare(T t1, T t2){
return t1.compareTo(t2);
}
Die Aufrufung dieser Methoden ist ähnlich wie bei einer unbeschränkten Methode, mit der Ausnahme, dass bei dem Versuch, eine Klasse zu verwenden, die nicht Comparable ist, ein Kompilierungsfehler auftritt. Begrenzte Typparameter können sowohl bei Methoden als auch bei Klassen und Schnittstellen verwendet werden. Java Generics unterstützt auch mehrere Grenzen, d. h. <T extends A & B & C>. In diesem Fall kann A eine Schnittstelle oder Klasse sein. Wenn A eine Klasse ist, sollten B und C Schnittstellen sein. Wir können nicht mehr als eine Klasse in mehreren Grenzen haben.
7. Java Generics und Vererbung
Wir wissen, dass Java-Vererbung es uns ermöglicht, einer Variablen A eine andere Variable B zuzuweisen, wenn A eine Unterklasse von B ist. Wir könnten also denken, dass jeder generische Typ von A einem generischen Typ von B zugewiesen werden kann, aber das ist nicht der Fall. Sehen wir uns dies mit einem einfachen Programm an.
package com.journaldev.generics;
public class GenericsInheritance {
public static void main(String[] args) {
String str = "abc";
Object obj = new Object();
obj=str; // works because String is-a Object, inheritance in java
MyClass myClass1 = new MyClass();
MyClass
Wir dürfen MyClass<String> Variable nicht MyClass<Object> Variable zuweisen, weil sie nicht verwandt sind, tatsächlich ist das Elternteil von MyClass<T> Object.
8. Java Generische Klassen und Subtypisierung
Wir können eine generische Klasse oder ein Interface untertypisieren, indem wir sie erweitern oder implementieren. Die Beziehung zwischen den Typparametern einer Klasse oder eines Interface und den Typparametern eines anderen wird durch die extends- und implements-Klauseln bestimmt. Zum Beispiel implementiert ArrayList<E> List<E>, die Collection<E> erweitert, daher ist ArrayList<String> ein Subtyp von List<String> und List<String> ist ein Subtyp von Collection<String>. Die Beziehung der Subtypisierung bleibt erhalten, solange wir das Typargument nicht ändern, unten wird ein Beispiel für mehrere Typparameter gezeigt.
interface MyList<E,T> extends List<E>{
}
Die Subtypen von List<String> können MyList<String,Object>, MyList<String,Integer> und so weiter sein.
9. Java Generics Wildcards
Fragezeichen (?) ist das Platzhalterzeichen in Generika und repräsentiert einen unbekannten Typ. Der Platzhalter kann als Typ eines Parameters, Feldes oder lokalen Variablen verwendet werden und manchmal auch als Rückgabetyp. Wir können Platzhalter nicht verwenden, während wir eine generische Methode aufrufen oder eine generische Klasse instanziieren. In den folgenden Abschnitten werden wir etwas über obere Grenzen von Platzhaltern, untere Grenzen von Platzhaltern und die Erfassung von Platzhaltern lernen.
9.1) Java Generics Obere Grenze der Platzhalter
Obere Grenzen von Platzhaltern werden verwendet, um die Einschränkung des Typs einer Variablen in einer Methode zu lockern. Angenommen, wir möchten eine Methode schreiben, die die Summe der Zahlen in der Liste zurückgibt, also wird unsere Implementierung ungefähr so aussehen.
public static double sum(List<Number> list){
double sum = 0;
for(Number n : list){
sum += n.doubleValue();
}
return sum;
}
Das Problem mit der obigen Implementierung ist, dass sie nicht mit Listen von Ganzzahlen oder Gleitkommazahlen funktioniert, weil wir wissen, dass List<Integer> und List<Double> nicht miteinander verwandt sind. Hierbei ist ein obere Grenze der Platzhalter hilfreich. Wir verwenden Generika-Platzhalter mit dem Schlüsselwort extends und der oberen Grenze der Klasse oder des Interfaces, was es uns ermöglicht, Argumente des oberen Grenzwerts oder seiner Unterklassentypen zu übergeben. Die obige Implementierung kann wie das folgende Programm geändert werden.
package com.journaldev.generics;
import java.util.ArrayList;
import java.util.List;
public class GenericsWildcards {
public static void main(String[] args) {
List<Integer> ints = new ArrayList<>();
ints.add(3); ints.add(5); ints.add(10);
double sum = sum(ints);
System.out.println("Sum of ints="+sum);
}
public static double sum(List<? extends Number> list){
double sum = 0;
for(Number n : list){
sum += n.doubleValue();
}
return sum;
}
}
Es ist ähnlich wie das Schreiben unseres Codes in Bezug auf die Schnittstelle, in der obigen Methode können wir alle Methoden der oberen Grenzklasse Number verwenden. Beachten Sie, dass bei einer oberen begrenzten Liste das Hinzufügen eines Objekts zur Liste außer null nicht erlaubt ist. Wenn wir versuchen, ein Element zur Liste innerhalb der Summenmethode hinzuzufügen, wird das Programm nicht kompilieren.
9.2) Java Generics Unbounded Wildcard
Manchmal haben wir eine Situation, in der wir möchten, dass unsere generische Methode mit allen Typen funktioniert, in diesem Fall kann ein ungebundener Platzhalter verwendet werden. Es ist dasselbe wie die Verwendung von <? extends Object>.
public static void printData(List<?> list){
for(Object obj : list){
System.out.print(obj + "::");
}
}
Wir können List<String> oder List<Integer> oder jeden anderen Typ von Objektlisteargument an die printData-Methode übergeben. Ähnlich wie bei einer oberen Begrenzung für Listen dürfen wir nichts zur Liste hinzufügen.
9.3) Java Generics Lower bounded Wildcard
Angenommen, wir möchten Ganzzahlen zu einer Liste von Ganzzahlen in einer Methode hinzufügen. Wir können den Argumenttyp als List
public static void addIntegers(List<? super Integer> list){
list.add(new Integer(50));
}
10. Subtyping mit Generics-Wildcard
List<? extends Integer> intList = new ArrayList<>();
List<? extends Number> numList = intList; // OK. List<? extends Integer> is a subtype of List<? extends Number>
11. Java Generics Typenlöschung
Generics in Java wurden hinzugefügt, um eine Typüberprüfung zur Compilezeit zu ermöglichen, und haben keine Verwendung zur Laufzeit. Daher verwendet der Java-Compiler die Funktion der Typenlöschung, um den gesamten Generics-Typüberprüfungscode im Bytecode zu entfernen und gegebenenfalls Typumwandlungen einzufügen. Die Typenlöschung stellt sicher, dass keine neuen Klassen für parametrisierte Typen erstellt werden. Folglich verursachen Generics keine Laufzeitüberkopf. Wenn wir beispielsweise eine generische Klasse wie unten haben;
public class Test<T extends Comparable<T>> {
private T data;
private Test<T> next;
public Test(T d, Test<T> n) {
this.data = d;
this.next = n;
}
public T getData() { return this.data; }
}
Der Java-Compiler ersetzt den gebundenen Typparameter T durch das erste gebundene Interface Comparable, wie im folgenden Code:
public class Test {
private Comparable data;
private Test next;
public Node(Comparable d, Test n) {
this.data = d;
this.next = n;
}
public Comparable getData() { return data; }
}
12. Generics FAQs
12.1) Warum verwenden wir Generics in Java?
Generics bieten eine starke Überprüfung der Typen zur Kompilierzeit und reduzieren das Risiko von ClassCastException und explizitem Casting von Objekten.
12.2) Was ist T in Generics?
Wir verwenden <T>, um eine generische Klasse, ein Interface und eine Methode zu erstellen. Das T wird durch den tatsächlichen Typ ersetzt, wenn wir es verwenden.
12.3) Wie funktionieren Generics in Java?
Generischer Code gewährleistet die Typsicherheit. Der Compiler verwendet Typlöschung, um alle Typparameter zur Kompilierzeit zu entfernen und die Überlastung zur Laufzeit zu reduzieren.
13. Generika in Java – Weiterführende Lektüre
- Generika unterstützen keine Untertypisierung, daher wird
List<Number> zahlen = new ArrayList<Integer>();
nicht kompilieren, erfahren Sie warum Generika keine Untertypisierung unterstützen. - Wir können kein generisches Array erstellen, daher wird
List<Integer>[] array = new ArrayList<Integer>[10]
nicht kompilieren, lesen Sie warum wir kein generisches Array erstellen können?.
Das ist alles für Generika in Java, Java Generika ist ein sehr umfangreiches Thema und erfordert viel Zeit, um es zu verstehen und effektiv zu nutzen. Dieser Beitrag hier ist ein Versuch, grundlegende Details zu Generika bereitzustellen und wie wir sie verwenden können, um unser Programm mit Typsicherheit zu erweitern.
Source:
https://www.digitalocean.com/community/tutorials/java-generics-example-method-class-interface