47 votes

Comment identifier les objets immuables en Java

Dans mon code, je crée une collection d'objets à laquelle les différents threads auront accès d'une manière qui n'est sûre que si les objets sont immuables. Lorsqu'on tente d'insérer un nouvel objet dans ma collection, je veux 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 me permet de faire 90 % du chemin, mais il arrive que mes utilisateurs veuillent créer eux-mêmes des types immuables simples :

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 (penser qu'elle est immuable alors qu'elle ne l'est pas) ne sont pas acceptables, mais les faux négatifs (penser qu'elle est mutable alors qu'elle ne l'est pas) le sont.

Modifié pour ajouter : Merci pour ces 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 est celle des développeurs ignorants - il s'agit d'un morceau de code de framework qui sera utilisé par un grand nombre de personnes qui ne connaissent pratiquement rien au threading et ne liront pas la documentation. Je n'ai PAS besoin de me défendre contre les développeurs malveillants -- toute personne suffisamment intelligente pour muter une chaîne de caractères ou faire d'autres manigances seront aussi assez intelligents pour savoir que ce n'est pas sûr dans ce cas. L'analyse statique de la base de code EST une option, tant qu'elle est automatisée, mais on ne peut pas compter sur les revues de code car il n'y a aucune garantie que les réviseurs de chaque revue soient sensibilisés au threading.

31voto

Simon Lehmann Points 5442

Il n'existe aucun moyen fiable de détecter si une classe est immuable. Cela est dû au fait qu'il existe de nombreuses façons de modifier une propriété d'une classe et qu'il est impossible de toutes les détecter par réflexion.

Le seul moyen de s'en approcher est :

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

Ensuite, vous pouvez vérifier avec le code suivant si l'objet que vous avez 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 récurer sur la superclasse au lieu de vérifier 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 quelques tests. Mais ce n'est pas trivial. 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 final !). De plus, vous vous exposez facilement à des récursions sans fin, ce qui provoque des StackOverflowErrors ;) D'autres problèmes peuvent être causés par les génériques, pour lesquels vous devez également vérifier l'immuabilité de leurs types.

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 il faut d'abord se demander si cela en vaut vraiment la peine (y compris en termes de performances).

31voto

Bno Points 5688

Utilisez le Immuable de l'annotation Java Concurrency en pratique . L'outil FindBugs peut alors aider à détecter les classes qui sont mutables mais ne devraient pas l'être.

9voto

Jason Cohen Points 36475

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

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

Bien sûr, vous dépendez toujours du respect de la parole de l'auteur concernant l'immuabilité, mais comme l'auteur a explicitement ajouté l'annotation, c'est une hypothèse raisonnable.

8voto

SCdF Points 11397

En gros, non.

Vous pourriez construire une liste blanche géante des classes acceptées, mais je pense que la façon la moins folle serait d'écrire dans la documentation de la collection que tout ce qui est accepté est la collection suivante doit être immuable.

Editar: D'autres personnes ont suggéré d'avoir une annotation immuable. C'est très bien, mais vous avez également besoin de la documentation. Sinon, les gens penseront simplement "si je mets cette annotation sur ma classe, je peux la stocker dans la collection" et la mettront sur n'importe quoi, aussi bien sur les classes immuables que mutables. En fait, je me méfierais d'avoir une annotation immuable juste au cas où les gens pensent que l'annotation fait leur classe immuable.

4voto

OscarRyz Points 82553

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 des types "primitifs", soit non mutables eux-mêmes.

De même, aucune méthode ne peut être surchargée, tous les champs sont finaux et privés,

Je vais essayer de coder quelque chose demain pour vous, mais le code de Simon utilisant la réflexion semble assez bon.

Entre-temps, essayez de vous procurer un exemplaire du livre "Effective Java" de Josh Block, qui contient un article sur ce sujet. Bien qu'il ne dise pas avec certitude comment détecter une classe inmmutable, il montre comment en créer une bonne.

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

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X