2010-05-17 15 views
5

Spesso si legge di oggetti immutabili che richiedono che i campi finali siano immutabili in Java. È questo il caso, o è semplicemente sufficiente per non avere mutabilità pubblica e non mutare effettivamente lo stato?I campi devono essere esplicitamente definitivi per avere un oggetto "corretto" immutabile?

Ad esempio, se si dispone di un oggetto immutabile creato dal modello di builder, è possibile farlo assegnando al builder i singoli campi mentre vengono creati, oppure mantenendo il campo stesso e restituendo infine l'oggetto immutabile passando i valori al suo costruttore (privato).

Avere i campi finali ha l'ovvio vantaggio di impedire errori di implementazione (come ad esempio consentire al codice di conservare un riferimento al builder e "costruire" l'oggetto più volte mentre in realtà muta un oggetto esistente), ma avendo l'archivio Builder i suoi dati all'interno dell'oggetto così come sono costruiti sembrano essere DRYer.

Quindi la domanda è: supponendo che il Builder non perda l'oggetto in anticipo e si fermi da modificare l'oggetto una volta costruito (ad esempio impostando il suo riferimento all'oggetto come null) ci sia effettivamente qualcosa ottenuto (come una migliore sicurezza del thread) nella "immutabilità" dell'oggetto se invece i campi dell'oggetto sono stati resi definitivi?

+1

Se non sbaglio * final * può anche aiutare a prevenire gli "attacchi di riflessione" in cui un programmatore canaglia usa la riflessione per accedere ai campi della classe e modificare il loro contenuto. C'era un famoso esempio che pervertiva completamente la classe * String * e mostrava che in molti casi * String * poteva essere effettivamente modificato (se non fosse dovuto a non-finalness btw ma a causa del fatto che una volta il sottostante * char [] * è stato utilizzato se era "game over" perché non si può forzare l'immutabilità sul contenuto di una matrice ... Ma non è questo il mio punto: il mio punto è che la riflessione può aiutare a fare cose davvero cattive). – TacticalCoder

+1

@user, di fronte a una riflessione del genere, non è possibile stabilire l'immutabilità. Tuttavia, il Security Manager sta facendo funzionare il codice di terze parti come tale. – Yishai

+0

wait ... Se ho una * final * class avente un * int finale unico * è possibile modificarlo usando il reflection? Il punto era proprio che * senza * usando * final * non si può prevenire contro gli attacchi di reflection (a meno che non si abbia un * Security Manager * in posizione, ma non è quasi mai il caso, motivo per cui ho scritto * "in molti casi può essere modificato "* ...). Tuttavia non sono sicuro che tu possa usare il reflection per modificare un * final int * ... Se non puoi, allora il mio commento è abbastanza in discussione con la tua domanda:) (Ho fatto +1 la tua domanda ieri btw:) – TacticalCoder

risposta

6

Sì, si ottiene "sicurezza filo" dai campi final. Vale a dire, il valore assegnato a un campo final durante la costruzione è garantito per essere visibile a tutti i thread. L'altra alternativa per la sicurezza delle filettature è dichiarare i campi volatile, ma in questo caso si incorre in un sovraccarico elevato con ogni lettura & hellip; e confondere chiunque guarda la tua classe e si chiede perché i campi di questa classe "immutabile" sono contrassegnati come "volatili".

Contrassegnare i campi final è il più corretto tecnicamente e trasmette il tuo intento in modo più chiaro. Sfortunatamente, rende molto complicato il modello di builder. Penso che dovrebbe essere possibile creare un processore di annotazione per sintetizzare un costruttore per una classe immutabile, proprio come fa Project Lombok con setter e getter. Il vero lavoro sarebbe il supporto IDE necessario per poter codificare i costruttori che non esistono realmente.

+0

Perché è necessario 'volatile '? – curiousguy

+0

@curiousguy: 'volatile' garantisce che il valore scritto da una discussione sia visibile a un'altra. Senza di esso, un lettore, ad esempio, potrebbe non cancellare un valore memorizzato nella cache di un registro e fare una lettura alla memoria principale. – erickson

+0

Vedere la mia risposta ai commenti. – curiousguy

2

Un oggetto può certamente avere campi privati ​​mutabili e funziona ancora come un oggetto immutabile. Tutto ciò che conta per soddisfare il contratto di immutabilità è che l'oggetto appare immutabile dall'esterno. Un oggetto con campi privati ​​non finali, ma nessun setter, ad esempio, soddisferà questo requisito.

