Generics e Argomenti Wildcard in Java

Concetti Chiave
  • Gli argomenti wildcard in Java permettono di specificare un tipo sconosciuto in un contesto generico.
  • Si utilizzano per creare metodi generici che possono operare su diversi tipi di classi senza specificare un tipo concreto.
  • Gli argomenti wildcard sono definiti con il simbolo ?.
  • Gli argomenti wildcard sono utili per scrivere codice generico che può lavorare con diversi tipi di classi, migliorando la flessibilità e la riusabilità del codice.

Uso degli Argomenti Wildcard per i Generics

Per quanto utile sia la sicurezza dei tipi, a volte può ostacolare costrutti perfettamente accettabili.

Ad esempio, riprendiamo la classe Statistiche mostrata nella lezione precedente:

class Statistiche<T extends Number> {
    // numeri è un array di tipo T
    // ma T è limitato a Number o sottoclasse.
    T[] numeri;

    // Passa al costruttore un riferimento a
    // un array di tipo Number o sottoclasse.
    Statistiche(T[] o) {
        numeri = o;
    }

    // Restituisce tipo double in tutti i casi.
    double media() {
        double somma = 0.0;
        for(int i=0; i < numeri.length; i++)
            somma += numeri[i].doubleValue();
        return somma / numeri.length;
    }

}

Supponiamo di voler aggiungere un metodo chiamato stessaMedia() che determina se due oggetti Statistiche contengono array che producono la stessa media, indipendentemente dal tipo di dati numerici che ogni oggetto contiene.

Per esempio, se un oggetto contiene i valori double 1.0, 2.0 e 3.0, e l'altro oggetto contiene i valori interi 2, 1 e 3, allora le medie saranno le stesse.

Un modo per implementare stessaMedia() è passargli un argomento Statistiche, e poi confrontare la media di quell'argomento con l'oggetto invocante, restituendo true solo se le medie sono le stesse. Per esempio, vogliamo essere in grado di chiamare stessaMedia(), come mostrato qui:

Integer[] numeri_interi = { 1, 2, 3, 4, 5 };
Double[] numeri_reali = { 1.1, 2.2, 3.3, 4.4, 5.5 };

// Crea due oggetti Statistiche.
Statistiche<Integer> iOgg =
    new Statistiche<Integer>(numeri_interi);
Statistiche<Double> dOgg =
    new Statistiche<Double>(numeri_reali);

// Confronta le medie.
if(iOgg.stessaMedia(dOgg))
    System.out.println("Le medie sono le stesse.");
else
    System.out.println("Le medie differiscono.");

Inizialmente, creare stessaMedia() sembra un problema facile. Poiché Statistiche è generico e il suo metodo media() può funzionare su qualsiasi tipo di oggetto Statistiche, sembra che creare stessaMedia() dovrebbe essere semplice.

Sfortunatamente, i problemi iniziano non appena si prova a dichiarare un parametro di tipo Statistiche. Poiché Statistiche è un tipo parametrizzato, cosa si specifica per il parametro di tipo di Statistiche quando si dichiara un parametro di quel tipo?

Inizialmente, si potrebbe pensare a una soluzione come questa, in cui T è usato come parametro di tipo:

// ERRORE: Questo codice non funziona!
// Determina se due medie sono le stesse.
boolean stessaMedia(Statistiche<T> oggetto) {
    if(media() == oggetto.media())
        return true;
    return false;
}

Il problema con questo tentativo è che funzionerà solo con altri oggetti Statistiche il cui tipo è lo stesso dell'oggetto invocante. Per esempio, se l'oggetto invocante è di tipo Statistiche<Integer>, allora il parametro oggetto deve anche essere di tipo Statistiche<Integer>. Non può essere usato per confrontare la media di un oggetto di tipo Statistiche<Double> con la media di un oggetto di tipo Statistiche<Short>, per esempio. Pertanto, questo approccio non funzionerà tranne che in un contesto molto ristretto e non produce una soluzione generale (cioè, generica).

Per creare un metodo stessaMedia() generico, dobbiamo usare un'altra caratteristica dei generici Java: gli argomenti wildcard.

Un argomento wildcard è specificato dal simbolo ?, e rappresenta un tipo sconosciuto.

Usando un wildcard, ecco un modo per scrivere il metodo stessaMedia():

// Determina se due medie sono le stesse.
// Si noti l'uso dell'argomento wildcard.
boolean stessaMedia(Statistiche<?> oggetto) {
    if(media() == oggetto.media())
        return true;
    return false;
}

Qui, Statistiche<?> corrisponde a qualsiasi oggetto Statistiche, permettendo a qualsiasi due oggetti Statistiche di avere le loro medie confrontate. Il seguente programma dimostra questo:

// Usando un argomento wildcard per Statistiche.

class Statistiche<T extends Number> {
    // numeri è un array di tipo T
    // ma T è limitato a Number o sottoclasse.
    T[] numeri;

    // Passa al costruttore un riferimento a
    // un array di tipo Number o sottoclasse.
    Statistiche(T[] o) {
        numeri = o;
    }

    // Restituisce tipo double in tutti i casi.
    double media() {
        double somma = 0.0;
        for(int i=0; i < numeri.length; i++)
            somma += numeri[i].doubleValue();
        return somma / numeri.length;
    }

    // Determina se due medie sono le stesse.
    // Si noti l'uso del wildcard.
    boolean stessaMedia(Statistiche<?> ob) {
        if(media() == ob.media())
            return true;
        return false;
    }

}

// Dimostra l'uso di un argomento wildcard.
class WildcardDemo {

    public static void main(String[] args) {

        // Crea un array di Integer e calcola la media.
        Integer[] numeri_interi = { 1, 2, 3, 4, 5 };
        Statistiche<Integer> iOgg =
            new Statistiche<Integer>(numeri_interi);
        double v = iOgg.media();
        System.out.println("la media di iOgg è " + v);

        Double[] numeri_double = { 1.1, 2.2, 3.3, 4.4, 5.5 };
        Statistiche<Double> dOgg = new Statistiche<Double>(numeri_double);
        double w = dOgg.media();
        System.out.println("la media di dOgg è " + w);

        Float[] numeri_float = { 1.0F, 2.0F, 3.0F, 4.0F, 5.0F };
        Statistiche<Float> fOgg = new Statistiche<Float>(numeri_float);
        double x = fOgg.media();
        System.out.println("la media di fOgg è " + x);

        // Vedi quali array hanno la stessa media.
        System.out.print("Le medie di iOgg e dOgg ");
        if(iOgg.stessaMedia(dOgg))
            System.out.println("sono le stesse.");
        else
            System.out.println("differiscono.");

        System.out.print("Le medie di iOgg e fOgg ");
        if(iOgg.stessaMedia(fOgg))
            System.out.println("sono le stesse.");
        else
            System.out.println("differiscono.");
    }

}

L'output è mostrato qui:

la media di iOgg è 3.0
la media di dOgg è 3.3
la media di fOgg è 3.0
Le medie di iOgg e dOgg differiscono.
Le medie di iOgg e fOgg sono le stesse.

Un ultimo punto da ricordare: è importante capire che l'argomento wildcard non influisce su quali tipi di oggetti Statistiche possono essere creati.

Questo è governato dalla clausola extends nella dichiarazione di Statistiche. Il wildcard semplicemente corrisponde a qualsiasi oggetto Statistiche valido.