Question

Dans mon code, je crée une collection d'objets qui seront accessibles via différents threads de manière sécurisée uniquement si les objets sont immuables. Lorsqu’on essaie d’insérer un nouvel objet dans ma collection, je souhaite vérifier s’il est immuable (sinon, je lève une exception).

Une chose que je peux faire est de vérifier quelques types immuables bien connus:

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

Cela représente en fait 90% du chemin, mais parfois mes utilisateurs voudront créer leurs propres types immuables:

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

Existe-t-il un moyen (peut-être en utilisant la réflexion) de détecter de manière fiable si une classe est immuable? Les faux positifs (pensant que c'est immuable quand ça ne l'est pas) ne sont pas acceptables, mais les faux négatifs (pensant que c'est mutable quand ça ne l'est pas) le sont.

Modifié pour ajouter: Merci pour vos réponses perspicaces et utiles. Comme certaines des réponses l'ont souligné, j'ai négligé de définir mes objectifs de sécurité. La menace ici, ce sont les développeurs ignorants. Il s'agit d'un élément de code-cadre qui sera utilisé par un grand nombre de personnes qui connaissent pratiquement rien à propos des threads et ne liront pas la documentation. Je n'ai PAS besoin de me défendre contre des développeurs malveillants - quiconque assez intelligent pour modifier un Chaîne ou effectuer d'autres manigances sera également assez intelligent pour savoir que ce n'est pas sans danger dans ce cas. L’analyse statique de la base de code EST une option, dans la mesure où elle est automatisée, mais on ne peut pas compter sur les révisions de code car rien ne garantit que chaque critique comportera des réviseurs avertis en matière de threading.

Était-ce utile?

La solution

Il n’existe aucun moyen fiable de détecter si une classe est immuable. En effet, il existe de nombreuses façons de modifier une propriété d'une classe et vous ne pouvez pas toutes les détecter via la réflexion.

La seule façon de vous en approcher est la suivante:

  • N'autorisez que les propriétés finales des types immuables (les types primitifs et les classes que vous savez immuables),
  • Exiger que la classe soit finale elle-même
  • Exige qu'ils héritent d'une classe de base que vous fournissez (qui est garantie immuable)

Ensuite, vous pouvez vérifier avec le code suivant si l'objet que vous possédez est immuable:

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

