Esempio di tutorial sui generici Java – Metodo generico, Classe, Interfaccia

Le Generics Java è una delle caratteristiche più importanti introdotte in Java 5. Se hai lavorato con le Collezioni Java e con la versione 5 o successiva, sono sicuro che l’hai utilizzata. I Generics in Java con le classi di raccolta sono molto facili ma forniscono molte più funzionalità oltre alla creazione del tipo di raccolta. Cercheremo di apprendere le caratteristiche dei generics in questo articolo. Comprendere i generics può diventare confuso a volte se ci si imbatte in parole gergali, quindi cercherò di mantenerlo semplice e facile da capire.

Esamineremo di seguito gli argomenti dei generics in java.

  1. Generics in Java

  2. Classe Generica Java

  3. Interfaccia Generica Java

  4. Tipo Generico Java

  5. Metodo Generico Java

  6. Parametri di Tipo Limitato Generics Java

  7. Java Generics e Ereditarietà

  8. Classi Generiche Java e Sottotipaggio

  9. Java Generics Wildcards

  10. Wildcard con Limitazione Superiore in Java Generics

  11. Wildcard senza Limitazioni in Java Generics

  12. Wildcard con Limitazione Inferiore in Java Generics

  13. Sottotipaggio utilizzando Wildcard Generics

  14. Cancellazione del Tipo in Java Generics

  15. Domande frequenti sulle Generics

1. Generics in Java

I generics sono stati aggiunti in Java 5 per fornire un controllo dei tipi in fase di compilazione e rimuovere il rischio di ClassCastException che era comune durante il lavoro con le classi di raccolta. L’intero framework delle raccolte è stato riscritto per utilizzare i generics per la sicurezza dei tipi. Vediamo come i generics ci aiutano nell’uso sicuro delle classi di raccolta.

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

for(Object obj : list){
	//il cast dei tipi porta a ClassCastException durante l'esecuzione
    String str=(String) obj; 
}

Il codice sopra compila correttamente ma genera ClassCastException durante l’esecuzione perché stiamo cercando di fare il cast di un Oggetto nella lista a Stringa mentre uno degli elementi è di tipo Integer. Dopo Java 5, utilizziamo le classi di raccolta come mostrato di seguito.

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

for(String str : list1){
     //nessun cast di tipo necessario, evita ClassCastException
}

Nota che al momento della creazione della lista, abbiamo specificato che il tipo degli elementi nella lista sarà Stringa. Quindi se proviamo ad aggiungere qualsiasi altro tipo di oggetto alla lista, il programma genererà un errore in fase di compilazione. Nota anche che nel ciclo for, non abbiamo bisogno di fare il cast del tipo dell’elemento nella lista, evitando quindi il ClassCastException durante l’esecuzione.

2. Classe Generica Java

Possiamo definire le nostre classi con tipi generici. Un tipo generico è una classe o un’interfaccia parametrizzata sui tipi. Utilizziamo le parentesi angolari (<>) per specificare il parametro di tipo. Per comprendere il beneficio, diciamo che abbiamo una classe semplice come:

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

Osserva che utilizzando questa classe, dobbiamo eseguire il cast del tipo e ciò può causare ClassCastException durante l’esecuzione. Ora utilizzeremo una classe generica Java per riscrivere la stessa classe come mostrato di seguito:

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

Osserva l’uso della classe GenericsType nel metodo principale. Non è necessario eseguire il cast del tipo e possiamo evitare ClassCastException durante l’esecuzione. Se non forniamo il tipo al momento della creazione, il compilatore produrrà un avviso che dice “GenericsType è un tipo grezzo. I riferimenti al tipo generico GenericsType dovrebbero essere parametrizzati”. Quando non forniamo il tipo, il tipo diventa Object e quindi consente sia oggetti String che Integer. Tuttavia, dovremmo sempre cercare di evitarlo perché dovremmo utilizzare il cast del tipo durante il lavoro sul tipo grezzo che può produrre errori durante l’esecuzione.

Suggerimento: Possiamo utilizzare l’annotazione @SuppressWarnings("rawtypes") per sopprimere l’avviso del compilatore, consulta il tutorial sulle annotazioni Java.

Osserva anche che supporta l’autoboxing Java.

3. Interfaccia Generica Java

L’interfaccia Comparable è un ottimo esempio di Generics nelle interfacce ed è scritta come:

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

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

