Koyote ha scritto:
Quindi diciamo che è un fatto di comodità?
No, non c'entra affatto la "comodità".
Dimentica solo un attimo il "abstract". Ti è chiaro in generale il concetto del polimorfismo e il principio di "override" (ripeto, in generale, non contare abstract ora)?
Se un tipo B è
sottotipo di un tipo A, allora B non può fare cose
in meno rispetto ad A. Può fare eventualmente cose
in più e/o le stesse cose ma
in maniera differente. Insomma, estendendo una classe non puoi "togliere" metodi/campi. Puoi aggiungere metodi/campi nuovi .... o ridefinire (=
override) metodi (di istanza chiaramente, non static) già esistenti e noti nel supertipo.
L'esempio che segue è il solito un po' "stupido" sugli animali (cioè non molto utile realmente) ma serve per capire:
public class Prova {
public static void main(String[] args) {
Animale animale1 = new Gatto();
Animale animale2 = new Cane();
animale1.mangia();
animale2.mangia();
}
}
class Animale {
public void mangia() {
System.out.println("mangia di Animale");
}
}
class Gatto extends Animale {
public void mangia() {
System.out.println("mangia di Gatto");
}
public void emettiFusa() {
System.out.println("emette fusa");
}
}
class Cane extends Animale {
public void mangia() {
System.out.println("mangia di Cane");
}
}
L'output è:
mangia di Gatto
mangia di Cane
Partiamo da un fatto: Gatto estende Animale, così come Cane estende Animale. Questa è la
ereditarietà, cioè il concetto generale che un tipo possa
estendere un altro tipo.
Quindi un oggetto Gatto è di tipo Gatto ma anche di tipo Animale. Si dice che un Gatto "
è-un" (in inglese IS-A) Animale. Quindi ovunque è richiesto un Animale, è assolutamente lecito passare/assegnare un oggetto Gatto (o Cane). Questo è il
polimorfismo derivante dalla ereditarietà. Esiste anche un polimorfismo più "leggero", quello dato dal overloading, che però non c'entra niente direttamente con la ereditarietà.
Gatto ha un emettiFusa(), questo è un metodo in più ("nuovo") rispetto ad Animale. Sulla variabile animale1, NON puoi invocare emettiFusa(), nonostante l'oggetto assegnato è un Gatto. Java è un linguaggio tipizzato staticamente, a livello di compilazione effettua tutta una serie di controlli sui tipi. E tra questi, verifica che se vuoi invocare un metodo su un tipo X, il metodo deve essere "noto" ad X.
Animale non ha un emettiFusa(), quindi su una variabile Animale non è invocabile.
Ma Animale ha un mangia(), con una sua implementazione di base. Gatto e Cane ridefiniscono questo metodo mettendo in atto il polimorfismo e il principio di override. Il metodo in Gatto/Cane ha la stessa signature e quindi fa effettivamente un override di quello in Animale.
Nel main il compilatore "sa" solo che su un riferimento di tipo Animale dovrà invocare un metodo con la forma del mangia() (con quel nome e senza argomenti). Questo lo verifica e stabilisce il compilatore.
Ma la implementazione viene scelta a RUNTIME, basandosi sull'oggetto realmente istanziato. Ad animale1 è assegnato un oggetto Gatto, quindi a runtime il mangia() realmente eseguito è quello che Gatto ha ridefinito e possiede. Non è quello di Animale.
--------------
Tutto chiaro fin qui? Torniamo al abstract. Quando vedi la parola chiave
abstract devi pensare a qualcosa di incompleto, che come tale non può essere usato direttamente così come è.
Ci possono essere diversi motivi per cui un elemento (classe/metodo) è incompleto ma tipicamente lo è perché rappresenta qualcosa che è molto astratto/generalizzato e quindi non ha senso poterlo usare direttamente.
E' il caso del Solido. Rappresenta un concetto molto astratto, non ha molto senso infatti fare
new Solido(). Solido di che??? Quindi Solido è abstract. E i suoi calcolaVolume/calcolaSuperficie, idem, non ha molto senso avere una implementazione concreta in Solido. Cosa potrebbero fare?? Quindi anche i metodi sono abstract.
Dal punto di vista del override, la questione è abbastanza semplice: la classe Gatto NON è obbligata a ridefinire il mangia(). Se gli andasse bene quello di Animale, potrebbe non ridefinirlo affatto!
Mentre una classe Cubo concreta (non abstract) che estende Solido invece DEVE implementare i calcolaVolume/calcolaSuperficie.
Alla fine è tutto qui: un metodo abstract è un obbligo (di implementazione) per una sottoclasse concreta.