1. Classe astratta: ripasso delle basi
Prima di passare al confronto, ricordiamo che cos'è una classe astratta.
Una classe astratta — è una classe che non può essere istanziata direttamente (non si può scrivere new Animal()), ma può contenere sia metodi normali (con implementazione) sia metodi astratti (senza implementazione). Una classe astratta è spesso usata come base per altre classi, che ne ereditano il comportamento e/o sono tenute a implementare alcuni metodi.
Per esempio, se nella nostra applicazione ci sono diversi tipi di trasporto, si può creare una classe astratta Transport:
public abstract class Transport {
private String model;
public Transport(String model) {
this.model = model;
}
public String getModel() {
return model;
}
// Metodo astratto — nessuna implementazione, solo dichiarazione
public abstract void move();
// Metodo normale — c'è un'implementazione
public void printInfo() {
System.out.println("Modello del trasporto: " + model);
}
}
Caratteristiche di una classe astratta:
- Può contenere campi (stato).
- Può contenere metodi implementati.
- Può contenere metodi astratti (obbligatori da implementare nelle sottoclassi).
- Non si può creare un'istanza direttamente.
- Usata per comportamento e stato comuni.
2. Interfaccia: ripasso delle basi
Un'interfaccia è un insieme di metodi che una classe deve implementare. Un'interfaccia non descrive lo stato (non può avere campi normali, solo costanti) e non contiene implementazioni dei metodi (fino a Java 8). Un'interfaccia è un contratto puro: “Se mi implementi, devi saper fare questo”.
Esempio di interfaccia:
public interface Movable {
void move(int x, int y);
}
Caratteristiche di un'interfaccia:
- Non contiene stato (solo costanti public static final).
- Fino a Java 8 — solo metodi astratti (da Java 8 sono arrivati i metodi default e static, ma ne parleremo più avanti).
- I metodi sono sempre public abstract per impostazione predefinita.
- Una classe può implementare più interfacce.
- Usata per descrivere le capacità, “che cosa sa fare” una classe.
3. Tabella di confronto: classe astratta vs interfaccia
È ora di confrontare questi due strumenti faccia a faccia! Ecco una tabella riassuntiva:
| Caratteristica | Classe astratta | Interfaccia |
|---|---|---|
| Sintassi | |
|
| Si può creare un'istanza? | No | No |
| Può contenere metodi normali? | Sì | Fino a Java 8 — no; da Java 8 — solo default/static |
| Può contenere metodi astratti? | Sì | Sì (tutti i metodi fino a Java 8 sono astratti) |
| Può contenere campi (stato)? | Sì (qualsiasi campo) | Solo public static final (costanti) |
| Può contenere costruttori? | Sì | No |
| Ereditarietà | Solo una classe (astratta o normale) | Si possono implementare più interfacce |
| Modificatori dei metodi | Qualsiasi (public, protected, private) | I metodi sono per impostazione predefinita public abstract. Da Java 9 è possibile aggiungere metodi private da usare all'interno dell'interfaccia |
| Ereditarietà tramite | |
|
| Uso tipico | Implementazione e stato comuni | Descrizione di capacità e ruoli |
| Esempi dalla JDK | |
|
4. Quando usare un'interfaccia e quando una classe astratta?
Usa un'interfaccia quando:
- vuoi descrivere “che cosa sa fare” una classe senza preoccuparti di come lo fa.
- hai bisogno che una classe possa implementare più capacità indipendenti.
- Esempio: Comparable (si può confrontare), Serializable (si può serializzare), Runnable (si può eseguire in un thread).
Usa una classe astratta quando:
- vuoi fornire un'implementazione e uno stato comuni per tutte le sottoclassi.
- ti serve che tutte le sottoclassi abbiano determinati campi o metodi con implementazione.
- L'ereditarietà è strettamente “uno a uno”: una classe può estendere una sola classe (concreta o astratta).
Analogie dal mondo reale
- Un'interfaccia è come una “patente di guida”: se ce l'hai, puoi guidare un'auto, ma nessuno specifica con quale auto e in che modo lo fai.
- Una classe astratta è come un “progetto generale di un'auto”: tutte le auto hanno volante, pedali, motore, ma ogni marca implementa i dettagli a modo suo.
5. Esempi dalla libreria standard di Java
Interfaccia: Comparable
public interface Comparable<T> {
int compareTo(T o);
}
Qualsiasi classe che implementa questa interfaccia deve implementare il metodo compareTo. Per esempio, String, Integer, LocalDate e molte altre.
Classe astratta: AbstractList
public abstract class AbstractList<E> implements List<E> {
// Implementazione predefinita di alcuni metodi di List
// Alcuni metodi sono lasciati astratti
}
AbstractList implementa già parte del comportamento delle collezioni (per esempio, i metodi di aggiunta/rimozione), ma lascia alcuni metodi astratti affinché le sottoclassi possano implementarli a modo loro.
6. Esempi di codice: confronto nella pratica
Interfaccia
Creiamo un'interfaccia e una classe che la implementa.
public interface Printable {
void print();
}
public class Document implements Printable {
@Override
public void print() {
System.out.println("Sto stampando il documento...");
}
}
Classe astratta
Ora una classe astratta e la sua sottoclasse.
public abstract class Machine {
public void turnOn() {
System.out.println("La macchina è accesa.");
}
public abstract void work();
}
public class Printer extends Machine {
@Override
public void work() {
System.out.println("La stampante stampa...");
}
}
Una classe che combina entrambi: interfaccia e classe astratta
public class SmartPrinter extends Machine implements Printable {
@Override
public void work() {
System.out.println("La stampante intelligente è in funzione...");
}
@Override
public void print() {
System.out.println("La stampante intelligente stampa...");
}
}
7. Implementazione multipla di interfacce: perché è potente
In Java una classe può estendere una sola classe (astratta o concreta), ma può implementare quante interfacce vuole! Questo consente di creare architetture flessibili ed estensibili.
public interface Scannable {
void scan();
}
public class MultiFunctionPrinter extends Machine implements Printable, Scannable {
@Override
public void work() {
System.out.println("Il multifunzione è in funzione...");
}
@Override
public void print() {
System.out.println("Il multifunzione stampa...");
}
@Override
public void scan() {
System.out.println("Il multifunzione scansiona...");
}
}
Cosa scegliere e quando?
- Se stai progettando funzionalità di base con stato condiviso (per esempio, campi), usa una classe astratta.
- Se vuoi aggiungere agli oggetti delle “etichette di capacità” (per esempio, “sa stampare”, “sa confrontarsi”, “sa essere serializzato”) — usa le interfacce.
- Se non sei sicuro — inizia da un'interfaccia. In Java è considerata una buona pratica: le interfacce offrono maggiore flessibilità ed estensibilità.
8. Errori tipici e insidie
Errore n. 1: tentare di estendere più classi — Java non lo permette!
Una classe può estendere solo un'unica classe, ma può implementare molte interfacce. Per esempio, class A extends B, C — errore, mentre class A extends B implements X, Y, Z — va benissimo.
Errore n. 2: confondere i campi di un'interfaccia e quelli di una classe.
In un'interfaccia si possono dichiarare solo costanti (public static final). Non si può dichiarare uno stato normale, per esempio, private int count; — il compilatore ti fermerà subito.
Errore n. 3: non hai implementato tutti i metodi dell'interfaccia.
Se una classe non implementa anche solo un metodo dell'interfaccia — deve essere dichiarata abstract, altrimenti il compilatore segnalerà un errore.
Errore n. 4: tentare di istanziare un'interfaccia o una classe astratta.
Entrambi questi tipi sono “semilavorati”. Si possono solo estendere, non istanziare direttamente:
Printable p = new Printable(); // Errore!
Machine m = new Machine(); // Errore!
Errore n. 5: pensare che un'interfaccia possa avere un costruttore.
Le interfacce non possono avere costruttori, perché non descrivono lo stato degli oggetti. Solo le classi (concrete e astratte).
GO TO FULL VERSION