In modo simile, possiamo creare interfacce generiche in Java. Possiamo anche avere più parametri di tipo come nell’interfaccia Map. Di nuovo, possiamo fornire un valore parametrizzato a un tipo parametrizzato anche, ad esempio new HashMap<String, List<String>>(); è valido.

4. Tipo Generico Java

La convenzione di denominazione del tipo generico Java ci aiuta a comprendere facilmente il codice e avere una convenzione di denominazione è una delle migliori pratiche del linguaggio di programmazione Java. Quindi i generics hanno anche le proprie convenzioni di denominazione. Di solito, i nomi dei parametri di tipo sono lettere maiuscole singole per renderli facilmente distinguibili dalle variabili Java. I nomi dei parametri di tipo più comunemente usati sono:

  • 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. Metodo Generico Java

A volte non vogliamo che tutta la classe sia parametrizzata, in tal caso possiamo creare un metodo generico in Java. Poiché il costruttore è un tipo speciale di metodo, possiamo utilizzare anche tipi generici nei costruttori. Ecco una classe che mostra un esempio di un metodo generico in Java.

package com.journaldev.generics;

public class GenericsMethods {

	// Metodo Generico in Java
	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);
		// Lo statement sopra può essere scritto semplicemente come
		isEqual = GenericsMethods.isEqual(g1, g2);
		// Questa funzionalità, nota come inferenza di tipo, ti consente di invocare un metodo generico come un metodo ordinario, senza specificare un tipo tra parentesi angolari.
		// Il compilatore determinerà il tipo necessario
	}
}

Osserva la firma del metodo isEqual che mostra la sintassi per utilizzare tipi generici nei metodi. Nota anche come utilizzare questi metodi nel nostro programma Java. Possiamo specificare il tipo durante l’invocazione di questi metodi o possiamo invocarli come un normale metodo. Il compilatore Java è sufficientemente intelligente da determinare il tipo di variabile da utilizzare, questa facilità è chiamata inferenza di tipo.

6. Parametri di Tipo Limitati per i Generics in Java

Supponiamo che vogliamo limitare il tipo di oggetti che possono essere utilizzati nel tipo parametrizzato, ad esempio in un metodo che confronta due oggetti e vogliamo assicurarci che gli oggetti accettati siano Comparables. Per dichiarare un parametro di tipo vincolato, elenca il nome del parametro di tipo, seguito dalla parola chiave extends, seguita dal suo limite superiore, simile al metodo qui sotto.

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

l’invocazione di questi metodi è simile al metodo non vincolato tranne che se cercheremo di utilizzare una qualsiasi classe che non sia Comparable, verrà generato un errore di compilazione. I parametri di tipo vincolato possono essere utilizzati con metodi così come con classi e interfacce. Java Generics supporta anche più limiti, cioè <T extends A & B & C>. In questo caso, A può essere un’interfaccia o una classe. Se A è una classe, allora B e C dovrebbero essere interfacce. Non possiamo avere più di una classe in vincoli multipli.

7. Java Generics e l’Ereditarietà

Sappiamo che l’ereditarietà Java ci consente di assegnare una variabile A a un’altra variabile B se A è una sottoclasse di B. Potremmo pensare che qualsiasi tipo generico di A possa essere assegnato al tipo generico di B, ma non è il caso. Vediamo questo con un programma semplice.

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; // errore di compilazione poiché MyClass non è un MyClass
		obj = myClass1; // MyClass parent is Object
	}
	
	public static class MyClass{}

}

Non è consentito assegnare una variabile MyClass a una variabile MyClassperché non sono correlate, infatti il genitore di MyClass è Object.

8. Classi e sottotipi generici di Java

Possiamo creare un sottotipo di una classe o interfaccia generica estendendola o implementandola. La relazione tra i parametri di tipo di una classe o interfaccia e i parametri di tipo di un’altra è determinata dalle clausole extends e implements. Ad esempio, ArrayList implementa List che estende Collection, quindi ArrayList è un sottotipo di List e List è un sottotipo di Collection. La relazione di sottotipizzazione è preservata finché non cambiamo l’argomento di tipo, di seguito viene mostrato un esempio di parametri di tipo multipli.

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

I sottotipi di List possono essere MyList, MyList e così via.

9. Wildcard Generics di Java

