Come identifico oggetti immutabili in Java

Nel mio codice, sto creando una collezione di oggetti a cui si accederanno vari thread in un modo che è sicuro solo se gli oggetti sono immutabili. Quando viene effettuato un tentativo di inserire un nuovo object nella mia raccolta, voglio testare per vedere se è immutabile (in caso contrario, farò un’eccezione).

Una cosa che posso fare è controllare alcuni tipi immutabili ben noti:

private static final Set knownImmutables = new HashSet(Arrays.asList( String.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Boolean.class, BigInteger.class, BigDecimal.class )); ... public static boolean isImmutable(Object o) { return knownImmutables.contains(o.getClass()); } 

Questo in realtà mi costa il 90% del tempo, ma a volte i miei utenti vorranno creare semplici tipi immutabili:

 public class ImmutableRectangle { private final int width; private final int height; public ImmutableRectangle(int width, int height) { this.width = width; this.height = height; } public int getWidth() { return width; } public int getHeight() { return height; } } 

Esiste un modo (magari usando la riflessione) per poter rilevare in modo affidabile se una class è immutabile? I falsi positivi (pensando che sia immutabile quando non lo sono) non sono accettabili ma i falsi negativi (pensando che sia mutabile quando non lo è) lo sono.

Modificato per aggiungere: Grazie per le risposte penetranti e utili. Come hanno sottolineato alcune delle risposte, ho trascurato di definire i miei obiettivi di sicurezza. La minaccia qui è che gli sviluppatori sono all’oscuro – questo è un pezzo di codice framework che verrà utilizzato da un gran numero di persone che sanno quasi nulla sul threading e che non leggeranno la documentazione. Non ho bisogno di difendermi dagli sviluppatori malintenzionati – chiunque sia abbastanza abile da mutare una stringa o eseguire altre imbrogli sarà anche abbastanza intelligente da sapere che non è sicuro in questo caso. L’analisi statica del codebase È un’opzione, a condizione che sia automatizzata, ma le revisioni del codice non possono essere contate perché non vi è alcuna garanzia che ogni revisione abbia revisori esperti del threading.

Non esiste un modo affidabile per rilevare se una class è immutabile. Questo perché ci sono tanti modi in cui una proprietà di una class potrebbe essere alterata e non è ansible rilevarli tutti tramite la riflessione.

L’unico modo per avvicinarsi a questo è:

  • Consenti solo proprietà finali di tipi che sono immutabili (tipi primitivi e classi che conosci sono immutabili),
  • Richiedere che la class sia definitiva
  • Richiedere che ereditino da una class base che fornisci (che è garantita per essere immutabile)

