Parola chiave super in Java
Usa super in Java per riferirsi alla classe genitore: chiama costruttori e metodi sovrascritti della superclasse.
super è il corrispettivo di this per la classe genitore. Dove this è "l'oggetto corrente", super è "l'oggetto corrente, visto come la sua superclasse." Ha senso solo all'interno di una classe che estende un'altra, e compare in tre contesti:
super(args)dentro un costruttore — chiama un costruttore del genitore.super.method(args)dentro un metodo di istanza — chiama la versione del genitore di un metodo sovrascritto.super.fielddentro un metodo di istanza — legge un campo del genitore che la sottoclasse nasconde (shadowing).
Il primo caso è di gran lunga il più comune.
super(...) — chiamare un costruttore del genitore
Ogni costruttore di una sottoclasse deve iniziare chiamando un costruttore del genitore. Se non ne scrivi uno, Java inserisce super(); come prima riga — una chiamata al costruttore senza argomenti del genitore:
public class Animal {
String name;
public Animal() { name = "(unnamed)"; } // no-arg
}
public class Cat extends Animal {
public Cat() {
// super(); ← inserted by the compiler
}
}Quando il genitore non ha un costruttore senza argomenti, devi chiamarne uno esplicitamente:
public class Animal {
String name;
public Animal(String name) { this.name = name; } // no no-arg constructor
}
public class Cat extends Animal {
public Cat(String name) {
super(name); // required — there is no Animal()
}
}L'errore più comune con l'ereditarietà è dimenticare questo. Quando un genitore non ha un costruttore senza argomenti, il compilatore tenta comunque di inserire super(); in qualsiasi costruttore della sottoclasse che non chiami super(...) esplicitamente — e poi fallisce con "there is no default constructor available in Animal". La soluzione è aggiungere una chiamata esplicita a super(...) che corrisponda a un costruttore reale del genitore.
Regole per super(...):
- Deve essere la prima istruzione nel corpo del costruttore.
- Un costruttore può concatenarsi a un altro costruttore della stessa classe con
this(...)oppure chiamare il genitore consuper(...)— non entrambi. - Gli argomenti devono corrispondere a un costruttore esistente del genitore.
Perché la regola della "prima istruzione"?
La JVM deve costruire completamente la parte del genitore dell'oggetto prima che qualsiasi codice della sottoclasse venga eseguito. Questo include l'inizializzazione dei campi final dichiarati dal genitore. Permettere al codice della sottoclasse di eseguire per primo significherebbe leggere quei campi prima che siano stati assegnati i loro valori.
super.method(args) — chiamare la versione del genitore
All'interno di un metodo di istanza, super.method(...) chiama la versione del genitore di method, anche se la classe corrente lo ha sovrascritto:
public class Animal {
String speak() { return "(some noise)"; }
}
public class Cat extends Animal {
@Override
String speak() {
return super.speak() + " — actually, meow";
// ^^^^^^^^^^^^^ calls Animal.speak(), not Cat.speak()
}
}
new Cat().speak(); // "(some noise) — actually, meow"Senza super., un semplice speak() dentro Cat.speak chiamerebbe se stesso e ricorrerebbe all'infinito.
Il pattern classico consiste nell'estendere il comportamento del genitore anziché sostituirlo:
@Override
void onSave() {
super.onSave(); // run parent's save logic first
// then add subclass-specific behavior
}super.method(...) non può risalire di due livelli — non esiste super.super.method() in Java. Se la tua classe estende una classe che ne estende un'altra, puoi chiamare il metodo del genitore immediato, non quello del nonno.
super.field — accedere a un campo nascosto
Se una sottoclasse dichiara un campo con lo stesso nome di un campo del genitore, il campo della sottoclasse nasconde (shadowing) quello del genitore:
public class A {
String label = "A's label";
}
public class B extends A {
String label = "B's label";
void show() {
System.out.println(label); // B's label
System.out.println(super.label); // A's label
System.out.println(this.label); // B's label
}
}Questa è quasi sempre una cattiva progettazione — avere due campi con lo stesso nome in una coppia genitore-figlio è confuso — ma il linguaggio ti fornisce super.field per disambiguare quando accade. La soluzione più pulita è rinominare uno dei campi.
Nota che i campi non sono polimorfici. A differenza dei metodi sovrascritti, l'accesso ai campi viene deciso in fase di compilazione in base al tipo dichiarato del riferimento. Il capitolo sul polimorfismo spiega il perché.
super e static
super esiste solo in contesti di istanza, come this. Non puoi scrivere super in un metodo o inizializzatore static — non c'è un oggetto corrente, quindi non c'è nemmeno una vista del genitore di esso. Per chiamare un metodo statico dichiarato sul genitore, qualificalo con il nome della classe del genitore:
public class A { static void hi() { System.out.println("hi"); } }
public class B extends A {
static void demo() {
A.hi(); // ok — call by class name
// super.hi(); // ERROR — super in a static context
}
}Una sottoclasse può anche dichiarare un metodo static con lo stesso nome del metodo static del genitore — questo si chiama method hiding, non overriding, e segue regole in fase di compilazione. Viene trattato brevemente nel capitolo sull'overriding dei metodi.
Catene di super
Ogni chiamata super(...) attiva il livello successivo della catena di ereditarietà. Tracciamo ciò che viene eseguito per una sottoclasse profondamente annidata:
new SiameseCat("Lulu")
→ SiameseCat(String)
→ super("Lulu") — Cat(String)
→ super("Lulu") — Animal(String)
→ super() — Object()
→ Animal body runs
→ Cat body runs
→ SiameseCat body runsL'inizializzazione procede sempre dall'antenato più lontano fino alla classe corrente. Al momento in cui il corpo di SiameseCat viene eseguito, ogni campo del genitore è completamente inizializzato.
Un esempio pratico
Cosa c'è dopo
super è il ponte verso la classe genitore, ma l'idea più grande che abilita è il polimorfismo — la capacità di chiamare un metodo su un riferimento di tipo genitore e fare in modo che venga eseguita l'implementazione corretta della sottoclasse a runtime. Questo è il prossimo capitolo. Continua con il polimorfismo in Java.