Classi Astratte in Java
- Le classi astratte in Java sono utilizzate per definire metodi che devono essere implementati dalle sottoclassi, senza fornire un'implementazione completa nella superclasse.
- Un metodo astratto è dichiarato con la parola chiave
abstract
e non ha un corpo. - Una classe che contiene uno o più metodi astratti deve essere anch'essa dichiarata astratta.
- Non è possibile istanziare direttamente una classe astratta, ma è possibile creare riferimenti a essa per puntare a oggetti di sottoclassi.
- Le classi astratte possono contenere metodi concreti, oltre ai metodi astratti.
- Le sottoclassi devono implementare tutti i metodi astratti della superclasse o essere anch'esse dichiarate astratte.
Uso delle Classi Astratte
Ci sono situazioni in cui è opportuno definire una superclasse che dichiari la struttura di una data astrazione senza fornire un'implementazione completa di ogni metodo.
Cioè, a volte si vuole creare una superclasse che definisce solo un formato generalizzato che sarà condiviso da tutte le sue sottoclassi, lasciando a ciascuna sottoclasse il compito di completarne i dettagli. Una tale classe determina la natura dei metodi che le sottoclassi devono implementare.
Una situazione di questo tipo può verificarsi quando una superclasse non è in grado di creare un'implementazione significativa per un metodo. Questo è il caso della classe Figura
usata nell'esempio della lezione precedente. La definizione di area()
è semplicemente un segnaposto. Non calcolerà né visualizzerà l'area di alcun tipo di oggetto.
Come si vedrà creando le proprie librerie di classi, non è raro che un metodo non abbia una definizione significativa nel contesto della sua superclasse. È possibile gestire questa situazione in due modi.
Un modo, come mostrato nell'esempio di Figura
, consiste semplicemente nel far sì che venga emesso un messaggio di avviso. Sebbene questo approccio possa essere utile in certe situazioni — come il debugging — di solito non è appropriato. Si possono avere metodi che devono essere sovrascritti dalla sottoclasse affinché la sottoclasse abbia un significato. Si consideri la classe Triangolo
. Non ha alcun significato se area()
non è definito. In questo caso, si vuole un modo per assicurarsi che una sottoclasse effettivamente sovrascriva tutti i metodi necessari. La soluzione di Java a questo problema è il metodo astratto.
Si può richiedere che determinati metodi vengano sovrascritti dalle sottoclassi specificando il modificatore di tipo abstract
. Questi metodi vengono talvolta indicati come responsabilità della sottoclasse poiché non hanno alcuna implementazione specificata nella superclasse. Quindi, una sottoclasse deve sovrascriverli — non può semplicemente usare la versione definita nella superclasse. Per dichiarare un metodo astratto, si usa questa forma generale:
abstract tipo nome(lista-parametri);
Come si può vedere, non è presente alcun corpo del metodo.
Ogni classe che contiene uno o più metodi astratti deve essere anch'essa dichiarata astratta.
Per dichiarare una classe astratta, si usa semplicemente la parola chiave abstract
davanti alla parola chiave class
all'inizio della dichiarazione della classe. Non possono esistere oggetti di una classe astratta. Cioè, una classe astratta non può essere istanziata direttamente con l'operatore new
.
Tali oggetti sarebbero inutili, poiché una classe astratta non è completamente definita. Inoltre, non è possibile dichiarare costruttori astratti o metodi statici astratti. Ogni sottoclasse di una classe astratta deve o implementare tutti i metodi astratti nella superclasse, oppure essere dichiarata anch'essa astratta.
Ecco un semplice esempio di una classe con un metodo astratto, seguita da una classe che implementa tale metodo:
// Una semplice dimostrazione di abstract.
abstract class A {
abstract void chiamami();
// i metodi concreti sono comunque permessi nelle classi astratte
void chiamamiAnche() {
System.out.println("Questo è un metodo concreto.");
}
}
class B estende A {
void chiamami() {
System.out.println("Implementazione di B di chiamami.");
}
}
class DimostrazioneAstratta {
public static void main(String[] args) {
B b = new B();
b.chiamami();
b.chiamamiAnche();
}
}
Oggetti di classi astratte
Si noti che non vengono dichiarati oggetti della classe A
nel programma. Come detto, non è possibile istanziare una classe astratta.
Un altro punto: la classe A
implementa un metodo concreto chiamato chiamamiAnche()
. Questo è perfettamente accettabile. Le classi astratte possono includere tutta l'implementazione che si ritiene opportuna.
Le classi astratte non possono essere usate per istanziare oggetti, ma possono essere usate per creare riferimenti a oggetti, poiché l'approccio di Java al polimorfismo in fase di esecuzione è implementato attraverso l'uso di riferimenti di superclasse.
Pertanto, è necessario essere in grado di creare un riferimento a una classe astratta in modo da poterlo usare per puntare a un oggetto di sottoclasse. Questa funzionalità sarà messa in pratica nell'esempio successivo.
Esempio
Utilizzando una classe astratta, si può migliorare la classe Figura
mostrata in precedenza. Poiché non esiste un concetto significativo di area per una figura bidimensionale indefinita, la versione seguente del programma dichiara area()
come metodo astratto all'interno di Figura
. Questo, ovviamente, significa che tutte le classi derivate da Figura
devono sovrascrivere area()
.
// Uso di metodi e classi astratti.
abstract class Figura {
double dim1;
double dim2;
Figura(double a, double b) {
dim1 = a;
dim2 = b;
}
// area è ora un metodo astratto
abstract double area();
}
class Rettangolo estende Figura {
Rettangolo(double a, double b) {
super(a, b);
}
// sovrascrive area per il rettangolo
double area() {
System.out.println("Dentro area per Rettangolo.");
return dim1 * dim2;
}
}
class Triangolo estende Figura {
Triangolo(double a, double b) {
super(a, b);
}
// sovrascrive area per triangolo rettangolo
double area() {
System.out.println("Dentro area per Triangolo.");
return dim1 * dim2 / 2;
}
}
class AreeAstratte {
public static void main(String[] args) {
// Figura f = new Figura(10, 10); // ora è illegale
Rettangolo r = new Rettangolo(9, 5);
Triangolo t = new Triangolo(10, 8);
Figura rifer; // questo è valido, nessun oggetto viene creato
rifer = r;
System.out.println("L'area è " + rifer.area());
rifer = t;
System.out.println("L'area è " + rifer.area());
}
}
Come indicato nel commento dentro main()
, non è più possibile dichiarare oggetti di tipo Figura
, poiché ora è astratta. E tutte le sottoclassi di Figura
devono sovrascrivere area()
. Per verificarlo, si può provare a creare una sottoclasse che non sovrascrive area()
. Si riceverà un errore in fase di compilazione.
Errore di compilazione in mancanza di override
Anche se non è possibile creare un oggetto di tipo Figura
, è possibile creare una variabile di riferimento di tipo Figura
.
La variabile rifer
è dichiarata come riferimento a Figura
, il che significa che può essere usata per fare riferimento a un oggetto di qualsiasi classe derivata da Figura
. Come spiegato, è proprio tramite variabili di riferimento della superclasse che i metodi sovrascritti vengono risolti a tempo di esecuzione.