Domanda

Nel mio codice, sto creando una raccolta di oggetti a cui accederanno vari thread in modo sicuro solo se gli oggetti sono immutabili. Quando si tenta di inserire un nuovo oggetto nella mia raccolta, voglio verificare se è immutabile (in caso contrario, verrà generata un'eccezione).

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

private static final Set<Class> knownImmutables = new HashSet<Class>(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 dà il 90% della strada, ma a volte i miei utenti vorranno creare i propri tipi immutabili propri:

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; }
}

C'è un modo (forse usando la riflessione) che potrei rilevare in modo affidabile se una classe è immutabile? I falsi positivi (pensare che sia immutabile quando non lo è) non sono accettabili, ma i falsi negativi (pensare che è mutevole quando non lo è) lo sono.

Modificato per aggiungere: Grazie per le risposte perspicaci e utili. Come alcune delle risposte hanno sottolineato, ho trascurato di definire i miei obiettivi di sicurezza. La minaccia qui sono gli sviluppatori all'oscuro: questo è un pezzo di codice quadro che verrà utilizzato da un gran numero di persone che non conoscono quasi nulla del threading e non leggeranno la documentazione. NON ho bisogno di difendermi da sviluppatori malevoli: chiunque sia abbastanza intelligente da mutare un String o eseguire altri shenanigans saranno anche abbastanza intelligenti da sapere che in questo caso non è sicuro. L'analisi statica della base di codice È un'opzione, purché sia ??automatizzata, ma le revisioni del codice non possono essere contate perché non vi è alcuna garanzia che ogni recensione avrà revisori esperti di threading.

È stato utile?

Soluzione

Non esiste un modo affidabile per rilevare se una classe è immutabile. Questo perché ci sono molti modi in cui una proprietà di una classe può essere modificata e non puoi rilevarli tutti tramite la riflessione.

L'unico modo per avvicinarsi a questo è:

  • Consenti solo le proprietà finali dei tipi immutabili (tipi primitivi e classi che sai essere immutabili),
  • Richiede che la classe sia definitiva stessa
  • È necessario che ereditino da una classe base fornita (che è garantita essere immutabile)

Quindi puoi verificare con il seguente codice se l'oggetto 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 per fare ricorso alla superclasse invece di verificare una determinata classe. È stato anche suggerito di usare isImmutable in modo ricorsivo nel metodo isValidFieldType. Questo potrebbe probabilmente funzionare e ho anche fatto alcuni 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 campo hash non è definitivo!). Inoltre, stai incontrando facilmente ricorsioni infinite, causando StackOverflowErrors ;) Altri problemi potrebbero essere causati dai 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, devi prima chiederti se ne vale davvero la pena (anche dal punto di vista delle prestazioni).

Altri suggerimenti

Utilizza il Immutabile annotazione da Java Concurrency in Practice . Lo strumento FindBugs può quindi aiutare a rilevare classi mutabili ma non dovrebbero esserlo.

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

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

Ovviamente dipendi ancora dall'autore che mantiene la sua parola sull'essere immutabile, ma poiché l'autore ha aggiunto esplicitamente l'annotazione è un presupposto ragionevole.

Fondamentalmente no.

Potresti costruire una gigantesca lista bianca di classi accettate, ma penso che il modo meno folle sarebbe semplicemente scrivere nella documentazione per la raccolta che tutto ciò che va è questa raccolta deve essere immutabile.

Modifica: altre persone hanno suggerito di avere un'annotazione immutabile. Questo va bene, ma hai bisogno anche della documentazione. Altrimenti le persone penseranno semplicemente " se inserisco questa annotazione nella mia classe, posso memorizzarla nella raccolta " e lo getterà su qualsiasi classe, immutabile e mutevole allo stesso modo. In effetti, diffiderei di avere un'annotazione immutabile nel caso in cui la gente pensi che l'annotazione rende la loro classe immutabile.

Questo potrebbe essere un altro suggerimento:

Se la classe non ha setter, non può essere mutata, a condizione che i parametri con cui è stata creata siano "primitive" tipi o non mutabili stessi.

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

Domani proverò a programmare qualcosa per te, ma il codice di Simon che usa la riflessione sembra abbastanza buono.

Nel frattempo prova a prendere una copia di " Effective Java " libro di Josh Block, ha un oggetto relativo a questo argomento. Sebbene non sia certo dire come rilevare una classe immutabile, mostra come crearne una buona.

L'articolo si chiama: " Favor immutability "

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

  

Nel mio codice, sto creando una raccolta di oggetti a cui accederanno vari thread in modo sicuro solo se gli oggetti sono immutabili.

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

Supponi di avere questa classe:

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 saltare in aria la tua app. E non è possibile testare questo usando la riflessione, 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 classe, controllare se ognuno di essi è definitivo e anche una classe immutabile, e il gioco è fatto. Non penso che i metodi finali siano un requisito.

Inoltre, fai attenzione a controllare ricorsivamente l'immutabilità dei campi dipendenti, potresti finire con le cerchie:

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 forse anche utilizzabili attraverso alcuni hack di riflessione), ma il codice ricorsivo ingenuo andrà in un ciclo infinito controllando A, quindi B, quindi ancora A, poi su B, ...

Puoi risolverlo 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 dipendenti sono immutabili solo in base a se stessi, ma sarà davvero complicato ...

Puoi chiedere ai tuoi clienti di aggiungere metadati (annotazioni) e controllarli in fase di esecuzione con la riflessione, in questo modo:

Metadata:

