CodeGym /Java Blog /Random-IT /Generici in Java
John Squirrels
Livello 41
San Francisco

Generici in Java

Pubblicato nel gruppo Random-IT
CIAO! Parleremo di Java Generics. Devo dire che imparerai molto! Non solo questa lezione, ma anche le prossime lezioni saranno dedicate ai generici. Quindi, se sei interessato ai generici, oggi sei un giorno fortunato: imparerai molto sulle caratteristiche dei generici. E se no, rassegnati e rilassati! :) Questo è un argomento molto importante e devi conoscerlo. Cominciamo con il semplice: il "cosa" e il "perché".

Cosa sono i generici Java?

I generici sono tipi che hanno un parametro. Quando si crea un tipo generico, si specifica non solo un tipo, ma anche il tipo di dati con cui funzionerà. Immagino che l'esempio più ovvio ti sia già venuto in mente: ArrayList! Ecco come di solito ne creiamo uno in un programma:

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<String> myList1 = new ArrayList<>();
       myList1.add("Test String 1");
       myList1.add("Test String 2");
   }
}
Come puoi immaginare, una caratteristica di questo elenco è che non possiamo inserirci tutto: funziona esclusivamente con oggetti String . Ora facciamo una piccola digressione nella storia di Java e proviamo a rispondere alla domanda "perché?" Per fare ciò, scriveremo la nostra versione semplificata della classe ArrayList. Il nostro elenco sa solo come aggiungere dati e recuperare dati da un array interno:

public class MyListClass {

   private Object[] data;
   private int count;

   public MyListClass() {
       this.data = new Object[10];
       this.count = 0;
   }

   public void add(Object o) {
       this.data[count] = o;
       count++;
   }

   public Object[] getData() {
       return data;
   }
}
Supponiamo di volere che la nostra lista memorizzi solo Integer s. Non stiamo usando un tipo generico. Non vogliamo includere un controllo esplicito "instanceof Integer " nel metodo add() . Se lo facessimo, la nostra intera classe sarebbe adatta solo per Integer e dovremmo scrivere una classe simile per ogni altro tipo di dati al mondo! Faremo affidamento sui nostri programmatori e lasceremo semplicemente un commento nel codice per assicurarci che non aggiungano nulla che non vogliamo:

// Use this class ONLY with the Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
Uno dei programmatori ha perso questo commento e ha inavvertitamente inserito diverse stringhe in un elenco di numeri e quindi ha calcolato la loro somma:

public class Main {

   public static void main(String[] args) {

       MyListClass list = new MyListClass();
       list.add(100);
       list.add(200);
       list.add("Lolkek");
       list.add("Shalala");

       Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
       System.out.println(sum1);

       Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
       System.out.println(sum2);
   }
}
Uscita console:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
      at Main.main (Main.java:14)
Qual è la parte peggiore di questa situazione? Non certo la disattenzione del programmatore. La parte peggiore è che il codice errato è finito in un posto importante nel nostro programma ed è stato compilato con successo. Ora incontreremo il bug non durante la scrittura del codice, ma solo durante il test (e questo è lo scenario migliore!). La correzione dei bug nelle fasi successive dello sviluppo costa molto di più, sia in termini di denaro che di tempo. È proprio qui che i generici ci avvantaggiano: una classe generica consente allo sfortunato programmatore di rilevare subito l'errore. Il programma semplicemente non viene compilato!

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Integer> myList1 = new ArrayList<>();
      
       myList1.add(100);
       myList1.add(100);
       myList1.add ("Lolkek"); // Error!
       myList1.add("Shalala"); // Error!
   }
}
Il programmatore si rende immediatamente conto del suo errore e migliora immediatamente. A proposito, non abbiamo dovuto creare la nostra classe List per vedere questo tipo di errore. Basta rimuovere le parentesi angolari e digitare ( <Integer> ) da un normale ArrayList!

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add(100);
      list.add(200);
      list.add("Lolkek");
      list.add("Shalala");

       System.out.println((Integer) list.get(0) + (Integer) list.get(1));
       System.out.println((Integer) list.get(2) + (Integer) list.get(3));
   }
}
Uscita console:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
     at Main.main(Main.java:16)
