Voorbeeldzelfstudie van Java-generics – Generieke methode, klasse, interface

Java Generics is een van de belangrijkste functies die zijn geïntroduceerd in Java 5. Als je hebt gewerkt met Java-collecties en versie 5 of hoger hebt, ben ik er zeker van dat je het hebt gebruikt. Generics in Java met collectieklassen is heel eenvoudig, maar het biedt veel meer functies dan alleen het maken van het type collectie. We zullen proberen de kenmerken van generics in dit artikel te leren. Het begrijpen van generics kan soms verwarrend worden als we met jargonwoorden gaan, dus ik zal proberen het eenvoudig en gemakkelijk te begrijpen te houden.

We zullen de volgende onderwerpen van generics in Java bekijken.

  1. Generics in Java

  2. Java Generic Class

  3. Java Generic Interface

  4. Java Generic Type

  5. Java Generic Method

  6. Java Generics Bounded Type Parameters

  7. Java Generics en Overerving

  8. Java Generieke Klassen en Subtyping

  9. Java Generics Wildcards

  10. Java Generics Upper Bounded Wildcard

  11. Java Generics Unbounded Wildcard

  12. Java Generics Lower bounded Wildcard

  13. Subtyping met behulp van Generics Wildcard

  14. Java Generics Type Verwijdering

  15. Veelgestelde Vragen over Generics

1. Generics in Java

Generics werden toegevoegd in Java 5 om compile-time type checking te bieden en het risico op ClassCastException te verminderen dat vaak voorkwam bij het werken met verzamelingsklassen. Het hele verzamelingsframework werd herschreven om generics te gebruiken voor typeveiligheid. Laten we eens kijken hoe generics ons helpen om verzamelingsklassen veilig te gebruiken.

List list = new ArrayList();
list.add("abc");
list.add(new Integer(5)); //OK

for(Object obj : list){
	//type-casting leidt tot ClassCastException tijdens runtime
    String str=(String) obj; 
}

Bovenstaande code compileert prima maar werpt een ClassCastException op tijdens runtime omdat we proberen Object in de lijst naar String te casten terwijl een van de elementen van het type Integer is. Na Java 5 gebruiken we verzamelingsklassen als volgt.

List list1 = new ArrayList(); // java 7 ? List list1 = new ArrayList<>(); 
list1.add("abc");
//list1.add(new Integer(5)); //compilerfout

for(String str : list1){
     //geen typecasting nodig, voorkomt ClassCastException
}

Let op dat bij het maken van de lijst is gespecificeerd dat het type elementen in de lijst String zal zijn. Dus als we proberen een ander type object aan de lijst toe te voegen, zal het programma een compileerfout veroorzaken. Merk ook op dat in de for-lus we geen typecasting van het element in de lijst nodig hebben, waardoor de ClassCastException tijdens runtime wordt verwijderd.

2. Java Generic Class

We kunnen onze eigen klassen definiëren met generieke types. Een generiek type is een klasse of interface die geparametriseerd is over types. We gebruiken haakjes (<>) om het typeparameter op te geven. Om het voordeel te begrijpen, laten we zeggen dat we een eenvoudige klasse hebben als:

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
	}
}

Merk op dat bij het gebruik van deze klasse, we type-casting moeten gebruiken en dit kan een ClassCastException veroorzaken tijdens runtime. Nu zullen we een Java generieke klasse gebruiken om dezelfde klasse opnieuw te schrijven zoals hieronder weergegeven.

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
	}
}

Merk op het gebruik van de GenericsType-klasse in de hoofdmethode. We hoeven geen type-casting te doen en we kunnen ClassCastException tijdens runtime vermijden. Als we het type niet opgeven op het moment van creatie, zal de compiler een waarschuwing produceren dat “GenericsType een raw type is. Verwijzingen naar generiek type GenericsType<T> moeten worden geparametriseerd”. Wanneer we het type niet opgeven, wordt het type Object en daarom staan zowel String- als Integer-objecten toe. Maar we moeten altijd proberen dit te vermijden omdat we type-casting moeten gebruiken tijdens het werken met een raw type dat runtime-fouten kan veroorzaken.

Tip: We kunnen de @SuppressWarnings("rawtypes") annotatie gebruiken om de compilerwaarschuwing te onderdrukken, bekijk Java-annotaties tutorial.

Merk ook op dat het Java-autoboxing ondersteunt.

3. Java Generieke Interface

Comparable-interface is een geweldig voorbeeld van Generics in interfaces en het is geschreven als:

package java.lang;
import java.util.*;

public interface Comparable<T> {
    public int compareTo(T o);
}

