# Polimorfismo

Riprendendo l'esempio sul [Polimorfismo](/corsojava/introduzione-su-oggetti.md#polimorfismo) già visto nel capitolo precedente (esempio Instrument). Rivediamo il  metodo **`tune()`**:

```java
public static void tune(Instrument i) {
    // ...
    i.play(Note.MIDDLE_C);
}

public static void main(String[] args) {
    Wind flute = new Wind();
    Stringed violin = new Stringed();
    Brass frenchHorn = new Brass();
    tune(flute); // No upcasting
    tune(violin);
    tune(frenchHorn);
}
```

L' output è:

```
Wind.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
```

Esso riceve una reference a **Instrument**. Così come fa il compilatore a sapere che questa reference a **Instrument** punta a un oggetto di tipo **Wind** e non a un **Brass** o **Stringed**? Il compilatore non può saperlo. Per avere una comprensione più approndita di questa problematica dobbiamo parlare del meccanismo del *binding* (associazione tra chiamata del metodo ed esecuzione).

### *Binding* della chiamata dei metodi

La connessione tra la chiamata di un metodo e il corpo del metodo, è detta *binding*. Quando il binding è eseguito prima del'esecuzione del programma (dal compilatore o dal linker) è detto *early binding*. Può darsi che non ne abbiate mai sentito parlare perchè nei linguaggi procedurali come il C, è la sola opzione.

La parte che confonde del precedente programma è legata all'*early binding*,  perchè il compilatore non può sapere il metodo corretto da chiamare quando ha solo la reference a **Instrument**.