Mise à jour: Comme suggéré dans les commentaires, il pourrait être étendu pour effectuer une récurrence sur la superclasse au lieu de rechercher une certaine classe. Il a également été suggéré d’utiliser récursivement isImmutable dans la méthode isValidFieldType. Cela pourrait probablement fonctionner et j'ai également fait des tests. Mais ce n'est pas anodin. Vous ne pouvez pas simplement vérifier tous les types de champs avec un appel à isImmutable, car String échoue déjà à ce test (son champ hash n'est pas définitif!). De plus, vous rencontrez facilement des récursions sans fin, causant des StackOverflowErrors ;). D'autres problèmes peuvent être causés par des génériques, pour lesquels vous devez également vérifier leur type pour déterminer leur immuabilité.

Je pense qu'avec un peu de travail, ces problèmes potentiels pourraient être résolus d'une manière ou d'une autre. Mais ensuite, vous devez vous demander d’abord si cela en vaut vraiment la peine (également en termes de performances).

Autres conseils

Utilisez le Annulation immuable de La concurrence de Java en pratique . L’outil FindBugs peut alors aider à détecter les classes qui sont mutables mais ne devraient pas l'être.

Dans mon entreprise, nous avons défini un attribut appelé @Immutable . Si vous choisissez de l’attacher à une classe, cela signifie que vous promettez d’être immuable.

Cela fonctionne pour la documentation et dans votre cas, il fonctionnerait comme un filtre.

Bien sûr, vous attendez toujours de l'auteur qu'il tienne parole d'être immuable, mais puisque l'auteur a explicitement ajouté l'annotation, il s'agit d'une hypothèse raisonnable.

Fondamentalement non.

Vous pourriez créer une liste blanche géante de classes acceptées, mais je pense que la façon la moins folle de faire serait simplement d'écrire dans la documentation de la collection que tout ce qui se passe est que cette collection doit être immuable.

Modifier: d'autres personnes ont suggéré l'utilisation d'une annotation immuable. C'est bien, mais vous avez également besoin de la documentation. Sinon, les gens vont simplement penser: "si je mets cette annotation sur ma classe, je peux la stocker dans la collection". et je vais juste le jeter sur n'importe quoi, les classes immuables et mutables de la même manière. En fait, je me méfierais d'une annotation immuable, au cas où les gens penseraient que l'annotation rend leur classe immuable.

Cela pourrait être un autre indice:

Si la classe n'a pas de setters, elle ne peut pas être mutée, car les paramètres avec lesquels elle a été créée sont soit "primitive". types ou non mutables eux-mêmes.

Aucune méthode ne peut être remplacée, tous les champs sont finaux et privés,

Je vais essayer de coder quelque chose demain, mais le code de Simon utilisant la réflexion est plutôt beau.

En attendant, essayez de vous procurer une copie du fichier "Effective Java". livre de Josh Block, il comporte un élément lié à ce sujet. Bien qu’on ne sache pas exactement comment détecter une classe immuable, elle montre comment en créer une bonne.

L'élément s'appelle: "Favoriser l'immuabilité"

.

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

  

Dans mon code, je crée une collection d'objets qui seront accessibles via différents threads de manière sécurisée uniquement si les objets sont immuables.

Ce n’est pas une réponse directe à votre question, mais gardez à l’esprit que les objets immuables ne sont pas automatiquement garantis comme étant thread-safe (malheureusement). Le code doit être libre d'effets secondaires pour être thread-safe, et c'est un peu plus difficile.

Supposons que vous ayez cette classe:

class Foo {
  final String x;
  final Integer y;
  ...

  public bar() {
    Singleton.getInstance().foolAround();
  }
}

Ensuite, la méthode foolAround () peut inclure des opérations ne prenant pas en compte les threads, ce qui fera exploser votre application. Et il n’est pas possible de tester cela en utilisant la réflexion, car la référence réelle ne peut être trouvée que dans le corps de la méthode, pas dans les champs ou l’interface exposée.

En dehors de cela, les autres sont corrects: vous pouvez analyser tous les champs déclarés de la classe, vérifier si chacun d'entre eux est final et également une classe immuable, et le tour est joué. Je ne pense pas que les méthodes soient définitives est une exigence.

Veillez également à vérifier de manière récursive l'immutabilité des champs dépendants. Vous pourriez vous retrouver avec des cercles:

class A {
  final B b; // might be immutable...
}

class B {
  final A a; // same so here.
}

Les classes A et B sont parfaitement immuables (et peuvent même être utilisées avec des piratages de réflexion), mais le code récursif naïf entrera dans une boucle sans fin vérifiant A, puis B, puis à nouveau, puis vers B, ...

Vous pouvez résoudre ce problème avec une carte "visible" qui interdit les cycles ou avec un code très intelligent qui décide que les classes sont immuables si toutes leurs personnes dépendantes ne dépendent que d'eux-mêmes, mais cela va être vraiment compliqué ...

Vous pouvez demander à vos clients d'ajouter des métadonnées (annotations) et de les vérifier au moment de l'exécution avec réflexion, comme suit:

Métadonnées:

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

Code client:

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

Ensuite, en utilisant la réflexion sur la classe, vérifiez si elle possède l'annotation (je collerais le code mais son passe-partout et on peut le trouver facilement en ligne)

pourquoi toutes les recommandations exigent-elles que la classe soit finale? si vous utilisez la réflexion pour vérifier la classe de chaque objet et que vous pouvez déterminer par programme que cette classe est immuable (champs définitifs immuables), vous n'avez pas besoin d'exiger que la classe elle-même soit finale.

Vous pouvez utiliser AOP et la @Immutable de jcabi-aspects :

@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();

Comme les autres répondants l'ont déjà dit, à mon humble avis, il n'existe aucun moyen fiable de savoir si un objet est vraiment immuable.

Je voudrais simplement introduire une interface "immuable". à vérifier lors de l'ajout. Cela fonctionne comme un indice selon lequel seuls les objets immuables doivent être insérés, quelle que soit la raison pour laquelle vous le faites.

interface Immutable {}

class MyImmutable implements Immutable{...}

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

Essayez ceci:

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

À ma connaissance, il n’existe aucun moyen d’identifier des objets immuables corrects à 100%. Cependant, j'ai écrit une bibliothèque pour vous rapprocher. Il effectue une analyse du bytecode d'une classe pour déterminer si elle est immuable ou non et si elle peut être exécutée au moment de l'exécution. C’est du côté strict, donc cela permet également d’inscrire en liste blanche les classes immuables connues.

Vous pouvez le vérifier à l'adresse suivante: www.mutabilitydetector.org

Cela vous permet d'écrire du code comme celui-ci dans votre application:

/*
* 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!
    */
}

Il est gratuit et à code source ouvert sous la licence Apache 2.0.

Découvrez joe-e , une implémentation des fonctionnalités pour Java.

Quelque chose qui fonctionne pour un pourcentage élevé de classes intégrées est test par exemple de Comparable. Pour les classes qui ne sont pas immuables comme Date, elles sont souvent traitées comme immuables dans la plupart des cas.