In altre parole, anche utilizzando i meccanismi "nativi" di Java, possiamo commettere questo tipo di errore e creare una raccolta non sicura. Tuttavia, se incolliamo questo codice in un IDE, riceviamo un avviso: "Unchecked call to add(E) as a member of raw type of java.util.List" Ci viene detto che qualcosa potrebbe andare storto durante l'aggiunta di un elemento a una raccolta priva di un tipo generico. Ma cosa significa la frase "tipo grezzo"? Un tipo non elaborato è una classe generica il cui tipo è stato rimosso. In altre parole, List myList1 è un tipo non elaborato . L'opposto di un tipo non elaborato è un tipo generico , una classe generica con un'indicazione dei tipi parametrizzati . Ad esempio, List<String> myList1. Potresti chiedere perché la lingua consente l'uso di tipi grezzi ? Il motivo è semplice. I creatori di Java hanno lasciato il supporto per i tipi grezzi nel linguaggio per evitare di creare problemi di compatibilità. Quando è stato rilasciato Java 5.0 (i generici sono apparsi per la prima volta in questa versione), molto codice era già stato scritto utilizzando tipi non elaborati . Di conseguenza, questo meccanismo è ancora supportato oggi. Abbiamo più volte menzionato il classico libro di Joshua Bloch "Effective Java" nelle lezioni. Come uno dei creatori del linguaggio, nel suo libro non ha saltato tipi grezzi e tipi generici .Cosa sono i generici in Java?  - 2Il capitolo 23 del libro ha un titolo molto eloquente: "Non usare tipi grezzi nel nuovo codice" Questo è ciò che devi ricordare. Quando si utilizzano classi generiche, non trasformare mai un tipo generico in un tipo non elaborato .

Metodi generici

Java consente di parametrizzare singoli metodi creando i cosiddetti metodi generici. In che modo questi metodi sono utili? Soprattutto, sono utili in quanto consentono di lavorare con diversi tipi di parametri del metodo. Se la stessa logica può essere tranquillamente applicata a tipi diversi, un metodo generico può essere un'ottima soluzione. Considera questo un esempio molto semplice: supponiamo di avere una lista chiamata myList1 . Vogliamo rimuovere tutti i valori dall'elenco e riempire tutti gli spazi vuoti con nuovi valori. Ecco come appare la nostra classe con un metodo generico:

public class TestClass {

   public static <T> void fill(List<T> list, T val) {
       for (int i = 0; i < list.size(); i++)
           list.set(i, val);
   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       strings.add("Old String 1");
       strings.add("Old String 2");
       strings.add("Old String 3");

       fill(strings, "New String");

       System.out.println(strings);

       List<Integer> numbers = new ArrayList<>();
       numbers.add(1);
       numbers.add(2);
       numbers.add(3);

       fill(numbers, 888);
       System.out.println(numbers);
   }
}
Fai attenzione alla sintassi. Sembra un po' insolito:

public static <T> void fill(List<T> list, T val)
Scriviamo <T> prima del tipo restituito. Questo indica che abbiamo a che fare con un metodo generico. In questo caso, il metodo accetta 2 parametri come input: un elenco di oggetti T e un altro oggetto T separato. Usando <T>, parametrizziamo i tipi di parametro del metodo: non possiamo passare un elenco di stringhe e un numero intero. Un elenco di stringhe e una stringa, un elenco di numeri interi e un numero intero, un elenco dei nostri oggetti Cat e un altro oggetto Cat : ecco cosa dobbiamo fare. Il metodo main() illustra come il metodo fill() può essere facilmente utilizzato per lavorare con diversi tipi di dati. Per prima cosa, usiamo il metodo con un elenco di stringhe e una stringa come input, quindi con un elenco di numeri interi e un numero intero. Uscita console:

[New String, New String, New String] [888, 888, 888]
Immagina se non avessimo metodi generici e avessimo bisogno della logica del metodo fill() per 30 classi diverse. Dovremmo scrivere lo stesso metodo 30 volte per diversi tipi di dati! Ma grazie a metodi generici, possiamo riutilizzare il nostro codice! :)

Classi generiche

Non sei limitato alle classi generiche fornite nelle librerie Java standard: puoi crearne di tue! Ecco un semplice esempio:

public class Box<T> {

   private T t;

   public void set(T t) {
       this.t = t;
   }

   public T get() {
       return t;
   }

   public static void main(String[] args) {

       Box<String> stringBox = new Box<>();

       stringBox.set("Old String");
       System.out.println(stringBox.get());
       stringBox.set("New String");

       System.out.println(stringBox.get());
      
       stringBox.set(12345); // Compilation error!
   }
}
La nostra classe Box<T> è una classe generica. Una volta assegnato un tipo di dati ( <T> ) durante la creazione, non siamo più in grado di inserire oggetti di altri tipi in esso. Questo può essere visto nell'esempio. Durante la creazione del nostro oggetto, abbiamo indicato che avrebbe funzionato con le stringhe:

Box<String> stringBox = new Box<>();
E nell'ultima riga di codice, quando proviamo a inserire il numero 12345 all'interno della casella, otteniamo un errore di compilazione! È così facile! Abbiamo creato la nostra classe generica! :) Con questo, la lezione di oggi giunge al termine. Ma non stiamo dicendo addio ai generici! Nelle prossime lezioni parleremo di funzionalità più avanzate, quindi non te ne andare! ) Per rafforzare ciò che hai imparato, ti suggeriamo di guardare una lezione video dal nostro corso Java
Il miglior successo nei tuoi studi! :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION