Buoni motivi per vietare l'eredità in Java?


178

Quali sono i buoni motivi per vietare l'ereditarietà in Java, ad esempio utilizzando le classi finali o le classi utilizzando un unico costruttore privato senza parametri? Quali sono i buoni motivi per rendere definitivo un metodo?


1
Pedantry: il costruttore predefinito è quello generato per te dal compilatore Java se non ne scrivi esplicitamente uno nel tuo codice. Intendi il costruttore no topic.
John Topley,

Non è il "costruttore predefinito implicito"?
cretzel,

2
"Non devi fornire alcun costruttore per la tua classe, ma devi fare attenzione quando lo fai. Il compilatore fornisce automaticamente un costruttore predefinito senza argomento per qualsiasi classe senza costruttori." java.sun.com/docs/books/tutorial/java/javaOO/constructors.html - John Topley
John Topley

Risposte:


184

Il tuo miglior riferimento qui è l'articolo 19 dell'eccellente libro di Joshua Bloch "Effective Java", chiamato "Progettazione e documento per ereditarietà o vietarlo". (È l'articolo 17 nella seconda edizione e l'articolo 15 nella prima edizione.) Dovresti davvero leggerlo, ma riassumerò.

L'interazione delle classi ereditate con i loro genitori può essere sorprendente e imprevedibile se l'antenato non è stato progettato per essere ereditato.

Le classi dovrebbero quindi venire in due tipi:

  1. Classi progettate per essere estese e con sufficiente documentazione per descrivere come dovrebbe essere fatto

  2. Classi contrassegnate come finali

Se stai scrivendo un codice puramente interno, questo potrebbe essere un po 'eccessivo. Tuttavia, lo sforzo aggiuntivo necessario per aggiungere cinque caratteri a un file di classe è molto ridotto. Se stai scrivendo solo per un consumo interno, un programmatore futuro può sempre rimuovere il "finale" - puoi pensarlo come un avvertimento che dice "questa classe non è stata progettata pensando all'eredità".


6
Nella mia esperienza questo non è eccessivo se qualcuno oltre a me utilizzerà il codice. Non ho mai capito perché Java abbia metodi sostituibili per impostazione predefinita.
Eric Weilnau,

9
Per comprendere alcuni degli effettivi Java, è necessario comprendere il modo in cui Josh progetta. Dice [qualcosa del genere] che dovresti sempre progettare interfacce di classe come se fossero un'API pubblica. Penso che l'opinione della maggioranza sia che questo approccio sia solitamente troppo pesante e poco flessibile. (YAGNI, ecc.)
Tom Hawtin - affronta il