@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 riflessione sulla classe, controlla se ha l'annotazione (incollerei il codice ma la sua piastra di caldaia e potrei trovarlo facilmente online)

perché tutte le raccomandazioni richiedono che la classe sia definitiva? se stai usando la riflessione per controllare la classe di ogni oggetto e puoi determinare a livello di codice che quella classe è immutabile (immutabile, campi finali), non è necessario richiedere che la classe stessa sia definitiva.

Puoi utilizzare AOP e @Immutable da jcabi-aspect :

@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 già affermato dagli altri risponditori, IMHO non esiste un modo affidabile per scoprire se un oggetto è davvero immutabile.

Vorrei solo introdurre un'interfaccia " Immutable " per verificare quando si aggiunge. Funziona come un suggerimento che solo gli oggetti immutabili dovrebbero essere inseriti per qualunque motivo lo stai 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 classe per determinare se è immutabile o meno e può essere eseguita in fase di esecuzione. È un aspetto rigoroso, quindi consente anche di autorizzare le classi immutabili conosciute.

Puoi verificarlo su: 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 licenza Apache 2.0.

Scopri joe-e , un'implementazione delle funzionalità per java.

Qualcosa che funziona per un'alta percentuale di classi integrate è il test per esempio di Comparable. Per le classi che non sono immutabili come Date, nella maggior parte dei casi vengono spesso considerate immutabili.

Apprezzo e ammiro la quantità di lavoro che Grundlefleck ha svolto 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 stai solo scrivendo un metodo che determina se una classe è immutabile; invece, dovrai scrivere una classe di rivelatore di immutabilità, perché dovrà mantenere un certo stato. Lo stato del rivelatore sarà l'immutabilità rilevata di tutte le classi che ha esaminato finora. Questo non è solo utile per le prestazioni, ma è effettivamente necessario perché una classe può contenere un riferimento circolare, che causerebbe una ricorsione infinita di un rivelatore di immutabilità semplicistico.

L'immutabilità di una classe ha quattro possibili valori: Unknown , Mutable , Immutable e Calcolo . Probabilmente vorrai avere una mappa che associ ogni classe che hai incontrato finora a un valore di immutabilità. Naturalmente, Unknown non deve essere effettivamente implementato, poiché sarà lo stato implicito di qualsiasi classe che non è ancora nella mappa.

Quindi, quando inizi a esaminare una classe, la associ a un valore Calcolo nella mappa e, quando hai finito, sostituisci Calcolo con Immutabile o Mutable .

Per ogni classe, devi solo controllare i membri del campo, non il codice. L'idea di controllare il bytecode è piuttosto sbagliata.

Prima di tutto, dovresti non verificare se una lezione è definitiva; La finalità di una classe non influisce sulla sua immutabilità. Invece, un metodo che prevede un parametro immutabile dovrebbe prima di tutto invocare il rivelatore di immutabilità per affermare l'immutabilità della classe dell'oggetto reale che è stato passato. Questo test può essere omesso se il tipo di parametro è una classe finale, quindi la finalità è buona per le prestazioni, ma a rigor di termini non è necessario. Inoltre, come vedrai più avanti, un campo il cui tipo è di una classe non finale farà sì che la classe dichiarante sia considerata mutabile, ma è comunque un problema della classe dichiarante, non il problema della non finale classe membro immutabile. Va benissimo avere un'alta gerarchia di classi immutabili, in cui tutti i nodi non foglia devono ovviamente essere non definitivi.

Dovresti non verificare se un campo è privato; è perfettamente bene che una classe abbia un campo pubblico e la visibilità del campo non influisce sull'immutabilità della classe dichiarante in alcun modo, forma o forma. Devi solo verificare se il campo è definitivo e il suo tipo è immutabile.

Quando esamini una classe, quello che vuoi fare prima di tutto è ricorrere per determinare l'immutabilità della sua classe super . Se il super è mutabile, anche il discendente è per definizione mutabile.

Quindi, devi solo controllare i campi dichiarati della classe, non tutti .

Se un campo non è definitivo, la tua classe è modificabile.

Se un campo è definitivo, ma il tipo di campo è mutabile, allora la tua classe è mutabile. (Le matrici sono per definizione mutabili.)

Se un campo è definitivo e il tipo di campo è Calcolo , ignoralo e passa al campo successivo. Se tutti i campi sono immutabili o Calcolo , allora la tua classe è immutabile.

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

Alcune classi possono contenere campi non finali ed essere comunque effettivamente immutabili . Un esempio di ciò è la classe String . Altre classi che rientrano in questa categoria sono le classi che contengono membri non finali a puro scopo di monitoraggio delle prestazioni (contatori di invocazione, ecc.), Le classi che implementano immutabilità dei ghiaccioli (cercalo) e le classi che contengono membri che sono interfacce che non sono noti per causare effetti collaterali. Inoltre, se una classe contiene campi mutabili in buona fede ma promette di non tenerne conto durante il calcolo di hashCode () e equals (), la classe ovviamente non è sicura quando si tratta di multi-threading, ma può ancora 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 sai che una certa classe è effettivamente immutabile, nonostante il fatto che il test di immutabilità non riesca, puoi aggiungere manualmente una voce al tuo rivelatore che la associ 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 definitivo o il suo tipo possa essere mutabile. Il rilevatore può anche verificare la presenza di questa annotazione sulla classe, trattando così la classe come immutabile senza nemmeno preoccuparsi di controllare i suoi campi.

Spero che questo aiuti le generazioni future.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top