polimorfismo

variazioni di comportamento al variare dei tipi

La parola polimorfismo deriva dal greco e significa “molte forme”. In Java, questo concetto si manifesta con due possibilità:
1 - avere più metodi con lo stesso nome ma parametri diversi nella stessa classe (overloading)
2 - trattare oggetti di sottoclassi come se fossero della superclasse (overriding).

Metodi (overloading)

Questo tipo di polimorfismo è noto anche come function overloading.

All'interno di una classe possiamo avere più metodi con lo stesso nome: l'importante è che il numero degli argomenti o il loro tipo non coincidano. Detto in altri termini due metodi sono diversi se hanno un numero di argomenti diversi o se hanno lo stesso numero di argomenti ma di tipi diversi.

Nel caso di java i metodi non possono però essere differenziati in base al tipo ritornato.

Prendiamo per esempio una classe che consente di impostare il lato di una figura che dovrebbe essere un numero in virgola mobile ma che permettiamo di impostare come testo per comodità dell'utilizzatore.


class Quadrato {
    private double lato;

    public void setLato(double d){
        lato = d;
    }

    public void setLato(String l){
        lato = Double.parseDouble(l);
    }
}

Spesso è utile ad esempio avere diversi costruttori per lo stesso oggetto: in questo caso l'oggetto Persona può essere costruito sia fornendo nome e altezza che il solo nome:


class Persona {
    private double altezza;
    private String nome;

    public Persona(String n, double a){
        nome = n;
        altezza = a;
    }

    public Persona(String n){
        nome = n;
    }
}

Classi (overriding)

Una classe figlia eredita tutti i metodi della classe padre, ma può decidere di ridefinirne alcuni. Quando un metodo viene ridefinito (overridden), nella classe figlia prende il posto di quello originale, e ogni volta che quel metodo viene chiamato su un’istanza della classe figlia verrà eseguita la nuova versione invece di quella ereditata.

In java per esplicitare che un metodo sovrascrive quello della classe padre si mette l'annotazione @Override prima della definizione del metodo, questo non è obbligatorio ma fa si che il compilatore controlli che in effetti si stia facendo overriding.

Vediamo un esempio di overriding definendo la classe Animale con un solo metodo.


class Animale {
  public String verso() {
    return "Verso generico";
  }
}

Ora estendiamo la classe e facciamo overriding del metodo verso()


class Cane extends Animale {
  @Override
  public String verso() {
    return "Bau!";
  }
}

Scriviamo una classe di prova per vedere cosa viene stampato quando si chiama il metodo verso() su diversi oggetti:


class Prova {
  public static void main(String[] args) {
    Animale animale = new Animale();
    Cane cane1 = new Cane();
    Animale cane2 = new Cane();

    System.out.println(animale.verso());  // Output: Verso generico
    System.out.println(cane1.verso());    // Output: Bau!
    System.out.println(cane2.verso());    // Output: Bau!
  }
}

Vediamo in ordine il perché dei diversi output:

Classi (generics)

Oltre che nei metodi si possono definire anche degli oggetti (classi) parametrici: proviamo a pensare ad un oggetto che rappresenta un insieme, sarebbe comodo poter fare insiemi di numeri, di nomi, o di colori senza dover creare tre classi diverse. Questo si fa utilizzando dei tipi (classi nel nostro caso) parametrici. Nelle interfacce grafiche posso dire di avere una ListView di String oppure di Integer o altro, in molti altri contesti torna utile un meccanismo simile e nella libreria di java ci sono tantissime classi parametriche.

Qui non impareremo a definire oggetti fatti in questo modo ma ci limitiamo soltanto a vedere come si usano.

Per utilizzare i tipi parametrici si inserisce < dopo il nome della classe, il tipo parametrico e poi un >, potrebbe sembrare complicato ma con un esempio si capisce subito:

Nel mio programma che usa JavaFX voglio creare una lista che visualizzi dei testi: uso ListView<String> e più precisamente per creare l'oggetto
ListView<String> l = new ListView<String>()
queste cose si fanno così tanto comunemente che nel costruttore (quello dopo il new!) per comodità non è necessario inserire di nuovo il tipo dell'oggetto contenuto, quindi in genere avremo:
ListView<String> l = new ListView<>()