Il punto interrogativo (?) è il jolly nei generics e rappresenta un tipo sconosciuto. Il jolly può essere utilizzato come tipo di parametro, campo o variabile locale e talvolta come tipo di ritorno. Non possiamo utilizzare jolly durante l’invocazione di un metodo generico o istanziare una classe generica. Nelle sezioni seguenti, apprenderemo i jolly superiori vincolati, i jolly inferiori vincolati e la cattura dei jolly.

9.1) Jolly Superiore Vincolato di Java Generics

I jolly superiori vincolati vengono utilizzati per rilassare la restrizione sul tipo di variabile in un metodo. Supponiamo di voler scrivere un metodo che restituirà la somma dei numeri nella lista, quindi la nostra implementazione sarà qualcosa del genere.

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

Il problema con l’implementazione sopra è che non funzionerà con List di Integer o Double perché sappiamo che List e List non sono correlate, qui è utile un jolly superiore vincolato. Utilizziamo il jolly generico con la parola chiave extends e la classe o interfaccia del limite superiore che ci consentirà di passare argomenti di tipo limite superiore o sottoclassi. L’implementazione sopra può essere modificata come nel programma seguente.

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

È simile a scrivere il nostro codice in termini di interfaccia, nel metodo sopra possiamo utilizzare tutti i metodi della classe Number del limite superiore. Nota che con una lista delimitata superiormente, non ci è consentito aggiungere alcun oggetto alla lista tranne null. Se proveremo ad aggiungere un elemento alla lista all’interno del metodo sum, il programma non compilerà.

9.2) Java Generics Wildcard non limitato

A volte ci troviamo in una situazione in cui vogliamo che il nostro metodo generico funzioni con tutti i tipi, in questo caso, può essere utilizzato un wildcard non limitato. È lo stesso che utilizzare <? extends Object>.

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

Possiamo fornire un argomento di tipo List<String> o List<Integer> o qualsiasi altro tipo di lista di oggetti al metodo printData. Similmente alla lista delimitata superiormente, non ci è consentito aggiungere nulla alla lista.

9.3) Java Generics Wildcard delimitato inferiormente

Supponiamo di voler aggiungere interi a una lista di interi in un metodo, possiamo mantenere il tipo di argomento come List ma sarà vincolato agli Integer mentre List e Listpossono anche contenere interi, quindi possiamo utilizzare un wildcard con vincolo inferiore per ottenere questo. Utilizziamo il wildcard dei generics (?) con la parola chiave super e la classe con limite inferiore per ottenere questo. Possiamo passare il limite inferiore o qualsiasi supertipo del limite inferiore come argomento, in questo caso, il compilatore Java consente di aggiungere tipi di oggetti con limite inferiore alla lista.

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

10. Sottotipizzazione utilizzando il wildcards dei Generics

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

11. Type Erasure dei Generics Java

I Generics in Java sono stati aggiunti per fornire un controllo dei tipi a tempo di compilazione e non hanno alcun utilizzo a tempo di esecuzione, quindi il compilatore Java utilizza la funzionalità di type erasure per rimuovere tutto il codice di controllo dei tipi dei generics nel bytecode e inserire il type-casting se necessario. Il type erasure garantisce che non vengano create nuove classi per i tipi parametrizzati; di conseguenza, i generics non comportano alcun overhead a tempo di esecuzione. Ad esempio, se abbiamo una classe generica come di seguito;

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

Il compilatore Java sostituisce il parametro di tipo limitato T con il primo tipo di interfaccia limitato, Comparable, come nel codice seguente:

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

Domande frequenti sulle Generics

12.1) Perché usiamo le Generics in Java?

Le Generics forniscono un forte controllo dei tipi in fase di compilazione e riducono il rischio di ClassCastException e di cast esplicito degli oggetti.

12.2) Cosa rappresenta T nelle Generics?

Usiamo <T> per creare una classe, un’interfaccia e un metodo generico. Il T viene sostituito con il tipo effettivo quando lo usiamo.

12.3) Come funzionano le Generics in Java?

Il codice generico garantisce la sicurezza dei tipi. Il compilatore utilizza l’annullamento dei tipi (type-erasure) per rimuovere tutti i parametri di tipo in fase di compilazione al fine di ridurre il sovraccarico a runtime.

13. Generici in Java – Ulteriori Letture

Questo è tutto per generici in java, i generici di Java sono un argomento davvero vasto e richiedono molto tempo per essere compresi e utilizzati efficacemente. Questo post è un tentativo di fornire dettagli di base sui generici e su come possiamo usarli per estendere il nostro programma con la sicurezza dei tipi.

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