Java-Generika-Beispiel-Tutorial – Generische Methode, Klasse, Schnittstelle

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. Generika in Java

  2. Java generische Klasse

  3. Java generische Schnittstelle

  4. Java generischer Typ

  5. Java generische Methode

  6. Java Generika Begrenzte Typparameter

  7. Java Generics und Vererbung

  8. Java generische Klassen und Subtypen

  9. Java Generics Wildcards

  10. Java Generics obere Begrenzung Wildcard

  11. Java Generics unbeschränkte Wildcard

  12. Java Generics untere Begrenzung Wildcard

  13. Subtypisierung mit Generics Wildcard

  14. Java Generics Typlöschung

  15. Generics FAQs

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 myClass2 = new MyClass();
		//myClass2=myClass1; // Kompilierungsfehler, da MyClass kein MyClass
		obj = myClass1; // MyClass parent is Object
	}
	
	public static class 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 beibehalten, aber er wird an Ganzzahlen gebunden sein, während List und Listauch Ganzzahlen enthalten können. Daher können wir ein Wildcard mit dem Schlüsselwort super und einer unteren Grenze verwenden, um dies zu erreichen. Wir verwenden Generics-Wildcard (?) mit dem super-Schlüsselwort und der unteren Grenzenklasse, um dies zu erreichen. Wir können die untere Grenze oder jeden Supertyp der unteren Grenze als Argument übergeben. In diesem Fall erlaubt der Java-Compiler das Hinzufügen von Objekttypen der unteren Grenze zur Liste.

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

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