Op dezelfde manier kunnen we generieke interfaces maken in Java. We kunnen ook meerdere typeparameters hebben, zoals in het Map-interface. Opnieuw kunnen we ook een geparametriseerde waarde voor een geparametriseerd type verstrekken, bijvoorbeeld new HashMap<String, List<String>>(); is geldig.

4. Java Generiek Type

De naamgevingsconventie voor Java Generieke Typen helpt ons de code gemakkelijk te begrijpen en het hebben van een naamgevingsconventie is een van de beste praktijken van de programmeertaal Java. Dus generics komen ook met hun eigen naamgevingsconventies. Gewoonlijk zijn de namen van typeparameters enkele hoofdletters om ze gemakkelijk te onderscheiden van Java-variabelen. De meest gebruikte namen voor typeparameters zijn:

  • 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 Generieke Methode

Soms willen we niet dat de hele klasse wordt geparameteriseerd, in dat geval kunnen we een generieke Java-methode maken. Aangezien de constructor een speciaal soort methode is, kunnen we ook generieke typen gebruiken in constructeurs. Hier is een klasse die een voorbeeld toont van een generieke Java-methode.

package com.journaldev.generics;

public class GenericsMethods {

	// Java Generieke 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);
		// bovenstaande verklaring kan eenvoudig worden geschreven als
		isEqual = GenericsMethods.isEqual(g1, g2);
		// Deze functie, bekend als type-inferentie, stelt u in staat om een generieke methode aan te roepen als een gewone methode, zonder een type tussen haakjes te specificeren.
		// De compiler zal het vereiste type afleiden
	}
}

Let op de isEqual methodehandtekening die de syntaxis toont om generieke typen in methoden te gebruiken. Merk ook op hoe we deze methoden gebruiken in ons Java-programma. We kunnen het type specificeren bij het oproepen van deze methoden of we kunnen ze aanroepen als een normale methode. De Java-compiler is slim genoeg om het type van de variabele te bepalen die moet worden gebruikt, deze functionaliteit wordt type inferentie genoemd.

6. Begrensde Typeparameters in Java Generics

Stel dat we het type objecten willen beperken dat kan worden gebruikt in het geparametriseerde type, bijvoorbeeld in een methode die twee objecten vergelijkt en we willen ervoor zorgen dat de geaccepteerde objecten Comparable zijn. Om een begrensd typeparameter te verklaren, vermeldt u de naam van de typeparameter, gevolgd door het sleutelwoord extends, gevolgd door de bovengrens, vergelijkbaar met de onderstaande methode.

public static <T extends Comparable<T>> int compare(T t1, T t2){
		return t1.compareTo(t2);
	}

De oproep van deze methoden is vergelijkbaar met onbegrensde methoden, behalve dat als we proberen een klasse te gebruiken die niet Comparable is, het een compileerfout zal veroorzaken. Begrensde typeparameters kunnen worden gebruikt bij methoden evenals klassen en interfaces. Java Generics ondersteunt ook meerdere grenzen, d.w.z. <T extends A & B & C>. In dit geval kan A een interface of klasse zijn. Als A een klasse is, moeten B en C een interface zijn. We kunnen niet meer dan één klasse hebben in meerdere grenzen.

7. Java Generics en Erfenis

We weten dat Java overerving ons toestaat om een variabele A toe te wijzen aan een andere variabele B als A een subklasse is van B. Dus we zouden kunnen denken dat elk generiek type van A kan worden toegewezen aan het generieke type van B, maar dat is niet het geval. Laten we dit zien aan de hand van een eenvoudig programma.

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; // compilatiefout omdat MyClass geen MyClass
		obj = myClass1; // MyClass parent is Object
	}
	
	public static class MyClass{}

}

We mogen geen variabele van MyClass toewijzen aan een variabele van MyClassomdat ze niet gerelateerd zijn, in feite is de ouder van MyClass Object.

8. Java Generieke Klassen en Subtyping

We kunnen een generieke klasse of interface subtypen door deze uit te breiden of te implementeren. De relatie tussen de typeparameters van de ene klasse of interface en de typeparameters van een andere worden bepaald door de extends en implements-clausules. Bijvoorbeeld, ArrayList implementeert List die Collection uitbreidt, dus ArrayList is een subtype van List en List is een subtype van Collection. De subtyping-relatie wordt behouden zolang we het typeargument niet wijzigen, hieronder wordt een voorbeeld getoond van meerdere typeparameters.

interface MyList<E,T> extends List<E>{
}

De subtypen van List kunnen MyList, MyList enzovoort zijn.

9. Java Generieke Wildcards

Vraagteken (?) is het jokerteken in generieken en vertegenwoordigt een onbekend type. Het jokerteken kan worden gebruikt als het type van een parameter, veld of lokale variabele en soms als retourtype. We kunnen jokertekens niet gebruiken bij het aanroepen van een generieke methode of bij het instantiëren van een generieke klasse. In de volgende secties zullen we meer leren over bovengrens jokertekens, ondergrens jokertekens en jokerteken vastlegging.

