Classi e Metodi final in Java

In Java la parola chiave final ha tre usi.

Il primo utilizzo lo abbiamo già analizzato: può essere utilizzata per creare l'equivalente di una costante con nome.

Gli altri due usi di final si applicano all'ereditarietà e li studieremo in questa lezione.

Concetti Chiave
  • final può essere utilizzato per prevenire l'override di metodi e l'ereditarietà di classi.
  • I metodi final non possono essere sovrascritti e possono migliorare le prestazioni grazie all'inserimento in linea.
  • Le classi final non possono essere sottoclassate, il che implica che tutti i loro metodi sono automaticamente final.
  • È illegale dichiarare una classe sia abstract che final.

Uso di final per Prevenire l'Override di Metodi

Sebbene l'override dei metodi sia una delle funzionalità più potenti di Java, ci saranno momenti in cui si vorrà impedirne l'applicazione.

Per vietare che un metodo venga sovrascritto, si deve specificare final come modificatore all'inizio della sua dichiarazione. I metodi dichiarati come final non possono essere sovrascritti ossia non se ne può effettuare l'override.

Il seguente frammento illustra final:

class A {
    final void metodo() {
        System.out.println("Questo è un metodo final.");
    }
}

class B extends A {
    void metodo() { // ERRORE! Impossibile sovrascrivere.
        System.out.println("Illegale!");
    }
}

Poiché metodo() è dichiarato come final, non può essere sovrascritto in B. Se si tenta di farlo, risulterà in un errore di compilazione.

I metodi dichiarati come final possono talvolta fornire un miglioramento delle prestazioni: il compilatore è libero di inserire in linea le chiamate a questi metodi perché sa a priori che non verranno sovrascritti da una sottoclasse.

Quando viene chiamato un piccolo metodo final, spesso il compilatore Java può copiare il bytecode per la subroutine direttamente nel codice compilato del metodo chiamante, eliminando così il costoso overhead associato a una chiamata di metodo. L'inserimento in linea è un'opzione disponibile solo con i metodi final.

Normalmente, Java risolve le chiamate ai metodi in modo dinamico, in fase di esecuzione. Questo è chiamato late binding. Tuttavia, poiché i metodi final non possono essere sovrascritti, una chiamata a uno di essi può essere risolta in fase di compilazione. Questo è chiamato early binding.

Uso di final per Prevenire l'Ereditarietà di Classi

A volte si vorrà impedire che una classe venga ereditata.

Per farlo, si deve far precedere la dichiarazione della classe con final. Dichiarare una classe come final implica dichiarare automaticamente tutti i suoi metodi come final, anch'essi.

Come ci si potrebbe aspettare, è illegale dichiarare una classe sia abstract che final, poiché una classe astratta è incompleta di per sé e si affida alle sue sottoclassi per fornire implementazioni complete.

Ecco un esempio di una classe final:

final class A {
    //...
}

// La seguente classe è illegale.
class B extends A { // ERRORE! Impossibile sottoclassare A
    //...
}

Come indicano i commenti, è illegale per B ereditare da A poiché A è dichiarata come final.

Consiglio

Controllo avanzato dell'ereditarietà

A partire da JDK 17, è stata aggiunta a Java la possibilità di sigillare (seal) una classe. Il sealing offre un controllo preciso sull'ereditarietà. Studieremo questo concetto nelle lezioni future.