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
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
We mogen geen variabele van MyClass
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
interface MyList<E,T> extends List<E>{
}
De subtypen van List
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
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
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
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
- Generics ondersteunt geen subtyping, dus
List<Number> numbers = new ArrayList<Integer>();
zal niet compileren, leer waarom generics geen subtyping ondersteunt. - We kunnen geen generiek array maken, dus
List<Integer>[] array = new ArrayList<Integer>[10]
zal niet compileren, lees waarom we geen generiek array kunnen maken?.
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