la class immutabile dovrebbe essere definitiva?

Dice in questo articolo che:

Fare una finale di class perché è immutabile è una buona ragione per farlo.

Sono un po ‘sconcertato da questo … Capisco che l’immutabilità sia una buona cosa dal punto di vista della sicurezza e della semplicità del thread, ma sembra che queste preoccupazioni siano in qualche modo ortogonali all’estensibilità. Quindi, perché l’immutabilità è una buona ragione per fare una finale di class?

La spiegazione di ciò è fornita nel libro “Effective Java”

Prendi in considerazione le classi BigDecimal e BigInteger in Java.

Non è stato ampiamente compreso che le classi immutabili dovevano essere effettivamente definitive quando venivano scritti BigInteger e BigDecimal , quindi tutti i loro metodi possono essere sovrascritti. Sfortunatamente, questo non ha potuto essere corretto dopo il fatto preservando la retrocompatibilità.

Se si scrive una class la cui sicurezza dipende dall’immutabilità di un argomento BigInteger o BigDecimal da un client non affidabile, è necessario verificare che l’argomento sia un BigInteger o BigDecimal “reale”, piuttosto che un’istanza di una sottoclass attendibile. . Se è quest’ultimo, è necessario copiarlo in modo difensivo ipotizzando che possa essere mutabile.

  public static BigInteger safeInstance(BigInteger val) { if (val.getClass() != BigInteger.class) return new BigInteger(val.toByteArray()); return val; } 

Se si consente la sottoclassificazione, si potrebbe rompere la “purezza” dell’object immutabile.

Seguendo il Principio di sostituzione di Liskov una sottoclass può estendersi ma mai ridefinire il contratto del suo genitore. Se la class base è immutabile, è difficile trovare esempi di dove la sua funzionalità possa essere utilmente estesa senza interrompere il contratto.

Si noti che in linea di principio è ansible estendere una class immutabile e modificare i campi base, ad esempio se la class base contiene un riferimento a un array, gli elementi all’interno dell’array non possono essere dichiarati definitivi. Ovviamente la semantica dei metodi può anche essere modificata tramite overriding.

Suppongo che potresti dichiarare tutti i campi come privati ​​e tutti i metodi come finali, ma quale sarebbe l’uso di ereditare?

Perché se la class è definitiva non puoi estenderla e renderla mutabile.

Anche se rendi i campi definitivi, ciò significa che non puoi riassegnare il riferimento, non significa che non puoi cambiare l’object a cui ci si riferisce.

Non vedo molto uso in un progetto per una class immutabile che dovrebbe anche essere estesa, quindi l’aiuto finale mantiene intatta l’immutabilità.

Principalmente sicurezza, penserei. Per la stessa ragione, String è definitivo, tutto ciò che qualsiasi codice relativo alla sicurezza vuole trattare come immutabile deve essere definitivo.

Supponiamo che tu abbia una class definita per essere immutabile, chiamala MyUrlClass, ma non contrassegnarla come finale.

Ora, qualcuno potrebbe essere tentato di scrivere un codice di sicurezza come questo;

 void checkUrl(MyUrlClass testurl) throws SecurityException { if (illegalDomains.contains(testurl.getDomain())) throw new SecurityException(); } 

Ed ecco cosa hanno inserito nel loro metodo DoRequest (MyUrlClass url):

 securitymanager.checkUrl(urltoconnect); Socket sckt = opensocket(urltoconnect); sendrequest(sckt); getresponse(sckt); 

Ma non possono farlo, perché non hai reso finale MyUrlClass. Il motivo per cui non possono farlo è che, se lo facessero, il codice potrebbe evitare le restrizioni del security manager semplicemente sovrascrivendo getDomain () per restituire “www.google.com” la prima volta che viene chiamato e “www.evilhackers.org” il secondo, e passando un object della loro class in DoRequest ().

A proposito, non ho nulla contro evilhackers.org, se esiste anche …

In assenza di problemi di sicurezza si tratta di evitare errori di programmazione, ed è ovviamente a te decidere come farlo. Le sottoclassi devono mantenere il contratto dei genitori e l’immutabilità è solo una parte del contratto. Ma se le istanze di una class devono essere immutabili, renderla definitiva è un buon modo per assicurarsi che siano davvero tutte immutabili (cioè che non ci siano istanze mutabili di sottoclassi che si muovono attorno, che possono essere usate ovunque che il genitore la class è richiesta).

Non penso che l’articolo che hai citato debba essere considerato come un’istruzione che “tutte le classi immutabili devono essere definitive”, specialmente se hai una ragione positiva per progettare la tua class immutabile per ereditarietà. Quello che stava dicendo è che proteggere l’immutabilità è un valido motivo per la finale, dove i problemi di prestazione immaginaria (che è quello di cui si sta parlando in quel momento) non sono validi. Si noti che ha dato “una class complessa non progettata per l’ereditarietà” come una ragione altrettanto valida. Si può ragionevolmente sostenere che non riuscire a rendere conto dell’eredità nelle classi complesse è qualcosa da evitare, così come non è in grado di tenere conto dell’eredità nelle classi immutabili. Ma se non puoi renderne conto, puoi almeno segnalare questo fatto prevenendolo.

È una buona idea rendere una class immutabile anche per motivi di prestazioni. Prendi Integer.valueOf per esempio. Quando si chiama questo metodo statico, non è necessario restituire una nuova istanza Integer. Può restituire un’istanza di sicurezza creata in precedenza con la consapevolezza che quando ti ha passato un riferimento a quell’istanza l’ultima volta non lo hai modificato (immagino che anche questo sia un buon ragionamento da una prospettiva di sicurezza).

Sono d’accordo con la tesi presa in Efficace Java su questi argomenti, ovvero che dovresti progettare le tue classi per l’estensibilità o renderle non estendibili. Se la tua intenzione è di rendere qualcosa estendibile, considera una interfaccia o una class astratta.

Inoltre, non devi fare la finale di class. Puoi rendere privati ​​i costruttori.

Quindi, perché l’immutabilità è una buona ragione per fare una finale di class?

Come affermato nei documenti di Oracle, ci sono fondamentalmente 4 passaggi per rendere una class immutabile.

Quindi uno dei punti afferma che

per rendere una class la class Immutable dovrebbe essere contrassegnata come definitiva o come costruttore privato

Di seguito sono riportati i 4 passaggi per rendere una class immutabile (direttamente dai documenti oracle)

  1. Non fornire metodi “setter” – metodi che modificano campi o oggetti cui fanno riferimento i campi.

  2. Rendi tutti i campi definitivi e privati.

  3. Non consentire alle sottoclassi di sovrascrivere i metodi. Il modo più semplice per farlo è dichiarare la class come definitiva. Un approccio più sofisticato è rendere privato il costruttore e build istanze in metodi di fabbrica.

  4. Se i campi dell’istanza includono riferimenti a oggetti mutabili, non consentire la modifica di tali oggetti:

    • Non fornire metodi che modificano gli oggetti mutabili.
    • Non condividere riferimenti agli oggetti mutabili. Non memorizzare mai riferimenti a oggetti esterni mutabili trasmessi al costruttore; se necessario, creare copie e memorizzare i riferimenti alle copie. Allo stesso modo, crea copie dei tuoi oggetti interni mutabili quando necessario per evitare di restituire gli originali nei tuoi metodi.