Quindi puoi verificare con il seguente codice se l’object che hai è immutabile:

 static boolean isImmutable(Object obj) { Class objClass = obj.getClass(); // Class of the object must be a direct child class of the required class Class superClass = objClass.getSuperclass(); if (!Immutable.class.equals(superClass)) { return false; } // Class must be final if (!Modifier.isFinal(objClass.getModifiers())) { return false; } // Check all fields defined in the class for type and if they are final Field[] objFields = objClass.getDeclaredFields(); for (int i = 0; i < objFields.length; i++) { if (!Modifier.isFinal(objFields[i].getModifiers()) || !isValidFieldType(objFields[i].getType())) { return false; } } // Lets hope we didn't forget something return true; } static boolean isValidFieldType(Class type) { // Check for all allowed property types... return type.isPrimitive() || String.class.equals(type); } 

Aggiornamento: come suggerito nei commenti, potrebbe essere esteso a recurse sulla superclass invece di cercare una certa class. È stato anche suggerito di utilizzare ricorsivamente isIutilizzabile nel metodo isValidFieldType. Probabilmente questo potrebbe funzionare e ho anche fatto dei test. Ma questo non è banale. Non puoi semplicemente controllare tutti i tipi di campo con una chiamata a IsImmutable, perché String ha già fallito questo test (il suo hash campo non è definitivo!). Inoltre puoi facilmente imbatterti in ricursioni infinite, causando StackOverflowErrors 😉 Altri problemi potrebbero essere causati da generici, in cui devi anche controllare i loro tipi per l’immutabilità.

Penso che con un po ‘di lavoro, questi potenziali problemi potrebbero essere risolti in qualche modo. Ma poi, prima devi chiederti se ne vale davvero la pena (anche per quanto riguarda le prestazioni).

Utilizzare l’annotazione Immutable da Java Concurrency in Practice . Lo strumento FindBugs può quindi aiutare a rilevare classi che sono mutabili ma che non dovrebbero essere.

Nella mia azienda abbiamo definito un attributo chiamato @Immutable . Se scegli di assegnarlo a una class, significa che prometti di essere immutabile.

Funziona per la documentazione e nel tuo caso funzionerebbe come filtro.

Ovviamente sei ancora dipendente dall’autore a mantenere la sua parola sull’essere immutabile, ma dal momento che l’autore ha esplicitamente aggiunto l’annotazione è un presupposto ragionevole.

Fondamentalmente no.

Potresti build una gigantesca white-list di classi accettate, ma penso che il modo meno pazzo sarebbe semplicemente scrivere nella documentazione per la raccolta che tutto ciò che va è che questa collezione deve essere immutabile.

Modifica: altre persone hanno suggerito di avere un’annotazione immutabile. Questo va bene, ma hai bisogno anche della documentazione. Altrimenti la gente penserà semplicemente “se metto questa annotazione sulla mia class posso conservarla nella collezione” e la butterò semplicemente su tutte le classi, immutabili e mutevoli allo stesso modo. In effetti, sarei diffidente nell’avere un’annotazione immutabile nel caso in cui la gente pensi che l’annotazione renda immutabile la loro class.

Questo potrebbe essere un altro suggerimento:

Se la class non ha setter, non può essere mutata, a condizione che i parametri con cui è stata creata siano tipi “primitivi” o non mutabili essi stessi.

Inoltre, nessun metodo può essere superato, tutti i campi sono finali e privati,

Proverò a codificare qualcosa domani per te, ma il codice di Simon che usa la riflessione sembra abbastanza buono.

Nel frattempo, prova ad afferrare una copia del libro “Effective Java” di Josh Block, ha un object relativo a questo argomento. Mentre non è sicuro dire come rilevare una class immutabile, mostra come crearne una buona.

L’object si chiama: “Favor immutabilità”

link: http://java.sun.com/docs/books/effective/

Nel mio codice, sto creando una collezione di oggetti a cui si accederanno vari thread in un modo che è sicuro solo se gli oggetti sono immutabili.

Non una risposta diretta alla tua domanda, ma tieni presente che gli oggetti che sono immutabili non sono automaticamente garantiti come thread-safe (purtroppo). Il codice deve essere privo di effetti collaterali per essere thread-safe, e questo è un po ‘più difficile.

Supponi di avere questa class:

 class Foo { final String x; final Integer y; ... public bar() { Singleton.getInstance().foolAround(); } } 

Quindi il metodo foolAround() potrebbe includere alcune operazioni non thread-safe, che faranno esplodere la tua app. E non è ansible testare questo usando il reflection, poiché il riferimento effettivo può essere trovato solo nel corpo del metodo, non nei campi o nell’interfaccia esposta.

A parte questo, gli altri sono corretti: puoi cercare tutti i campi dichiarati della class, controllare se ognuno di essi è definitivo e anche una class immutabile, e il gioco è fatto. Non credo che i metodi definitivi siano un requisito.

Inoltre, fai attenzione al controllo ricorsivo dei campi dipendenti per l’immutabilità, potresti finire con cerchi:

 class A { final B b; // might be immutable... } class B { final A a; // same so here. } 

Le classi A e B sono perfettamente immutabili (e possibilmente utilizzabili anche attraverso alcuni hack di riflessione), ma il codice ricorsivo ingenuo entrerà in un ciclo infinito controllando A, poi B, poi A ancora, in avanti a B, …

Puoi sistemarlo con una mappa ‘vista’ che non consente cicli o con un codice davvero intelligente che decide che le classi sono immutabili se tutti i loro depenati sono immutabili solo a seconda di loro stessi, ma sarà davvero complicato …

Puoi chiedere ai tuoi clienti di aggiungere metadati (annotazioni) e controllarli in fase di runtime con reflection, come questo:

Metadati:

 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.CLASS) public @interface Immutable{ } 

Codice cliente:

 @Immutable public class ImmutableRectangle { private final int width; private final int height; public ImmutableRectangle(int width, int height) { this.width = width; this.height = height; } public int getWidth() { return width; } public int getHeight() { return height; } } 

Quindi, usando la reflection sulla class, controlla se ha l’annotazione (io incollerei il codice ma il suo boilerplate e può essere trovato facilmente online)

perché tutte le raccomandazioni richiedono che la lezione sia definitiva? se si utilizza reflection per verificare la class di ciascun object e si può determinare a livello di codice che quella class è immutabile (campi finali immutabili), non è necessario richiedere che la class stessa sia definitiva.

Puoi utilizzare le annotazioni AOP e @Immutable dagli aspetti di jcabi :

 @Immutable public class Foo { private String data; } // this line will throw a runtime exception since class Foo // is actually mutable, despite the annotation Object object = new Foo(); 

Come gli altri rispondenti hanno già detto, IMHO non esiste un modo affidabile per scoprire se un object è davvero immutabile.

Vorrei solo introdurre un’interfaccia “Immutabile” da verificare in fase di aggiunta. Questo funziona come suggerimento che solo gli oggetti immutabili dovrebbero essere inseriti per qualsiasi ragione tu lo stia facendo.

 interface Immutable {} class MyImmutable implements Immutable{...} public void add(Object o) { if (!(o instanceof Immutable) && !checkIsImmutableBasePrimitive(o)) throw new IllegalArgumentException("o is not immutable!"); ... } 

Prova questo:

 public static boolean isImmutable(Object object){ if (object instanceof Number) { // Numbers are immutable if (object instanceof AtomicInteger) { // AtomicIntegers are mutable } else if (object instanceof AtomicLong) { // AtomLongs are mutable } else { return true; } } else if (object instanceof String) { // Strings are immutable return true; } else if (object instanceof Character) { // Characters are immutable return true; } else if (object instanceof Class) { // Classes are immutable return true; } Class objClass = object.getClass(); // Class must be final if (!Modifier.isFinal(objClass.getModifiers())) { return false; } // Check all fields defined in the class for type and if they are final Field[] objFields = objClass.getDeclaredFields(); for (int i = 0; i < objFields.length; i++) { if (!Modifier.isFinal(objFields[i].getModifiers()) || !isImmutable(objFields[i].getType())) { return false; } } // Lets hope we didn't forget something return true; } 

Per quanto ne so, non c’è modo di identificare oggetti immutabili che siano corretti al 100%. Tuttavia, ho scritto una biblioteca per avvicinarti. Esegue l’analisi del bytecode di una class per determinare se è immutabile o meno e può essere eseguito in fase di esecuzione. È un aspetto rigido, quindi consente anche di annotare classificazioni immutabili note.

Puoi verificarlo su: http://www.mutabilitydetector.org

Ti permette di scrivere codice come questo nella tua applicazione:

 /* * Request an analysis of the runtime class, to discover if this * instance will be immutable or not. */ AnalysisResult result = analysisSession.resultFor(dottedClassName); if (result.isImmutable.equals(IMMUTABLE)) { /* * rest safe in the knowledge the class is * immutable, share across threads with joyful abandon */ } else if (result.isImmutable.equals(NOT_IMMUTABLE)) { /* * be careful here: make defensive copies, * don't publish the reference, * read Java Concurrency In Practice right away! */ } 

È gratuito e open source con la licenza Apache 2.0.

Scopri joe-e , un’implementazione di funzionalità per java.

Qualcosa che funziona per un’alta percentuale di classi builtin è test per l’istanza di Comparable. Per le classi che non sono immutabili come Date, nella maggior parte dei casi sono spesso considerate immutabili.

Apprezzo e ammiro la quantità di lavoro che Grundlefleck ha inserito nel suo rilevatore di mutabilità, ma penso che sia un po ‘eccessivo. Puoi scrivere un rilevatore semplice ma praticamente molto adeguato (cioè pragmatico ) come segue:

(nota: questa è una copia del mio commento qui: https://stackoverflow.com/a/28111150/773113 )

Prima di tutto, non starai semplicemente scrivendo un metodo che determina se una class è immutabile; invece, dovrai scrivere una class di rilevatore di immutabilità, perché dovrà mantenere uno stato. Lo stato del rivelatore sarà l’immutabilità rilevata di tutte le classi che ha esaminato finora. Ciò non è utile solo per le prestazioni, ma è effettivamente necessario perché una class può contenere un riferimento circolare, che farebbe cadere in ricorsione infinita un rilevatore di immutabilità semplicistico.

L’immutabilità di una class ha quattro valori possibili: Unknown , Mutable , Immutable e Calculating . Probabilmente vorrai avere una mappa che associa ogni class che hai incontrato fino ad un valore di immutabilità. Ovviamente, Unknown non ha realmente bisogno di essere implementato, poiché sarà lo stato implicito di qualsiasi class che non è ancora nella mappa.

Quindi, quando inizi a esaminare una class, la associ a un valore Calculating nella mappa e quando hai finito, sostituisci Calculating con Immutable o Mutable .

Per ogni class, devi solo controllare i membri del campo, non il codice. L’idea di controllare bytecode è piuttosto fuorviante.

Prima di tutto, non dovresti controllare se una class è definitiva; La finalità di una class non influisce sulla sua immutabilità. Invece, un metodo che si aspetta un parametro immutabile dovrebbe prima di tutto invocare il rilevatore di immutabilità per affermare l’immutabilità della class dell’object reale che è stato passato. Questo test può essere omesso se il tipo del parametro è una class finale, quindi la finalità è buona per le prestazioni, ma in senso stretto non è necessaria. Inoltre, come vedrai più in basso, un campo il cui tipo è di una class non finale farà sì che la class dichiarante sia considerata come mutabile, ma ancora, questo è un problema della class dichiarante, non il problema della non-finale membro immutabile. È perfettamente bene avere un’alta gerarchia di classi immutabili, in cui tutti i nodes non fogliari devono ovviamente essere non-finali.

Non dovresti controllare se un campo è privato; è perfetto per una class avere un campo pubblico, e la visibilità del campo non influenza l’immutabilità della class dichiarante in alcun modo, forma o forma. Hai solo bisogno di verificare se il campo è definitivo e il suo tipo è immutabile.

Quando si esamina una class, ciò che si vuole fare prima di tutto è recitare per determinare l’immutabilità della sua super class. Se il super è mutabile, anche il discendente è per definizione mutabile.

Quindi, è sufficiente controllare i campi dichiarati della class, non tutti i campi.

Se un campo non è definitivo, la tua class è mutabile.

Se un campo è definitivo, ma il tipo di campo è mutabile, allora la tua class è mutabile. (Gli array sono per definizione mutabili).

Se un campo è definitivo e il tipo di campo è Calculating , ignorarlo e passare al campo successivo. Se tutti i campi sono immutabili o Calculating , la tua class è immutabile.

Se il tipo del campo è un’interfaccia, una class astratta o una class non finale, allora deve essere considerato come mutabile, poiché non si ha assolutamente alcun controllo su ciò che l’effettiva implementazione potrebbe fare. Questo potrebbe sembrare un problema insormontabile, perché significa che il wrapping di una raccolta modificabile all’interno di UnmodifiableCollection fallirà il test di immutabilità, ma in realtà è soddisfacente e può essere gestito con la seguente soluzione alternativa.

Alcune classi possono contenere campi non finali e comunque essere effettivamente immutabili . Un esempio di questo è la class String . Altre classi che rientrano in questa categoria sono classi che contengono membri non finali puramente per scopi di monitoraggio delle prestazioni (contatori di invocazione, ecc.), Classi che implementano l’ immutabilità di popsicle (cercare) e classi che contengono membri che sono interfacce che sono note non causare alcun effetto collaterale Inoltre, se una class contiene campi mutabili in buona fede ma promette di non tenerne conto quando si compila hashCode () ed equals (), allora la class è ovviamente non sicura quando si parla di multi-threading, ma può comunque essere considerata come immutabile allo scopo di usarlo come chiave in una mappa. Quindi, tutti questi casi possono essere gestiti in due modi:

  1. Aggiunta manuale di classi (e interfacce) al rilevatore di immutabilità. Se si sa che una determinata class è effettivamente immutabile nonostante il test di immutabilità non riesca, è ansible aggiungere manualmente una voce al rilevatore che la associa a Immutable . In questo modo, il rilevatore non tenterà mai di verificare se è immutabile, dirà sempre “Sì, lo è”.

  2. Presentazione di un’annotazione @ImmutabilityOverride . Il rilevatore di immutabilità può verificare la presenza di questa annotazione su un campo e, se presente, può trattare il campo come immutabile, nonostante il fatto che il campo possa essere non finalizzato o che il suo tipo possa essere mutabile. Il rilevatore può anche verificare la presenza di questa annotazione sulla class, trattando così la class come immutabile senza nemmeno preoccuparsi di controllarne i campi.

Spero che questo aiuti le generazioni future.