9.1) Java Generieken Bovengrens Jokerteken

Bovengrens jokertekens worden gebruikt om de beperking op het type variabele in een methode te versoepelen. Stel dat we een methode willen schrijven die de som van getallen in de lijst retourneert, dus onze implementatie zal er als volgt uitzien.

public static double sum(List<Number> list){
		double sum = 0;
		for(Number n : list){
			sum += n.doubleValue();
		}
		return sum;
	}

Nu is het probleem met bovenstaande implementatie dat het niet zal werken met een lijst van Integers of Doubles omdat we weten dat List en List niet gerelateerd zijn, dit is wanneer een bovengrens jokerteken nuttig is. We gebruiken generieke jokertekens met het extends trefwoord en de bovengrens klasse of interface die ons in staat zal stellen een argument van bovengrens of de subklassen types door te geven. De bovenstaande implementatie kan worden gewijzigd zoals het onderstaande programma.

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;
	}
}

Het is vergelijkbaar met het schrijven van onze code in termen van interface, in de bovenstaande methode kunnen we alle methoden van de bovengrens klasse Nummer gebruiken. Let op dat bij een bovengrenslijst het niet is toegestaan om een object aan de lijst toe te voegen, behalve null. Als we proberen een element aan de lijst toe te voegen binnen de sum-methode, zal het programma niet compileren.

9.2) Java Generics Onbegrensde Wildcard

Soms hebben we een situatie waarin we willen dat onze generieke methode werkt met alle typen, in dat geval kan een onbegrensde wildcard worden gebruikt. Het is hetzelfde als het gebruik van <? extends Object>.

public static void printData(List<?> list){
		for(Object obj : list){
			System.out.print(obj + "::");
		}
	}

We kunnen List<String> of List<Integer> of elk ander type Objectlijstargument aan de printData-methode geven. Net als bij een bovengrenslijst is het niet toegestaan om iets aan de lijst toe te voegen.

9.3) Java Generics Ondergrens Wildcard

Stel dat we integers aan een lijst van integers willen toevoegen in een methode, we kunnen het argumenttype als List houden maar het zal gebonden zijn aan Integers terwijl List en Listook integers kunnen bevatten, dus we kunnen een wildcard met ondergrens gebruiken om dit te bereiken. We gebruiken generieke wildcard (?) met het sleutelwoord super en een klasse met een ondergrens om dit te bereiken. We kunnen een ondergrens of een willekeurige supertype van de ondergrens doorgeven als argument, in dit geval staat de Java-compiler toe dat ondergrenstypen aan de lijst worden toegevoegd.

public static void addIntegers(List<? super Integer> list){
		list.add(new Integer(50));
	}

10. Subtypen met Generieke Wildcard

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // OK. List<? extends Integer> is a subtype of List<? extends Number>

11. Typeuitwissing van Java Generics

Generics in Java zijn toegevoegd om typecontrole tijdens compilatie mogelijk te maken en hebben geen nut tijdens runtime, dus de Java-compiler gebruikt de typeuitwissing-functie om alle generieke typecontrolecode in de bytecode te verwijderen en type-casting in te voegen indien nodig. Typeuitwissing zorgt ervoor dat er geen nieuwe klassen worden gemaakt voor geparametriseerde typen; bijgevolg brengen generieken geen overhead tijdens runtime met zich mee. Als voorbeeld, als we een generieke klasse hebben zoals hieronder;

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; }
}

Vervang de Java-compiler de begrensde typeparameter T door de eerste gebonden interface, Comparable, zoals in onderstaande 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. Veelgestelde vragen over Generics

12.1) Waarom gebruiken we Generics in Java?

Generics bieden sterke typecontrole tijdens compilatie en verminderen het risico op ClassCastException en expliciete omzetting van objecten.

12.2) Wat is T in Generics?

We gebruiken om een generieke klasse, interface en methode te maken. De T wordt vervangen door het daadwerkelijke type wanneer we het gebruiken.

12.3) Hoe werken Generics in Java?

Generieke code zorgt voor typeveiligheid. De compiler gebruikt type-uitwissing om alle typeparameters bij compilatie te verwijderen om de overbelasting bij runtime te verminderen.

13. Generics in Java – Verdere Lectuur

Dat is alles voor generics in Java, Java generics is een zeer uitgebreid onderwerp en vereist veel tijd om het effectief te begrijpen en te gebruiken. Dit bericht is een poging om basisdetails van generics te verstrekken en hoe we het kunnen gebruiken om ons programma uit te breiden met typeveiligheid.

Source:
https://www.digitalocean.com/community/tutorials/java-generics-example-method-class-interface