J'apprécie et admire le travail que Grundlefleck a consacré à son détecteur de mutabilité, mais je pense que c'est un peu excessif. Vous pouvez écrire un détecteur simple mais pratiquement très adéquat (c'est-à-dire pragmatique ) comme suit:

(note: ceci est une copie de mon commentaire ici: https://stackoverflow.com/a/28111150/773113)

Tout d’abord, vous n’allez pas écrire une méthode qui détermine si une classe est immuable; au lieu de cela, vous devrez écrire une classe de détecteurs d'immutabilité, car il va falloir maintenir un certain état. L'état du détecteur sera l'immuabilité détectée de toutes les classes qu'il a examinées jusqu'à présent. Ce n'est pas seulement utile pour les performances, mais c'est vraiment nécessaire, car une classe peut contenir une référence circulaire, ce qui ferait tomber un détecteur d'immutabilité simpliste dans une récursion infinie.

L'immuabilité d'une classe a quatre valeurs possibles: inconnu , mutable , immuable et Calculant . Vous voudrez probablement avoir une carte qui associe chaque classe que vous avez rencontrée jusqu'à une valeur d'immutabilité. Bien entendu, Unknown n'a pas réellement besoin d'être implémenté, car ce sera l'état implicite de toute classe qui ne figure pas encore dans la carte.

Ainsi, lorsque vous commencez à examiner une classe, vous l'associez à une valeur Calculating dans la carte et lorsque vous avez terminé, vous remplacez Calculating par Immutable ou Mutable .

Pour chaque classe, il vous suffit de vérifier les membres du champ, pas le code. L’idée de vérifier le bytecode est plutôt erronée.

Tout d'abord, vous non devez vérifier si une classe est finale; La finalité d'une classe n'affecte pas son immutabilité. Au lieu de cela, une méthode qui attend un paramètre immuable doit d'abord appeler le détecteur d'immutabilité pour affirmer l'immutabilité de la classe de l'objet qui a été passé. Ce test peut être omis si le type du paramètre est une classe finale. La finalité est donc bonne pour les performances, mais à proprement parler pas nécessaire. En outre, comme vous le verrez plus loin, un champ dont le type est celui d'une classe non finale fera en sorte que la classe déclarante soit considérée comme mutable, mais il s'agit tout de même d'un problème de la classe déclarante et non du problème de la classe non finale. classe de membre immuable. Il est parfaitement approprié d’avoir une haute hiérarchie de classes immuables, dans laquelle tous les nœuds non-feuilles doivent bien sûr être non finaux.

Vous ne devez pas vérifier si un champ est privé; il est parfaitement correct pour une classe d'avoir un champ public, et la visibilité du champ n'affecte en aucun cas l'immuabilité de la classe déclarante. Il vous suffit de vérifier si le champ est final et son type immuable.

Lorsque vous examinez une classe, ce que vous voulez tout d’abord faire est de déterminer si l’immuabilité de sa classe super est immuable. Si le super est modifiable, le descendant est également modifiable par définition.

Ensuite, il vous suffit de vérifier les champs déclarés de la classe, et non tous .

Si un champ n'est pas final, votre classe peut être modifiée.

Si un champ est final, mais que le type du champ est modifiable, votre classe est modifiable. (Les tableaux sont par définition mutables.)

Si un champ est final et que le type du champ est Calculant , ignorez-le et passez au champ suivant. Si tous les champs sont soit immuables soit Calculating , votre classe est immuable.

Si le type du champ est une interface, une classe abstraite ou une classe non finale, il doit être considéré comme mutable, car vous n'avez absolument aucun contrôle sur ce que l'implémentation réelle peut faire. Cela peut sembler un problème insurmontable, car cela signifie que le fait d’envelopper une collection modifiable dans une UnmodifiableCollection échouera toujours le test d’immutabilité, mais c’est correct, et il peut être géré avec la solution de contournement suivante:

Certaines classes peuvent contenir des champs non finaux et rester effectivement immuables . Un exemple de ceci est la classe String . Les autres classes qui entrent dans cette catégorie sont les classes qui contiennent des membres non finaux uniquement à des fins de surveillance des performances (compteurs d'invocation, etc.), les classes qui implémentent l'immuabilité du popsicle (recherche), et les classes qui contiennent membres qui sont des interfaces connues pour ne causer aucun effet secondaire. De plus, si une classe contient des champs mutables authentiques mais promet de ne pas les prendre en compte lors du calcul de hashCode () et equals (), la classe est bien sûr dangereuse en multi-threading, mais elle peut toujours être considérée comme immuable dans le but de l'utiliser comme une clé dans une carte. Tous ces cas peuvent donc être traités de l’une des deux manières suivantes:

  1. Ajouter manuellement des classes (et des interfaces) à votre détecteur d'immutabilité. Si vous savez qu'une certaine classe est effectivement immuable, malgré le fait que le test d'immutabilité pour elle échoue, vous pouvez ajouter manuellement une entrée à votre détecteur qui l'associe à Immutable . De cette façon, le détecteur ne tentera jamais de vérifier s’il est immuable, il dira toujours «oui, c’est vrai».

  2. Introduction d'une annotation @ImmutabilityOverride . Votre détecteur d’immutabilité peut vérifier la présence de cette annotation sur un champ et, s’il est présent, il peut considérer le champ comme immuable, bien que le champ puisse être non final ou que son type soit mutable. Le détecteur peut également vérifier la présence de cette annotation sur la classe, la traitant ainsi comme immuable, sans même prendre la peine de vérifier ses champs.

J'espère que cela aidera les générations futures.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top