Cos'è una classe astratta
Un'interfaccia dichiara un comportamento senza stato. Una classe normale è completamente implementata e si può istanziare. Una classe astratta sta a metà strada: può avere campi, costruttori e metodi completi come una classe normale, ma può anche lasciare alcuni metodi non implementati e non può essere istanziata direttamente. La contrassegni con la parola chiave abstract.
L'idea è raccogliere in un unico punto tutto ciò che le sottoclassi hanno in comune, obbligando ogni sottoclasse a completare le parti che cambiano davvero.
Animal definisce getName() una sola volta per tutte le sottoclassi e dichiara sound() come abstract: un metodo con una firma ma senza corpo, che Dog deve fornire.
Non puoi istanziare una classe astratta
Dato che una classe astratta può avere metodi incompleti, crearne una direttamente ti lascerebbe con un oggetto incompleto. Il compilatore lo rifiuta:
Animal a = new Animal("???"); // error: Animal is abstract; cannot be instantiated
Istanzi sempre una sottoclasse concreta, cioè una che ha implementato ogni metodo astratto. L'istanza di quella sottoclasse può poi essere memorizzata in una variabile del tipo astratto, ed è proprio così che si usa l'astrazione.
I metodi astratti obbligano le sottoclassi a decidere
Un metodo abstract è una promessa che la sottoclasse deve mantenere. Se una sottoclasse si dimentica di implementarne uno, diventa essa stessa astratta e il compilatore te lo segnala. È questa la leva principale della classe astratta: garantisce che un certo comportamento esista senza imporre cosa faccia.
describe() è scritto una sola volta in Shape, eppure chiama l'area() di ciascuna sottoclasse. La classe astratta fornisce l'impalcatura comune; le sottoclassi forniscono i dettagli.
Stato condiviso e costruttori
A differenza di un'interfaccia tradizionale, una classe astratta può contenere campi di istanza e definire dei costruttori. Il costruttore non crea mai un Animal o una Shape da solo: viene eseguito tramite super(...) quando si crea una sottoclasse, e inizializza lo stato condiviso.
Il campo balance, deposit e applyInterest stanno in un unico punto. Solo la regola che cambia davvero, interestRate(), resta astratta. Il costruttore di una sottoclasse deve chiamare super(...) per inizializzare lo stato ereditato.
Una trappola: abstract insieme a final
abstract e final sono opposti. Un metodo abstract richiede un override; un metodo final lo vieta. Marcare la stessa cosa in entrambi i modi, o creare una classe astratta final, è un errore di compilazione. Ricorda anche che una classe astratta può avere zero metodi astratti: dichiarare una classe abstract solo per impedirne l'istanziazione è lecito, e a volte utile per tipi base che vuoi soltanto estendere.
abstract final class Bad { } // error: abstract and final conflict
abstract class Base {
abstract final void f(); // error: an abstract method can't be final
}
Classe astratta o interfaccia
Le due cose si sovrappongono, quindi la scelta dipende da cosa devi condividere:
- Classe astratta: usala per classi strettamente collegate che condividono stato e codice.
SavingseCheckingestendono entrambeAccount, ereditando il campobalancee la logica dideposit. Una classe ne estende solo una. - Interfaccia: usala per una capacità che classi non collegate possono condividere. Un
Birde unAirplanepossono essere entrambiFlyablesenza condividere alcuna implementazione. Una classe può implementarne molte.
Uno schema frequente le combina: un'interfaccia definisce il contratto e una classe astratta implementa il codice ripetitivo, così le sottoclassi concrete completano solo ciò che le rende uniche.
Prossimo passo: il polimorfismo
Nota che in ogni esempio qui sopra abbiamo memorizzato un'istanza di una sottoclasse in una variabile del tipo astratto, poi abbiamo chiamato un metodo ottenendo in automatico il comportamento della sottoclasse. Questa capacità, un solo tipo di riferimento con molti comportamenti a runtime, si chiama polimorfismo, ed è ciò che rende davvero utili classi astratte e interfacce. È l'argomento della prossima pagina.
Domande frequenti
Cos'è una classe astratta in Java?
Una classe astratta è una classe dichiarata con la parola chiave abstract che non può essere istanziata da sola. È pensata per essere estesa. Può combinare metodi e campi completamente implementati (stato e codice condivisi per le sottoclassi) con metodi abstract senza corpo: implementarli diventa compito di ogni sottoclasse.
Si può istanziare una classe astratta in Java?
No. new AbstractType() è un errore di compilazione perché una classe astratta può avere metodi non implementati (abstract), quindi l'oggetto sarebbe incompleto. Si istanzia invece una sottoclasse concreta che implementa tutti i metodi astratti, e la si memorizza in una variabile del tipo astratto.
Qual è la differenza tra una classe astratta e un'interfaccia in Java?
Una classe astratta può avere campi di istanza, costruttori e logica parzialmente implementata, ma una classe può estenderne solo una. Un'interfaccia dichiara un comportamento senza stato di istanza, e una classe può implementarne molte. Usa una classe astratta per condividere stato e codice tra sottoclassi strettamente collegate; usa un'interfaccia per dare a classi non collegate una capacità comune.