9
Buona risposta. (Non posso resistere nel sottolineare che sono sei personaggi, dato che c'è anche uno spazio ... ;-)
joel.neely

36
Nota che rendere le lezioni definitive potrebbe rendere più difficile il test (più difficile da
stubare

10
Se la classe finale sta scrivendo su un'interfaccia, la beffa non dovrebbe essere un problema.
Fred Haslam,

26

È possibile che si desideri rendere definitivo un metodo in modo che le classi di esclusione non possano modificare il comportamento su cui si fa affidamento in altri metodi. I metodi chiamati nei costruttori sono spesso dichiarati finali, quindi non si ottengono spiacevoli sorprese durante la creazione di oggetti.


Non ho capito bene questa risposta. Se non ti dispiace, potresti spiegare cosa intendi per "ignorare le classi non può cambiare il comportamento su cui si basa in altri metodi"? In primo luogo chiedo perché non ho capito la frase, e in secondo luogo perché Netbeans sta raccomandando che una delle mie funzioni che sto chiamando dal costruttore dovrebbe essere final.
Nav

1
@Nav Se rendi definitivo un metodo, una classe di override non sarà in grado di avere la sua versione di quel metodo (o sarà un errore di compilazione). In questo modo sai che il comportamento implementato in quel metodo non cambierà in seguito quando qualcun altro estende la classe.
Bill the Lizard,

19

Un motivo per rendere finale una classe sarebbe se volevi forzare la composizione sull'eredità. Ciò è generalmente desiderabile per evitare l'accoppiamento stretto tra le classi.


15

Ci sono 3 casi d'uso in cui puoi scegliere i metodi finali.

  1. Per evitare che la classe derivata sovrascriva una particolare funzionalità della classe base.
  2. Questo per motivi di sicurezza, in cui la classe di base fornisce alcune importanti funzionalità di base del framework in cui la classe derivata non dovrebbe modificarla.
  3. I metodi finali sono più veloci dei metodi di istanza, in quanto non viene utilizzato il concetto di tabella virtuale per i metodi finali e privati. Quindi, ovunque ci sia una possibilità, prova ad usare i metodi finali.

Scopo del finale di classe:

In modo che nessun corpo possa estendere quelle classi e cambiare il loro comportamento.

Ad esempio: Wrapper class Integer è una classe finale. Se quella classe non è definitiva, chiunque può estendere Integer nella propria classe e modificare il comportamento di base della classe intera. Per evitare ciò, java ha creato tutte le classi wrapper come classi finali.


Non molto convinto con il tuo esempio. In tal caso, perché non rendere definitivi i metodi e le variabili invece di rendere definitiva l'intera classe. Puoi per favore modificare la tua risposta con i dettagli.
Rajkiran,

4
Anche se rendi definitive tutte le variabili e i metodi, altri possono ereditare la tua classe e aggiungere funzionalità extra e modificare il comportamento di base della tua classe.
user1923551

3
> Se quella classe non è definitiva, chiunque può estendere Integer nella propria classe e modificare il comportamento di base della classe intera. Diciamo che è stato permesso e che questa nuova classe è stata chiamata DerivedInteger, non cambia ancora la classe Integer originale e chiunque la usi lo DerivedIntegerfa a proprio rischio, quindi non capisco perché sia ​​un problema.
Siddhartha,


7

L'ereditarietà è come una motosega: molto potente, ma terribile nelle mani sbagliate. O progetti una classe da cui ereditare (che può limitare la flessibilità e impiegare molto più tempo) o dovresti proibirla.

Vedi gli articoli 16 e 17 della seconda edizione di Java effettivi o il mio post sul blog "Imposta sulle successioni" .


Il link al post del blog è interrotto. Inoltre, pensi di poter approfondire un po 'di più su questo?

@MichaelT: corretto il collegamento, grazie - seguilo per ulteriori elaborazioni :) (Non ho il tempo di aggiungere questo in questo momento, temo.)
Jon Skeet

@JonSkeet, il collegamento al post del blog è interrotto.
Lucifero,

@Kedarnath: Funziona per me ... è stato rotto per un po ', ma ora punta alla pagina corretta, su codeblog.jonskeet.uk ...
Jon Skeet

4

Hmmm ... posso pensare a due cose:

Potresti avere una classe che si occupa di determinati problemi di sicurezza. Sottoclassandolo e alimentando il sistema con la sua sottoclasse, un utente malintenzionato può eludere le restrizioni di sicurezza. Ad esempio, la tua applicazione potrebbe supportare plug-in e se un plug-in può semplicemente sottoclassare le tue classi rilevanti per la sicurezza, può usare questo trucco per far entrare in qualche modo una sua sottoclasse. Tuttavia, questo è piuttosto qualcosa che Sun deve affrontare per quanto riguarda applet e simili, forse non un caso così realistico.

Uno molto più realistico è evitare che un oggetto diventi mutevole. Ad esempio, poiché le stringhe sono immutabili, il codice può conservare in modo sicuro riferimenti ad esso

 String blah = someOtherString;

invece di copiare prima la stringa. Tuttavia, se puoi sottoclassare String, puoi aggiungere dei metodi che consentono di modificare il valore della stringa, ora nessun codice può fare più affidamento sul fatto che la stringa rimarrà la stessa se copia la stringa come sopra, invece deve duplicare il corda.


2

Inoltre, se stai scrivendo una classe commerciale a codice chiuso, potresti non voler che le persone siano in grado di cambiare la funzionalità in linea, specialmente se hai bisogno di fornirti supporto e le persone hanno scavalcato il tuo metodo e ti lamenti del fatto che chiamarlo risultati inaspettati.


2

Se contrassegni le classi e i metodi come finali, potresti notare un piccolo guadagno in termini di prestazioni, poiché il runtime non deve cercare il metodo di classe giusto per invocare un determinato oggetto. I metodi non finali sono contrassegnati come virtuali in modo che possano essere correttamente estesi se necessario, i metodi finali possono essere direttamente collegati o compilati in linea nella classe.


1
Credo che questo sia un mito, poiché le macchine virtuali di generazione attuale dovrebbero essere in grado di ottimizzare e deoptimizzare secondo necessità. Ricordo di aver visto questo bencharmking e classificarlo come un mito.
Miguel Ping,

1
La differenza nella generazione del codice non è un mito, ma forse lo sono le prestazioni. Come ho detto è un piccolo guadagno, un sito ha menzionato un aumento delle prestazioni del 3,5%, un po 'più alto, che nella maggior parte dei casi non vale la pena andare tutto nazista sul tuo codice.
sommato il

1
Non è un mito. In effetti, il compilatore può solo incorporare i metodi V finali. Non sono sicuro se qualche JIT "inline" esegue il runtime artistico, ma ne dubito, dato che potresti caricare una classe derivata in un secondo momento.
Uri,

1
Microbenchmark == grano-of-sale. Detto questo, dopo il riscaldamento del ciclo 10B, le chiamate avg su 10B non mostrano praticamente alcuna differenza in Eclipse sul mio laptop. Due metodi che restituivano String, final era 6.9643us, non final era 6.9641us. Quel delta è rumore di fondo, IMHO.
joel.neely,

1

Per impedire alle persone di fare cose che potrebbero confondere se stesse e gli altri. Immagina una libreria di fisica in cui hai alcune costanti o calcoli definiti. Senza usare la parola chiave finale, qualcuno potrebbe venire e ridefinire i calcoli di base o le costanti che non dovrebbero MAI cambiare.


2
C'è qualcosa che non capisco su quell'argomento - se qualcuno ha cambiato quei calcoli / costanti che non dovrebbero cambiare - non ci sono errori dovuti a quel 100% è colpa loro? In altre parole, in che modo il cambiamento è di beneficio?
opaco b

Sì, è tecnicamente "colpa loro", ma immagina che qualcun altro che utilizza la libreria che non sia stata messa al corrente delle modifiche, possa creare confusione. Ho sempre pensato che quello fosse il motivo per cui molte delle classi di Java Base erano contrassegnate come finali, per impedire alle persone di alterare le funzionalità di base.
Anson Smith,

@matt b - Sembra che tu stia supponendo che lo sviluppatore che ha apportato la modifica abbia fatto così consapevolmente o che gli sarebbe importato che fosse colpa sua. Se qualcuno oltre a me utilizzerà il mio codice, lo contrassegnerò come definitivo, a meno che non sia destinato a essere modificato.
Eric Weilnau,

Questo è in realtà un uso diverso di "final" da quello richiesto.
DJClayworth,

Questa risposta sembra confondere l'ereditarietà e la mutabilità dei singoli campi con la capacità di scrivere una sottoclasse. È possibile contrassegnare singoli campi e metodi come finali (impedendone la ridefinizione) senza vietare l'ereditarietà.
joel.neely,

0

Volete rendere definitivo un metodo in modo che le classi di override non cambino il suo comportamento. Quando vuoi essere in grado di cambiare il comportamento, rendi pubblico il metodo. Quando si esegue l'override di un metodo pubblico, è possibile modificarlo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.