La soluzione è chiamata *late binding*, che significa che il binding avviene a run time (durante l'esecuzione), basandosi sul tipo dell'oggetto. Il *late binding* è anche detto *dinamic binding* o *runtime binding*. Quando un linguaggio implementa il *late binding*, ci deve essere un meccanismo per determinare a runtime il tipo dell'oggetto e chiamare il metodo appropriato. Cioè, il compilatore non può conoscere il tipo dell'oggetto, ma il meccanismo di chiamata del metodo riesce a saperlo e chiama il codice del metodo appropriato. Il meccanismo del *late binding*, varia da linguaggio a linguaggio, ma si può pensare che qualche tipo di informazione sul tipo sia all'interno dell'oggetto.&#x20;

Il binding di tutti i metodi in Java è tipo *late binding* a meno che il metodo non sia **static** o **final** (i metodi **private** sono implicitamente **final**). &#x20;

Perchè dichiarare un metodo **final**? Previene da fare l'overridding del metodo. Forse ancora più importante, disattiva il *late binding* o piuttosto dice al compilatore che il *dinamic binding* non è necessario. Permette al compilatore di generare codice leggermente più efficiente.&#x20;

### Produrre il giusto comportamento

Una volta che sappiamo che che il *binding* di tutti i metodi in Java avviene tramite il *late binding*, possimo scrivere il codice che parla alle classi base (superclasse, classe astratta o interface) e sapere che tutte le classi derivate lavoreranno correttamente utilizzando lo stesso codice.  O detto con altre parole, tu "invii un messaggio a un oggetto ed è poi l'oggetto a capire la cosa giusta da fare".&#x20;

Con il solito esempio, il diagramma dell'ereditarietà della classe:

![](/files/-LXhoR7z5BYXWls1AiRC)

`Shape` è la classe base e `Circle`, `Square` e `Triangle` sono le classi derivate o sottoclassi.

Facciamo l'upcast, della sottoclasse alla classe base, così:

```java
Shape s = new Circle();
```

Con questa operazione, un oggetto di tipo **Circle** è creato, e la reference risultante è assegnata a una reference **Shape**, che potrebbe sembrare un errore (l'assegnamento di un tipo a un altro); ma è corretto perché un oggetto di tipo **Circle** è anche un oggetto di tipo **Shape** per ereditarietà. Così il compilatore non segnala messaggi d'errore.&#x20;

Supponiamo che noi chiamiamo un metodo della base class (che noi abbiamo sovrascritto, overrdidden, nella classe derivata):

```java
s.draw();
```

Di nuovo, noi ci aspetteremmo che il metodo **draw()** di **Shape** è invocato perchè dopo tutto abbiamo una reference a **Shape**, così come potrebbe il compilatore fare altro? Ma vediamo che **draw()** di **Circle** è chiamata a causa dal *late binding* (polimorfismo).

Vediamo il seguente esempio, sempre sul polimorfimo, che mostra come la scelta del metodo da invocare, sia fatta a run-time:

```java
package net.mindview.util;

public class Print {
	
	public static void print(String message) {
		System.out.println(message);
	}
}

```

```java
package polymorphisme.shape;

public class Shape {
	public void draw() {
	}

	public void erase() {
	}
}
```

```java
package polymorphisme.shape;

import static net.mindview.util.Print.*;

public class Circle extends Shape {
	public void draw() {
		print("Circle.draw()");
	}

	public void erase() {
		print("Circle.erase()");
	}
}
```

```java
package polymorphisme.shape;

import static net.mindview.util.Print.*;

public class Square extends Shape {
	public void draw() {
		print("Square.draw()");
	}

	public void erase() {
		print("Square.erase()");
	}
}

```

```java
package polymorphisme.shape;

import static net.mindview.util.Print.*;

public class Triangle extends Shape {
	public void draw() {
		print("Triangle.draw()");
	}

	public void erase() {
		print("Triangle.erase()");
	}
}
```

```java
package polymorphisme.shape;

import java.util.*;

public class RandomShapeGenerator {
	private Random rand = new Random(47);

	public Shape next() {
		switch (rand.nextInt(3)) {
		default:
		case 0:
			return new Circle();
		case 1:
			return new Square();
		case 2:
			return new Triangle();
		}
	}
}

```

```java
package polymorphisme.shape;

public class Shapes {
	private static RandomShapeGenerator gen = new RandomShapeGenerator();

	public static void main(String[] args) {
		Shape[] s = new Shape[9];
		// Fill up the array with shapes:
		for (int i = 0; i < s.length; i++)
			s[i] = gen.next();
		// Make polymorphic method calls:
		for (Shape shp : s)
			shp.draw();
	}
}

```

Il metodo `next()` della classe `RandomShapeGenerator`, restituisce un oggetto sottoclasse di Shape generato in modo casuale (random). L'uppercast è fatto col *return* del metodo, in quanto il metodo `next()` ritorna un oggetto di tipo `Shape`. Nel `main` di `Shapes` viene così riempito un array di oggetti di sottoclassi di `Shape` generati in modo casuale. Dall'esecuzione si vede che viene chiamato il metodo `draw()` delle sottoclassi. E' come se gli oggetti su cui viene invocato il metodo, conservino memoria di quale sia il loto effettivo tipo.

Codice esempio [PolimorfismoRandomShapes](https://github.com/checksound/PolimorfismoRandomShapes).

### Estensibilità

Vediamo come il Polimorfismo renda semplice lavorare con gerarchie di classi che si estendono, cioè che aggiungo nuove sottoclassi di una classe base o di una sottoclasse.

![](/files/-LXhtA0S2Baj9BQSkToF)

Tutte questa nuove classi lavorano correttamente con metodo **tune()** precedente: non è necessario fare nessuna modifica al metodo e il metodo funziona correttamente anche con le nuove classi aggiunte, **Woodwind** e **Brass**. Se il metodo **tune()** è in un file separato e sono aggiunti dei metodi all'interfaccia **Instrument**, **tune** continua a lavorare correttamente, anche senza la necessità di ricompilare. Il polimorfismo è una tecnica importante per "separare le parti che cambiano dalle parti che rimangono le stesse".

### *Sostituzione pura vs Estensione*

Può sembrare che il modo più pulito per creare una gerarchia di ereditarietà è l'approccio "puro".  Cioè, solo i metodi che sono stati definiti nella classe base (superclasse) sono sovrascritti nelle classi derivate (sottoclassi), come si vede nel diagramma:

![Is-a relazione](/files/-LXii2zw-C5EfsnJQVNp)

Questo può essere chiamato una relazione "*is-a*" pura: oggetti della classe base e sottoclassi hanno la stessa interfaccia (cioè gli stessi metodi, anche se sovrascritti nelle sottoclassi).

Questo può essere pensato come sostituzione pura, perchè oggetti delle classi derivate possono essere dei perfetti sostituti per la classe base, e non dobbiamo sapere nessuna informazione extra sulle sottoclassi quando le utilizziamo:

![Pure substitution](/files/-LXiiF1G0SHRxdTgGi4f)

Cioè, la classe base può ricevere qualsiasi messaggio, che può essere inviato alle sottoclassi senza problemi perchè hanno esattamente la stessa interfaccia. Tutto quello che bisogna fare, è l'upcast dalla classe derivata e non preoccuparti più di quale specifico tipo avevi a che fare. Ogni cosa è gestita tramite il polimorfismo.

Quando si vedono le cose in questo modo, sembra che la relazione di sostutuzione pura sia l'unico modo per lavorare. Ma ciò è parziale, vediamo, come anche la parola **extends** sembra incoraggiare per le sottoclassi, ci possono essere anche relazioni del tipo *is-like-a*, nel senso che la classe derivata è come la classe base, ha la stessa interfaccia fondamentale, ma ha anche altre funzionalità che necessitano di altri metodi che le implementino:

![Is-like-a relazione](/files/-LXiiwp3_UhNJQKkkDsE)

Sebbene questo è un approccio utile c'è anche un inconveniente: riguardo la parte estesa dell'interfaccia della sottoclasse, una volta fatto l'upcasting alla classe base, non possono essere chiamati i metodi (dell'interfaccia estesa):

![Estenzione](/files/-LXijMPg0son_CgX02Ji)

Ci sono casi in cui necessiti chiamare i metodi dell'intaccia estesa e devi recuperare il tipo esatto dell'oggetto, non ti basta più la base class ma ti serve come tipo della sottoclasse. Nella sezione seguente vediamo come si fa.

### Downcasting e runtime type information

Siccome facendo l'upcasting abbiamo perso le specifiche informazioni del tipo, ha senso che per ritrovare le informazioni del tipo - coiè per scendere nella gerarchia dei tipi - bisogna fare il **downcast**. Tuttavia sappiamo che l'**upcast** è sempre sicuro, perchè la classe base non può avere un'interfaccia più grande delle proprie classi derivate. Quindi ogni messaggio inviato tramite la classe base è garantito che sia accettato. Ma con il **downcast**, noi non sappiamo se una `Shape` è (per esempio) un `Circle`, potrebbe essere un `Triangle` o uno `Square` o qualche altra forma.

In Java, ogni cast è controllato a runtime. Se non è corretto, viene sollevata l'eccezione **ClassCastException**. L' azione di controllare il tipo a runtime è detta *runtime type identification* (RTTI). Il seguente codice mostra il comportamento di RTTI:

```java
class Useful {
    public void f() {}
    public void g() {}
}

class MoreUseful extends Useful {
    public void f() {}
    public void g() {}
    public void u() {}
    public void v() {}
    public void w() {}
}

public class RTTI {
    public static void main(String[] args) {
        Useful[] x = {
                new Useful(),
                new MoreUseful()
                };
        x[0].f();
        x[1].g();
        // Compile time: method not found in Useful:
        //! x[1].u();
        ((MoreUseful)x[1]).u(); // Downcast/RTTI
        ((MoreUseful)x[0]).u(); // Exception thrown
        }
}
```

### Costruzione di un generic Sorter

Utilizzando il polimorfismo possiamo creare un generic Sorter che ordini un array di oggetti di classi che implementano una interfaccia da noi definita chiamata `it.esempiosorter.comparable.Comparable` con un metodo `compareTo`. Il metodo decidiamo che ritorni l'intero *0* se i due oggetto per l'ordinamento sono uguali, un mumero *> 0* se invece l'oggetto che implementa l'interfaccia è maggiore dell'oggetto passato per il confronto, un numero *< 0*, viceversa. Questo serve affinchè poi nel metodo che esegue il sorting, ci sia un criterio per controntare due oggetti e dire quale viene prima e dopo in base a come li vogliamo ordinare.

Sotto la nostra interfaccia `Comparable`:

```java
package it.esempiosorter.comparable;

public interface Comparable {
	
	/**
	* Compares this object with the specified object for order. 
	* Returns a negative integer, zero, or a positive integer as this object 
	* is less than, equal to, or greater than the specified object.
	**/
	public int compareTo(Object o);
	
}
```

La classe per il sorting `Sorter` con il metodo statico `sort`, che implementa l'algoritmo di sorting (quicksort) su oggetti di tipo `Comparable`:

```java
package it.esempiosorter.comparable;

public class Sorter {
	
	public static void sort(Comparable[] a, 
            boolean up) {
		sort(a, 0, a.length -1, up);
	}

	private static void sort(Comparable[] a, 
            int from, int to, 
            boolean up) {
		
		// If there is nothing to sort, return
	    if ((a == null) || (a.length < 2)) return;
	    
	    // This is the basic quicksort algorithm, stripped of frills that can make
	    // it faster but even more confusing than it already is.  You should
	    // understand what the code does, but don't have to understand just 
	    // why it is guaranteed to sort the array...
	    // Note the use of the compare() method of the Comparer object.
	    int i = from, j = to;
	    Comparable center = a[(from + to) / 2];
	    do {
	      if (up) {  // an ascending sort
	        while((i < to) && center.compareTo(a[i]) > 0) i++;
	        while((j > from) && center.compareTo(a[j]) < 0) j--;
	      } else {   // a descending sort
	        while((i < to) && center.compareTo(a[i]) < 0) i++;
	        while((j > from) && center.compareTo(a[j]) > 0) j--;
	      }
	      if (i < j) { 
	    	  Comparable tmp = a[i];  a[i] = a[j];  a[j] = tmp;          // swap elements
	       
	      }
	      if (i <= j) { i++; j--; }
	    } while(i <= j);
	    if (from < j) sort(a, from, j, up); // recursively sort the rest
	    if (i < to) sort(a, i, to, up);
		
	}
}

```

Come si vede il metodo **sort** ordina l'array di oggetti che implementano l'interfaccia `Comparable` : per eseguire l'ordinamento il metodo `sort` utilizza il `compareTo` quando deve decidere tra due oggetti quale è il meggiore. Il fatto che gli oggetti da ordinare implementino l'interfaccia `Comparable`, gli garantisce che il metodo `compareTo` è implementato.

Ora sappiamo che, per il polimorfismo, array di oggetti di classi che implementano `Comparable` sono sostituibili ad array di `Comparable`: basta che la mia classe implementi `Comparable` e la posso utilizzare il metodo di `sort` da noi implementato.

Vediamo un esempio di utilizzo con oggetti concreti: ordiniamo un array di oggetti di tipo `Persona` che implementa l'interfaccia `Comparable`:

```java
package it.esempiosorter.comparable;

public class Persona implements Comparable {
	
	private final String name;
	private final int altezza;
	
	public Persona(String name, int altezza) {
		this.name = name;
		this.altezza = altezza;
	}

	public String getName() {
		return name;
	}

	public int getAltezza() {
		return altezza;
	}
	
	@Override
	public int compareTo(Object o) {
		Persona pers = (Persona) o;
		if(this.altezza > pers.altezza)
			return 1;
		if(this.altezza == pers.altezza)
			return 0;
		
		return -1;
	}

	@Override
	public String toString() {
		return "Persona [name=" + name + ", altezza=" + altezza + "]";
	}
}

```

Classe di test:

```java
package it.esempiosorter.comparable;

import java.util.Arrays;

public class TestSorter {

	public static void main(String[] args) {
		
		Persona[] persone = {
				new Persona("Massimo", 175),
				new Persona("Luca", 159),
				new Persona("Chiara", 120),
				new Persona("Stefano", 180),
				new Persona("Eliseo", 160)
		};
		
		System.out.println("PRIMA: " + Arrays.toString(persone));
		
		Sorter.sort(persone, true);
		
		System.out.println("DOPO: " + Arrays.toString(persone));
		
	}
}

```

Vedi esempio: [Polimorfismo Comparable](https://github.com/checksound/EsempioPolimorfismoComparable) il codice nel package `it.esempiosorter.comparable`.

#### **ESERCIZI:**

* e se volessi ordinare le persone in base al nome della persona e non come ora in base all'altezza? Cosa dovrei fare? Fate una versione che ordina le persone in base al nome. (Suggerimento, dovete modificare il metodo `compareTo` di `Persona`).
* creo una classe di `Prodotti` con attributi `nomeProdotto` di tipo *String* e un altro `prezzo` di tipo *float*;  usando sempre `it.esempiosorter.comparable.Sorter.sort()` implementate l'ordinamento (sorting) in base al prezzo del prodotto.

Vediamo ora un altro caso: ammettiamo che io abbia invece una classe `Persona` che non implementa `it.esempiosorter.comparable.Comparable` perché ad esempio la classe è stata scritta da qualcun altro ed è **final,** quindi non posso estendere la classe originale con una sottoclasse che implementi l'interfaccia `Comparable`  e quindi non posso utilizzare il metodo `void sort(Comparable[] a, boolean up)` di `it.esempiosorter.comparable.Sorter`. Come fare a questo punto?&#x20;

Partiamo da una nuova classe `Persona` che questa volta non implementa `Comparable`:

```java
package it.esempiosorter.comparator;

public final class Persona {
	
	private final String name;
	private final int altezza;
	
	public Persona(String name, int altezza) {
		this.name = name;
		this.altezza = altezza;
	}

	public String getName() {
		return name;
	}

	public int getAltezza() {
		return altezza;
	}
	
	@Override
	public String toString() {
		return "Persona [name=" + name + ", altezza=" + altezza + "]";
	}

}


```

Scriviamo ora un'altra funzione sort generica che ordina oggetti ma che ha come parametro anche un oggetto che imprementa l'interfaccia `Comparator` (da non confondere con `Comparable` di prima): la signature del metodo sort: `void sort(Object[] a, boolean up, Comparator comparator)`

Vediamo l'interfaccia `Comparator`:

```java
package it.esempiosorter.comparator;

public interface Comparator {
	
	/**
	* Compares its two arguments for order. 
	* Returns a negative integer, zero, or a positive integer as the first argument 
	* is less than, equal to, or greater than the second.
	**/
	public int compare(Object o1, Object o2);
}
```

l'interfaccia ha il solo metodo, `compare`, che permette di confrontare due oggetti per dire quale dei due viene prima in base all'ordinamento che vogliamo eseguire: se i due oggetti sono uguali torna il valore *0*, se il primo oggetto è maggiore del secondo allora ritorna un numero *> 0* altrimenti un numero *< 0*. Il criterio per confrontare due oggetti è in questo caso passato come parametro alla funzione sort come oggetto che implementa l'interfaccia `Comparator`.

Vediamo il nuovo sorter per oggetti generici:

```java
package it.esempiosorter.comparator;

public class Sorter {
	
	public static void sort(Object[] a, 
            boolean up, Comparator comparator) {
		sort(a, 0, a.length -1, up, comparator);
	}
	
	private static void sort(Object[] a, 
            int from, int to, 
            boolean up, Comparator comparator) {
		
		// If there is nothing to sort, return
	    if ((a == null) || (a.length < 2)) return;
	    
	    // This is the basic quicksort algorithm, stripped of frills that can make
	    // it faster but even more confusing than it already is.  You should
	    // understand what the code does, but don't have to understand just 
	    // why it is guaranteed to sort the array...
	    // Note the use of the compare() method of the Comparer object.
	    int i = from, j = to;
	    Object center = a[(from + to) / 2];
	    do {
	      if (up) {  // an ascending sort
	        while((i < to) && comparator.compare(center, a[i]) > 0) i++;
	        while((j > from) && comparator.compare(center, a[j]) < 0) j--;
	      } else {   // a descending sort
	        while((i < to) && comparator.compare(center, a[i]) < 0) i++;
	        while((j > from) && comparator.compare(center, a[j]) > 0) j--;
	      }
	      if (i < j) { 
	    	  Object tmp = a[i];  a[i] = a[j];  a[j] = tmp;          // swap elements
	       
	      }
	      if (i <= j) { i++; j--; }
	    } while(i <= j);
	    if (from < j) sort(a, from, j, up, comparator); // recursively sort the rest
	    if (i < to) sort(a, i, to, up, comparator);
		
	}
}

```

L'implementazione è molto simile al sort precedente: si può vedere che nella signature del metodo ora accetta array di tipo `Object`, ma viene passato anche un oggetto che implementa l'interfaccia `Comparator` che è utilizzato per confrontare due oggetti e dire quale dei due viene prima nell'ordinamento.

Vediamo quindi di implementare un `Comparator` per oggetti di tipo `Persona` per ordinarle per attributo `eta`:

```java
package it.esempiosorter.comparator;

public class PersonaComparator implements Comparator {

	@Override
	public int compare(Object o1, Object o2) {
		Persona p1 = (Persona) o1;
		Persona p2 = (Persona) o2;
		
		if(p1.getAltezza() > p2.getAltezza())
			return 1;
		
		if(p1.getAltezza() == p2.getAltezza())
			return 0;
		
		return -1;
	}

}

```

Una classe per testare il sort:

```java
package it.esempiosorter.comparator;

import java.util.Arrays;

public class TestSorter {
	
	public static void main(String[] args) {
		
		Persona[] persone = {
				new Persona("Massimo", 175),
				new Persona("Luca", 159),
				new Persona("Chiara", 120),
				new Persona("Stefano", 180),
				new Persona("Eliseo", 160)
		};
		
		System.out.println("PRIMA: " + Arrays.toString(persone));
		
		Sorter.sort(persone, true, new PersonaComparator());
		
		System.out.println("DOPO: " + Arrays.toString(persone));
		
	}
}

```

Il fatto che sort abbia per parametro un array di `Object` permette di invocare il metodo anche per array di qualsiasi altra classe in quanto sottoclasse di `Object`: nel nostro caso come array di oggetti di tipo `Persona`.

#### ESERCIZI:

* costruite una nuova classe che implementa `Comparator` per ordinate oggetti di tipo `Persona` in base al nome, invece che per l'altezza;&#x20;
* costruite un nuovo `Comparator` per `Prodotti` e ordinateli per il prezzo;

### Esempio polimorfismo utilizzato in classi JDK - Collections.sort()

Siccome le problematiche di ordinamento (*sorting*) sono molto comuni nella programmazione, Java nelle librerie standard (JDK - Java Development Kit), già fornisce le classi [Arrays](https://docs.oracle.com/javase/7/docs/api/java/util/Arrays.html) e [Collections ](https://docs.oracle.com/javase/7/docs/api/java/util/Collections.html)con una serie di metodi static **sort** per ordinare rispettivamente array e collezioni. Fornisce inoltre le interfacce [Comparable](https://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html) (*java.lang.Comparable*) e [Comparator](https://docs.oracle.com/javase/7/docs/api/java/util/Comparator.html) (*java.util.Comparator*) come quelle da noi prima da noi implementate.

Cominciamo dalla signature del metodo **Collections.sort**:

```java
public static <T extends Comparable<? super T>> void sort(List<T> list)
```

La signature del metodo per ora è troppo complicata da capire completamente. Dice in sostanza che gli elementi della lista devono implementare l'interfaccia [Comparable](https://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html) che ha un solo metodo:

```java
int compareTo(T o)
```

Se gli oggetti della lista che voglio ordinare implementano l'interfaccia **Comparable** allora il metodo statico **Collections.sort()** permette di ordinare la lista.

Quindi il metdoto **sort** sfrutta il polimorfismo, nel senso che le liste passate come parametri, vengono viste come liste di **Comparable** e utilizzando il metodo di **Comparable**, **compareTo**, vengono ordinate.

Vedi esempio: [Polimorfismo Comparable](https://github.com/checksound/EsempioPolimorfismoComparable) il codice nel package `it.esempiosorter.general`.

**TO-DO**: Costruire un esempio simile usando la classe `java.util.Arrays` e utilizzando il metodo **sort()** per l'ordinamento degli array.

### Esempio polimorfismo *Sistema gestione temperatura*

![](/files/-LXJ_MFAyGQumMVI6W9u)

*Air Conditioner* **is-a** (è ) un *Cooling System* (hanno la stessa interfaccia, intesa come metodi pubblici della classe).

*Heat Pump* **is-like-a** (è come) un *Cooling System* (aggiunge il metodo *heat()* all'interfaccia, intesa come metodi pubblici della classe).

Vedi esempio: [EsempioGestioneTemperatura](https://github.com/checksound/EsempioGestioneTemperatura). Completare il codice implementatndo cil metodo **compareTo**.

## RIASSUNTO

Polimorfismo significa "forme differenti". Nella programmazione ad oggetti, abbiamo la stessa interface dalla classe base, e differenti forme usando quella interfaccia: le differenti versioni dei metodi dinamicamente chiamati.

Come aggiamo visto in questo capitolo è impossibile capire o anche creare, un esempio di polimorfismo senza usare l'astrazione dei dati e l'ereditarietà. Il polimorfismo è una proprietà che non può essere vista isolata (come lo **switch** statement per esempio), ma invece lavora in collaborazione, come parte di un mosaico più grande. delle relazioni tra le classi.

Per utilizzare il polimorfismo - e quindi la modalità di programmazione ad oggetti - bisogna espandere la visione della programmazione per includere non solo attributi e metodi di una singola classe, ma anche le cose in comuni tra le classi e le relazioni tra le classi. Sebbene lo sforzo richiesto non sia da poco, il gioco vale la candela.  Il risultato è una modalità di sviluppo più veloce, migliore organizzazione del codice, programmi estensibili, e più facili da mantenere.&#x20;


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://checksound.gitbook.io/corsojava/polimorfismo.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
