Metodi Generici in Java

Concetti Chiave
  • I metodi generici possono utilizzare parametri di tipo propri, diversi da quelli della classe a cui appartengono.
  • È possibile dichiarare metodi generici all'interno di classi non generiche.
  • I metodi generici possono essere statici e non statici.
  • I metodi generici possono essere utilizzati con argomenti di tipo specifici o con inferenza di tipo automatica.
  • I costruttori possono essere generici anche se la classe non lo è.
  • I costruttori generici possono accettare parametri di tipo generico, consentendo la creazione di oggetti con tipi numerici diversi.
  • I metodi generici possono essere utilizzati per scrivere codice flessibile e riusabile.

Metodi Generici

Nelle lezioni precedenti sui Generics in Java, abbiamo visto che i metodi all'interno di una classe generica possono utilizzare il parametro di tipo della classe e sono, quindi, automaticamente generici relativamente al parametro di tipo.

Ad esempio, consideriamo la seguente classe generica:

class Gen<T> {
    T ob; // dichiarazione di un oggetto di tipo T

    Gen(T o) {
        ob = o;
    }

    T getOb() {
        return ob;
    }

    void mostraTipo() {
        System.out.println("Il tipo di ob è " + ob.getClass().getName());
    }

}

In questo esempio, mostraTipo è un metodo generico perché utilizza il parametro di tipo T della classe a cui appartiene, Gen. Quindi, il metodo è automaticamente generico.

Tuttavia, è possibile dichiarare un metodo generico che utilizza uno o più parametri di tipo propri, differenti da quelli della classe a cui appartiene.

Inoltre, è possibile creare un metodo generico che è racchiuso all'interno di una classe non generica.

Iniziamo con un esempio. Il seguente programma dichiara una classe non generica chiamata DemoMetodoGen e un metodo generico statico all'interno di quella classe chiamato siTrovaIn(). Il metodo siTrovaIn() determina se un oggetto è un membro di un array. Può essere utilizzato con qualsiasi tipo di oggetto e array finché l'array contiene oggetti che sono compatibili con il tipo dell'oggetto che viene cercato.

// Dimostrazione di un metodo generico.

class DemoMetodoGen {

    // Determina se un oggetto è in un array.
    static
    <T extends Comparable<T>, V extends T>
    boolean siTrovaIn(T x, V[] y) {
        for(int i=0; i < y.length; i++)
            if(x.equals(y[i])) return true;
        return false;
    }

    // Metodo main per testare siTrovaIn().
    // Usa siTrovaIn() su Integer e String.
    public static void main(String[] args) {

        // Usa siTrovaIn() su Integer.
        Integer[] numeri = { 1, 2, 3, 4, 5 };

        if(siTrovaIn(2, numeri))
            System.out.println("2 è in numeri");

        if(!siTrovaIn(7, numeri))
            System.out.println("7 non è in numeri");

        System.out.println();

        // Usa siTrovaIn() su String.
        String[] stringhe = {
            "uno",
            "due",
            "tre",
            "quattro",
            "cinque"
        };

        if(siTrovaIn("due", stringhe))
            System.out.println("due è in stringhe");

        if(!siTrovaIn("sette", stringhe))
            System.out.println("sette non è in stringhe");

        // Questo codice Non compilerà!
        // I tipi devono essere compatibili.
        // if(siTrovaIn("due", numeri))
        //     System.out.println("due è in stringhe");
    }

}

L'output del programma è mostrato qui:

2 è in numeri
7 non è in numeri

due è in stringhe
sette non è in stringhe

Esaminiamo siTrovaIn() attentamente.

Prima, notiamo come è dichiarato da questa riga:

static
<T extends Comparable<T>, V extends T>
boolean siTrovaIn(T x, V[] y) {
    /* ... */
}

I parametri di tipo sono dichiarati prima del tipo di ritorno del metodo. Notiamo anche che T estende Comparable<T>. Comparable è un'interfaccia dichiarata in java.lang.

Una classe che implementa Comparable definisce oggetti che possono essere ordinati, ossia che possono essere confrontati tra loro. Quindi, richiedere un limite superiore di Comparable assicura che siTrovaIn() possa essere utilizzato solo con oggetti che sono capaci di essere confrontati.

