super è un puntatore implicito alla classe madre.
Questo significa che punta sempre alla classe estesa da quella che stai usando.
Per capire se e come va utilizzato dipende dall'ambito in cui lo utilizzi: esso ricopre un ruolo particolare nel costruttore, mentre ricopre un ruolo "più marginale" (con questo non intendo che sia superfluo, anzi...) all'interno di altri metodi.
Nel costruttore
Se viene richiamato, super va richiamato obbligatoriamente come prima istruzione.
Esso è obbligatorio solo nel caso in cui la classe madre non preveda un costruttore vuoto (chiamato costruttore di default o anche costruttore implicito). Il perchè dovrebbe essere ovvio, se si conoscono le basi della programmazione ObjectOriented: quando si istanzia un oggetto di una qualsiasi classe, per prima cosa viene sempre istanziato un oggetto della classe madre; ma se per istanziare un oggetto della classe madre devo passare degli argomenti al costruttore, allora sarà la classe figlia che si prenderà in carico l'onere di passarglieli. E questo viene fatto, appunto, tramite il puntatore implicito super. Esempio:
codice:
public class Madre {
public Madre(int param) {
...
}
...
}
public class Figlia extends Madre {
public Figlia(...) {
super(0); // Inizializzo con valore 0
...
}
...
}
Perchè non è obbligatorio utilizzarlo se la classe madre dispone di un costruttore di default? Perchè, ovviamente, se c'è un costruttore di default la VM riesce ad istanziare l'oggetto senza doverglielo specificare esplicitamente (è, appunto, implicito).
Fuori dal costruttore
Fuori dal costruttore l'utilizzo del puntatore alla classe madre serve per potersi riferire ai membri (metodi o proprietà pubbliche) della classe madre. Va utilizzato obbligatoriamente nel caso si voglia riferirsi ad un membro della classe madre che, nella figlia, è stato sottoposto ad override (riscrittura, vedi Polimorfismo). Diventa, allora, palese che, se nella classe figlia, io sovrascrivo un metodo della classe madre, non potrei più riferirmi ad esso (per le regole di scope). Vediamo un esempio:
codice:
public class Madre {
...
public void disegna() {
xyz; // Codice della classe madre
}
}
public class Figlia {
...
public void disegna() {
super.disegna();
abc; // Codice della classe figlia
}
}
Nell'esempio c'è il metodo disegna() che la classe figlia vuole personalizzare. Ma la personalizzazione richiede, comunque, che venga eseguito il codice della classe madre. Per poterlo fare devo ricorrere a super. Se non utilizzassi super, richiamerei implicitamente il metodo disegna() della classe figlia (e in questo particolare caso provocherei un disastro: l'applicazione genererebbe una StackOverflowError).
Lo stesso se io volessi, in un qualsiasi altro punto del programma nella classe Figlia, richiamare il metodo disegna() della classe madre dovrei riferirmi ad esso con il super altrimenti richiamerei quello della classe figlia.
Perchè la maggior parte delle volte non è necessario utilizzarlo per richiamare dei membri della classe madre? La risposta risiede nell'ereditarietà: la classe figlia eredita tutti i membri pubblici (e protected) della classe madre; per cui, se io non sottopongo ad override nulla, posso riferirmi ad essi come se essi fossero dei membri della classe Figlia (e per ereditarietà è proprio così).
Spero di averti illustrato decentemente tutto quanto.
Ciao.