Infatti, se il tuo incapsulamento è corretto, puoi effettivamente mutare lo stato interno e continuare a funzionare correttamente come un oggetto "immutabile". Un esempio potrebbe essere una sorta di valutazione pigra o memorizzazione nella cache delle strutture di dati.

Clojure ad esempio esegue questa implementazione interna di sequenze lazy, questi oggetti si comportano come se fossero immutabili ma solo in realtà calcolano e memorizzano i valori futuri quando vengono richiesti direttamente. Qualsiasi richiesta successiva recupera il valore memorizzato.

Tuttavia, aggiungerei come un avvertimento che il numero di luoghi in cui si vorrebbe effettivamente modificare l'interno di un oggetto immutabile è probabilmente piuttosto raro. In caso di dubbio, rendili definitivi.

+1

"Un oggetto con campi privati ​​non finali ma nessun setter ad esempio soddisferebbe questo requisito" - Questo di per sé non garantisce l'immutabilità. I getter possono restituire i riferimenti ai membri dei dati e questi possono essere modificati dal chiamante. –

+0

@Eyal: true - anche se i riferimenti stessi sarebbero immutabili. Se costruissi l'oggetto con membri immutabili, allora l'immutabilità sarebbe rimasta fino in fondo. In alternativa, la creazione di una composizione o di una collezione immutabili di oggetti mutabili ha senso in alcuni contesti. – mikera

+0

Dovrebbe aggiungere - diverse persone hanno definizioni leggermente diverse di immutabilità ... la mia è "immutabilità dal punto di vista dell'osservatore" che ritengo sia la più utile ma ho anche sentito persone definire l'immutabilità come assolutamente nessun cambiamento permesso. – mikera

0

Penso che dovresti solo considerare l'ambiente in cui è in esecuzione e decidere se i quadri che usano la riflessione per manipolare gli oggetti sono un pericolo.

Uno potrebbe facilmente creare uno scenario strano in cui un oggetto apparentemente immutabile viene danneggiato da un attacco di iniezione POST a causa di un framework di associazione Web configurato per l'uso di reflection anziché di bean setter.

0

Si può sicuramente avere un oggetto immutabile con campi non finali.

Ad esempio, vedere l'implementazione java 1.6 di java.lang.String.

+0

@ user988052 Non interrompe l'integrità della classe e la sicurezza del programma? – curiousguy

+0

@TacticalCoder - Anche i campi finali possono essere modificati tramite la riflessione. Quindi l'argomento che si può avere "stringhe non immutabili" è semplicemente falso, a meno che non si sia anche disposti a dire che è impossibile avere un oggetto immutabile in Java. La classe String, in base alla progettazione, è immutabile. Reflection side-steps the design of a class e in realtà non è rilevante nel contesto della domanda originale, né la risposta di questo poster. – Scrubbie

+0

@TacticalCoder. Perché -1. Pensi che String sia mutabile? Hai visto la sua implementazione in 1.6? Come in ogni filosofia di progettazione, entrambi si fidano dei vostri subappaltatori, e voi stessi, per far funzionare apparecchiature e sottoparti in base alle specifiche operative. Se inizi a operare al di fuori di queste specifiche (come usare la riflessione su oggetti immutabili) il risultato finale potrebbe essere simile a Chernobyl -> http://en.wikipedia.org/wiki/Chernobyl_disaster. –

0

Commento: @erickson

Come quella:

 
class X { volatile int i, j; } 
X y; 

// thread A: 
X x = new X; 
x.i = 1; 
x.j = 2; 
y = x; 

// thread B: 
if (y != null) { 
    a = y.i; 
    b = y.j; 
} 

?

+2

Il thread B non è garantito per vedere che 'y' è stato assegnato; anche se "y" può essere assegnato "prima" B lo legge, secondo un orologio sul muro, non c'è alcuna barriera di memoria, quindi B potrebbe leggere un valore scaduto di "null" da "y". Qui, se hai rimosso 'volatile' da' i' & 'j', e lo hai messo su' y', invece, B vedrebbe valori "correnti" per 'i',' j' e 'y'. Questo perché scrivere su un campo volatile scarica le scritture su qualsiasi campo nella memoria principale e la lettura di una variabile volatile aggiorna i valori memorizzati nella cache dalla memoria principale. – erickson

Problemi correlati