Comparable è generico, e il suo parametro di tipo specifica il tipo di oggetti che confronta. Nella prossima lezione vedremo come implementare interfacce generiche.

Successivamente, notiamo che il tipo V è limitato superiormente da T. Quindi, V deve essere o lo stesso tipo di T, o una sottoclasse di T. Questa relazione impone che siTrovaIn() possa essere chiamato solo con argomenti che sono compatibili tra loro. Notiamo anche che siTrovaIn() è static, permettendogli di essere chiamato indipendentemente da qualsiasi oggetto. Si noti, tuttavia, che i metodi generici possono essere sia static che non static. Non c'è alcuna restrizione in questo senso.

Ora, notiamo come siTrovaIn() è chiamato all'interno di main() utilizzando la normale sintassi di chiamata, senza la necessità di specificare argomenti di tipo. Questo perché i tipi degli argomenti sono automaticamente inferiti, ossia il compilatore comprende in automatico chi sia il tipo di T e V in base agli argomenti passati.

Per esempio, nella prima chiamata:

if(siTrovaIn(2, numeri))

il tipo del primo argomento è Integer (a causa dell'autoboxing), che causa la sostituzione di Integer per T. Il tipo base del secondo argomento è anche Integer, che rende Integer un sostituto per V, anch'esso. Nella seconda chiamata, vengono utilizzati tipi String, e i tipi di T e V sono sostituiti da String.

Anche se l'inferenza di tipo sarà sufficiente per la maggior parte delle chiamate di metodi generici, possiamo specificare esplicitamente l'argomento di tipo se necessario.

Per esempio, ecco come appare la prima chiamata a siTrovaIn() quando gli argomenti di tipo sono specificati:

DemoMetodoGen.<Integer, Integer>siTrovaIn(2, numeri)

Naturalmente, in questo caso, non si ottiene nulla specificando gli argomenti di tipo. Inoltre, JDK 8 ha migliorato l'inferenza di tipo per quanto riguarda i metodi. Di conseguenza, oggi ci sono meno casi in cui gli argomenti di tipo espliciti sono necessari.

Ora, notiamo il codice commentato, mostrato qui:

// if(siTrovaIn("due", numeri))
// System.out.println("due è in stringhe");

Se rimuoviamo i commenti e poi proviamo a compilare il programma, riceveremo un errore. Il motivo è che il parametro di tipo V è limitato da T nella clausola extends nella dichiarazione di V. Questo significa che V deve essere o il tipo T, o una sottoclasse di T.

In questo caso, il primo argomento è di tipo String, rendendo T in String, ma il secondo argomento è di tipo Integer, che non è una sottoclasse di String. Questo causa un errore di mancata corrispondenza di tipo a tempo di compilazione. Questa capacità di imporre la sicurezza dei tipi è uno dei vantaggi più importanti dei metodi generici.

La sintassi utilizzata per creare siTrovaIn() può essere generalizzata. Ecco la sintassi per un metodo generico:

<lista_parametri_tipo> tipo_ritorno nome_metodo (lista_param) {
    // corpo del metodo
}

In tutti i casi, lista_parametri_tipo è una lista separata da virgole di parametri di tipo. Notiamo che per un metodo generico, la lista dei parametri di tipo precede il tipo di ritorno.

Costruttori Generici

È possibile realizzare anche costruttori generici, anche se la loro classe non lo è.

Ad esempio, consideriamo il seguente breve programma:

// Usa un costruttore generico.
class ConsGen {

    private double val;

    <T extends Number> ConsGen(T arg) {
        val = arg.doubleValue();
    }

    void mostraVal() {
        System.out.println("val: " + val);
    }

}

class DemoConsGen {

    public static void main(String[] args) {
        ConsGen test = new ConsGen(100);
        ConsGen test2 = new ConsGen(123.5F);
        test.mostraVal();
        test2.mostraVal();
    }

}

L'output è mostrato qui:

val: 100.0
val: 123.5

Poiché ConsGen() specifica un parametro di un tipo generico, che deve essere una sottoclasse di Number, ConsGen() può essere chiamato con qualsiasi tipo numerico, inclusi Integer, Float, o Double. Pertanto, anche se ConsGen non è una classe generica, il suo costruttore è generico.