W3docs

Override dei metodi in Java

Sovrascrivi i metodi della classe padre nelle sottoclassi Java con @Override e il dispatch dinamico.

L'override si verifica quando una sottoclasse dichiara un metodo con la stessa firma di uno ereditato, sostituendo la versione del padre. Combinato con il polimorfismo, è il meccanismo che consente di chiamare un metodo su un riferimento di tipo padre e ottenere l'esecuzione dell'implementazione corretta della sottoclasse.

Questo capitolo stabilisce le regole: cosa conta come override, cosa no, cosa il compilatore permette di modificare nella versione della sottoclasse e cosa garantisce realmente @Override.

Come appare un override

Un metodo sovrascrive quello ereditato quando si verificano tutte queste condizioni:

  • Stesso nome.
  • Stessa lista di parametri (tipi e ordine, dopo l'erasure dei generici).
  • Tipo di ritorno uguale a quello del padre oppure un suo sottotipo (return covariante).
  • Visibilità uguale a quella del padre o più ampia.
  • Non dichiarato final, static o private nel padre.
class Animal {
  String speak() { return "(noise)"; }
}
class Cat extends Animal {
  @Override
  String speak() { return "meow"; }
}

Questo è un override valido. Chiamare speak() su un Cat restituisce "meow"; chiamarlo attraverso un riferimento Animal che punta a un Cat restituisce anch'esso "meow" — il dispatch viene deciso dalla classe dell'oggetto reale.

@Override — usalo sempre

@Override è un'annotazione che dice al compilatore "intendo sovrascrivere un metodo ereditato." Se non sovrascrive effettivamente un metodo — nome errato, parametri errati, tipo di ritorno errato — il compilatore segnala un errore:

class Cat extends Animal {
  @Override
  String Speak() { return "meow"; }    // ERROR — no Speak() in Animal
}

Senza l'annotazione, questo compilerebbe silenziosamente come un metodo del tutto nuovo chiamato Speak, e ((Animal)cat).speak() continuerebbe a restituire "(noise)" del padre. L'annotazione non ha alcun costo e intercetta un'intera famiglia di bug silenziosi. Scrivila sempre sugli override.

Nota

Gli override che scriverai più spesso non riguardano le tue classi — si tratta di metodi ereditati da Object, la radice di ogni classe. Sovrascrivere toString(), equals(Object) e hashCode() è il modo in cui i tuoi oggetti si stampano in modo leggibile, si confrontano per valore e si comportano correttamente come chiavi in una HashMap o HashSet. @Override su questi intercetta il classico bug di scrivere equals(Cat other) (un overload) invece di equals(Object other) (il vero override).

Override vs overloading

AspettoOverrideOverloading
DoveTra una sottoclasse e una superclasseNella stessa classe
FirmaDeve corrispondere a quella del padreDeve differire dalle altre
DispatchA runtime, basato sull'oggetto realeA compile-time, basato sui tipi degli argomenti
Annotazione@Overridenessuna

Si confondono spesso. L'override è un metodo, diversi tipi di oggetti. L'overloading è più metodi, un tipo, scelto in base a ciò che si passa.

Tipi di ritorno covarianti

La sottoclasse può dichiarare un tipo di ritorno più specifico rispetto al padre:

class Animal {
  Animal makeBaby() { return new Animal(); }
}
class Cat extends Animal {
  @Override
  Cat makeBaby() { return new Cat(); }      // returns Cat, not Animal — fine
}

Questa è la covarianza — il tipo di ritorno dell'override è un sottotipo di quello del padre. È sicuro perché qualsiasi chiamante che si aspetta un Animal da makeBaby() accetta volentieri un Cat. Il vantaggio è che i chiamanti che sanno di avere un Cat ricevono un Cat senza casting:

Cat kitten = new Cat().makeBaby();    // no cast needed

Fare il contrario (una sottoclasse che allarga il tipo di ritorno) non è consentito — romperebbe i chiamanti che si aspettavano il tipo più specifico.

La visibilità può solo allargarsi

L'override deve essere almeno altrettanto visibile quanto il metodo del padre:

class A { public  void hi() { } }
class B extends A { protected void hi() { } }   // ERROR — reduces visibility
class C extends A { public    void hi() { } }   // ok

Restringerla significherebbe che un chiamante con un riferimento A potrebbe chiamare hi() ma, dopo l'assegnazione A a = new B(), non potrebbe più — il che romperebbe la sostituibilità.

Le eccezioni possono solo ridursi

Se il metodo padre non dichiara eccezioni checked, l'override non può dichiararne nessuna. Se il padre lancia un'eccezione checked, l'override può lanciare la stessa o una sua sottoclasse più specifica — ma non una più ampia:

class A {
  void run() throws IOException { ... }
}
class B extends A {
  @Override void run() throws FileNotFoundException { ... }    // ok — FileNotFoundException extends IOException
}
class C extends A {
  @Override void run() throws Exception { ... }                // ERROR — too broad
}

Le eccezioni unchecked (sottoclassi di RuntimeException) sono senza restrizioni — puoi sempre lanciarle da un override.

Cosa non si può sovrascrivere

  • Metodi private. Non sono visibili alla sottoclasse, quindi un metodo con lo stesso nome nella sottoclasse è semplicemente un nuovo metodo.
  • Metodi final. Contrassegnati specificamente per impedire gli override.
  • Metodi static. Una sottoclasse può dichiarare un metodo statico con lo stesso nome e firma, ma questo si chiama method hiding, non override. Non c'è polimorfismo — la JVM risolve la chiamata in base al tipo a compile-time del riferimento:
class A { static String klass() { return "A"; } }
class B extends A { static String klass() { return "B"; } }

A a = new B();
System.out.println(a.klass());   // "A" — static, not polymorphic

Questo è uno dei pochi casi in cui la regola "il dispatch del metodo sceglie la versione dell'oggetto reale" non si applica.

Chiamare il padre dall'override

Un pattern comune è estendere anziché sostituire il comportamento del padre usando super.method(...):

class Logger {
  void log(String s) { System.out.println(s); }
}
class TimestampedLogger extends Logger {
  @Override
  void log(String s) {
    super.log("[" + System.currentTimeMillis() + "] " + s);
  }
}

L'override decora il comportamento del padre invece di duplicarlo. Trattato nel capitolo sulla parola chiave super.

Esempio pratico

java— editable, runs on the server

Prossimi passi

Hai visto come le sottoclassi sostituiscono i metodi del padre. L'idea successiva — l'astrazione — è il rovescio della medaglia: dichiarare un metodo che non ha corpo predefinito e obbligare ogni sottoclasse concreta a fornirne uno. Continua con astrazione in Java.

Esercitazione

Pratica
In una sottoclasse, perché vale la pena scrivere @Override su ogni override anche se il codice compilerebbe senza?
In una sottoclasse, perché vale la pena scrivere @Override su ogni override anche se il codice compilerebbe senza?
Was this page helpful?