Lezioni del modulo (2/2)
Polimorfismo e Classi Astratte
Il polimorfismo e le classi astratte sono concetti avanzati della programmazione ad oggetti che consentono di scrivere codice flessibile, estensibile e indipendente dalle specifiche implementazioni.
Il Polimorfismo
Il termine polimorfismo (molte forme) si riferisce alla capacità di trattare oggetti di classi diverse come se appartenessero ad un tipo comune. In Java, una variabile di tipo superclasse può memorizzare un riferimento ad un oggetto di qualsiasi sottoclasse.
class Animal {
public void makeSound() {
System.out.println("Verso...");
}
}
class Dog extends Animal {
public void makeSound() {
System.out.println("Woof");
}
}
class Cat extends Animal {
public void makeSound() {
System.out.println("Miao");
}
}
Grazie al polimorfismo, possiamo fare questo:
Animal myAnimal1 = new Dog(); // Polimorfismo
Animal myAnimal2 = new Cat(); // Polimorfismo
myAnimal1.makeSound(); // Esegue il metodo di Dog (Woof)
myAnimal2.makeSound(); // Esegue il metodo di Cat (Miao)
La decisione su quale metodo invocare avviene a runtime (Late Binding o dynamic dispatch) in base all'oggetto effettivo e non al tipo della variabile di riferimento.
Classi Astratte (abstract)
Una classe astratta è una classe contrassegnata con la parola chiave abstract che non può essere istanziata direttamente (non puoi fare new MyAbstractClass()). Serve come "modello parziale" per altre classi.
Metodi Astratti
Una classe astratta può contenere metodi astratti: metodi dichiarati senza corpo (senza parentesi graffe e senza codice), terminanti con un punto e virgola. Le sottoclassi non astratte sono obbligate ad implementare tutti i metodi astratti ereditati.
abstract class Shape {
String color;
// Metodo astratto (senza corpo)
public abstract double getArea();
}
class Circle extends Shape {
double radius;
public Circle(double radius) {
this.radius = radius;
}
// Obbligatorio implementare getArea
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
Dynamic Binding / Late Binding
Come fa Java a sapere quale metodo eseguire a runtime? Questo processo è noto come Late Binding (o Dynamic Binding). A differenza dell'overloading (risolto a tempo di compilazione), il compilatore non sa quale metodo specifico verrà eseguito. Genera invece un'istruzione di chiamata generica. A runtime, la Java Virtual Machine (JVM) esamina l'oggetto reale in memoria e invoca l'implementazione del metodo definita nella classe di quell'oggetto.
Classi Astratte e Mancata Implementazione
Se una sottoclasse eredita da una classe astratta, ha due opzioni:
- Implementare tutti i metodi astratti: in questo caso la classe può essere una classe normale (concreta) ed essere istanziata.
- Dichiararsi astratta: se la sottoclasse non fornisce un'implementazione per tutti i metodi astratti ereditati, deve essere a sua volta dichiarata con la parola chiave
abstract.
abstract class Animal {
public abstract void makeSound();
}
// Questa classe DEVE essere astratta perché non implementa makeSound()
abstract class Canine extends Animal {
// Eredita makeSound() ma non lo implementa
}
Prova tu
Dichiara una variabile s di tipo Shape e assegnale un nuovo oggetto Circle usando il polimorfismo.
Mostra suggerimento
Scrivi `Shape s = new Circle();` per usare il tipo base come tipo del riferimento.
Soluzione disponibile dopo 3 tentativi
Rendi la classe Shape astratta e aggiungi il metodo astratto double getArea(). Poi completa Square in modo che estenda Shape ed implementi getArea() ritornando side * side.
Mostra suggerimento
Dichiara `public abstract double getArea();` in Shape. In Square, aggiungi `@Override public double getArea() { return side * side; }`.
Soluzione disponibile dopo 3 tentativi
Dichiara nel main un array di tipo Shape[] contenente un oggetto Circle con raggio 2.0 e un oggetto Square con lato 3.0. Successivamente, usa un ciclo (for o for-each) per stampare a console l'area di ciascuna forma invocando il metodo getArea().
Mostra suggerimento
Dichiara l'array con `Shape[] shapes = { new Circle(2.0), new Square(3.0) };` e scorrilo con un ciclo `for (Shape s : shapes)` stampando `s.getArea()`.
Soluzione disponibile dopo 3 tentativi