web-dev-qa-db-fra.com

Pourquoi déclarer une classe immuable en Java?

J'ai lu que pour rendre une classe immuable en Java, nous devrions faire ce qui suit, 

  1. Ne pas fournir de setters 
  2. Marquer tous les champs comme privés 
  3. Rendre la classe finale

Pourquoi l'étape 3 est-elle requise? Pourquoi devrais-je marquer la classe final

66
Anand

Si vous ne marquez pas la classe final, il me sera peut-être possible de transformer soudainement votre classe apparemment immuable. Par exemple, considérons ce code:

public class Immutable {
     private final int value;

     public Immutable(int value) {
         this.value = value;
     }

     public int getValue() {
         return value;
     }
}

Maintenant, supposons que je fasse ce qui suit:

public class Mutable extends Immutable {
     private int realValue;

     public Mutable(int value) {
         super(value);

         realValue = value;
     }

     public int getValue() {
         return realValue;
     }
     public void setValue(int newValue) {
         realValue = newValue;
     }

    public static void main(String[] arg){
        Mutable obj = new Mutable(4);
        Immutable immObj = (Immutable)obj;              
        System.out.println(immObj.getValue());
        obj.setValue(8);
        System.out.println(immObj.getValue());
    }
}

Notez que dans ma sous-classe Mutable, j'ai remplacé le comportement de getValue pour lire un nouveau champ mutable déclaré dans ma sous-classe. En conséquence, votre classe, qui semble au départ immuable, n’est vraiment pas immuable. Je peux transmettre cet objet Mutable chaque fois qu'un objet Immutable est attendu, ce qui pourrait faire très mal à coder en supposant que l'objet est vraiment immuable. Le marquage de la classe de base final empêche que cela se produise.

J'espère que cela t'aides!

110
templatetypedef

Contrairement à ce que beaucoup de gens croient, créer une classe immuable final est pas requis.

L'argument standard pour créer des classes immuables final est que si vous ne le faites pas, les sous-classes peuvent alors ajouter de la mutabilité, violant ainsi le contrat de la superclasse. Les clients de la classe assumeront l’immuabilité, mais seront surpris lorsque quelque chose mute sous eux.

Si vous prenez cet argument à l'extrême logique, alors toutes les méthodes doivent être définies comme suit final, sinon une sous-classe pourrait remplacer une méthode d'une manière non conforme au contrat de sa superclasse. Il est intéressant de noter que la plupart des programmeurs Java voient cela comme ridicule, mais sont d'accord pour dire que les classes immuables devraient être final. Je suppose que cela a quelque chose à voir avec le fait que les programmeurs Java en général ne sont pas tout à fait à l'aise avec la notion d'immutabilité et peut-être une sorte de pensée floue concernant les multiples significations du mot clé final en Java.

La conformité au contrat de votre super-classe n'est pas quelque chose qui peut ou doit toujours être appliqué par le compilateur. Le compilateur peut appliquer certains aspects de votre contrat (par exemple, un ensemble minimal de méthodes et leurs signatures de type), mais de nombreux éléments de contrats types ne peuvent pas être appliqués par le compilateur.

L'immuabilité fait partie du contrat d'une classe. C'est un peu différent de certaines choses auxquelles les gens sont plus habitués, parce qu'il dit ce que la classe (et toutes les sous-classes) ne peut pas faire, alors que je pense que la plupart des programmeurs Java (et généralement OOP) ont tendance à penser à les contrats relatifs à ce qu'une classe peut faire, pas ce qu'elle ne peut pas faire. 

L’immuabilité affecte également plus d’une méthode unique - elle affecte l’ensemble de l’instance - mais cela n’est pas très différent de la façon dont equals et hashCode en Java fonctionnent. Ces deux méthodes ont un contrat spécifique défini dans Object. Ce contrat expose très soigneusement les choses que ces méthodes ne peuvent pas faire. Ce contrat est précisé dans les sous-classes. Il est très facile de remplacer equals ou hashCode d'une manière non conforme au contrat. En fait, si vous substituez l'une de ces deux méthodes sans l'autre, il est probable que vous violez le contrat. Donc, equals et hashCode auraient-ils été déclarés final dans Object pour éviter cela? Je pense que la plupart diraient qu'ils ne devraient pas. De même, il n'est pas nécessaire de faire des classes immuables final.

Cela dit, la plupart de vos classes, immuables ou non, probablement devraient être final. Voir Effective Java Second Edition élément 17: «Concevez et documentez l'héritage ou interdisez-le». 

Ainsi, une version correcte de votre étape 3 serait: "Rendez la classe finale ou, lors de la conception pour le sous-classement, indiquez clairement que toutes les sous-classes doivent rester immuables."

35
Laurence Gonsalves

Ne marquez pas la finale de la classe entière.

Il existe des raisons valables de permettre l’extension d’une classe immuable, comme indiqué dans certaines des autres réponses, de sorte que marquer la classe comme finale n’est pas toujours une bonne idée. 

Il est préférable de marquer vos propriétés comme privées et définitives et si vous souhaitez protéger le "contrat", marquez vos accesseurs comme étant définitifs. 

De cette façon, vous pouvez autoriser l’extension de la classe (oui, éventuellement, même par une classe mutable), mais les aspects immuables de votre classe sont protégés. Les propriétés sont privées et inaccessibles, les getters de ces propriétés sont finaux et ne peuvent pas être remplacés. 

Tout autre code utilisant une instance de votre classe immuable pourra s'appuyer sur les aspects immuables de votre classe, même si la sous-classe à laquelle elle est transmise est mutable sur d'autres aspects. Bien sûr, puisqu'il faut une instance de votre classe, il ne saurait même pas connaître ces autres aspects.

18
Justin Ohms

Si ce n'est pas définitif, n'importe qui peut prolonger le cours et faire ce qu'il veut, comme fournir des paramètres, observer vos variables privées et, fondamentalement, le rendre mutable.

5
digitaljoel

Cela contraint les autres classes à prolonger votre classe.

la classe finale ne peut pas être étendue par d'autres classes.

Si une classe étend la classe que vous voulez rendre immuable, cela peut changer l'état de la classe en raison des principes d'héritage.

Juste clarifier "cela peut changer". La sous-classe peut remplacer le comportement de la super-classe, comme en utilisant une méthode (par exemple, templatetypedef/Ted Hop answer)

4
kosa

Pour créer une classe immuable, il n'est pas obligatoire de marquer la classe en tant que finale.

Permettez-moi de prendre l'un de ces exemples dans les classes Java. La classe "BigInteger" est immuable, mais elle n'est pas définitive.

En réalité, l’immutabilité est un concept selon lequel l’objet créé crée alors il ne peut pas être modifié.

Pensons, du point de vue de la machine virtuelle Java, que tous les threads doivent partager la même copie de l'objet et qu'il est entièrement construit avant qu'un thread ne puisse y accéder et que l'état de l'objet ne change pas après sa construction.

Immutability signifie qu'il n'y a aucun moyen de changer l'état de l'objet une fois qu'il est créé et ceci est réalisé par trois règles de contrôle qui obligent le compilateur à reconnaître que la classe est immuable et qui sont comme suit:

  • tous les champs non privés doivent être définitifs
  • assurez-vous qu'il n'y a pas de méthode dans la classe qui puisse changer les champs de l'objet directement ou indirectement
  • toute référence d'objet définie dans la classe ne peut pas être modifiée en dehors de la classe

Pour plus d'informations, consultez l'URL ci-dessous.

http://javaunturnedtopics.blogspot.in/2016/07/can-we-create-immutable-class-without.html

2
Tarun Bedi

Si vous ne le rendez pas définitif, je peux l'étendre et le rendre non mutable.

public class Immutable {
  privat final int val;
  public Immutable(int val) {
    this.val = val;
  }

  public int getVal() {
    return val;
  }
}

public class FakeImmutable extends Immutable {
  privat int val2;
  public FakeImmutable(int val) {
    super(val);
  }

  public int getVal() {
    return val2;
  }

  public void setVal(int val2) {
    this.val2 = val2;
  }
}

Maintenant, je peux passer FakeImmutable à n’importe quelle classe qui attend Immutable et elle ne se comportera pas comme le contrat attendu.

1
Roger Lindsjö

Supposons que la classe suivante ne soit pas final:

public class Foo {
    private int mThing;
    public Foo(int thing) {
        mThing = thing;
    }
    public int doSomething() { /* doesn't change mThing */ }
}

Il est apparemment immuable, car même les sous-classes ne peuvent pas modifier mThing. Cependant, une sous-classe peut être mutable:

public class Bar extends Foo {
    private int mValue;
    public Bar(int thing, int value) {
        super(thing);
        mValue = value;
    }
    public int getValue() { return mValue; }
    public void setValue(int value) { mValue = value; }
}

Désormais, il n'est plus garanti qu'un objet pouvant être assigné à une variable de type Foo soit mmutable. Cela peut causer des problèmes avec des choses comme le hachage, l’égalité, la concurrence, etc.

0
Ted Hopp

La signification par défaut d’equals () est identique à l’égalité référentielle. Pour les types de données immuables, c'est presque toujours faux. Vous devez donc redéfinir la méthode equals () en la remplaçant par votre propre implémentation. lien

0
Anitha B S

La conception en soi n'a aucune valeur. Le design est toujours utilisé pour atteindre un objectif. Quel est le but ici? Voulons-nous réduire le nombre de surprises dans le code? Voulons-nous prévenir les insectes? Est-ce que nous suivons aveuglément les règles?

En outre, la conception a toujours un coût. Chaque conception qui mérite le nom signifie que vous avez un conflit d'objectifs .

Dans cet esprit, vous devez trouver des réponses à ces questions:

  1. Combien de bugs évidents cela va-t-il empêcher?
  2. Combien de bugs subtils cela évitera-t-il?
  3. À quelle fréquence cela rend-il les autres codes plus complexes (= plus sujets aux erreurs)?
  4. Cela rend-il le test plus facile ou plus difficile?
  5. Quelle est la qualité des développeurs de votre projet? De combien de conseils ont-ils besoin avec une masse?

Supposons que vous avez beaucoup de développeurs juniors dans votre équipe. Ils essaieront désespérément d’essayer d’être stupides simplement parce qu’ils ne connaissent pas encore de solutions à leurs problèmes. Rendre la classe finale pourrait éviter les bugs (bien), mais pourrait aussi les amener à proposer des solutions "intelligentes" comme copier toutes ces classes dans des classes mutables partout dans le code.

D'autre part, il sera très difficile de faire une classe final après son utilisation partout mais il est facile de faire une classe final non -final plus tard si vous découvrez que vous devez l'étendre.

Si vous utilisez correctement les interfaces, vous pouvez éviter le problème "J'ai besoin de rendre ce mutable" en utilisant toujours l'interface, puis en ajoutant ultérieurement une implémentation mutable en cas de besoin.

Conclusion: Il n'y a pas de "meilleure" solution pour cette réponse. Cela dépend du prix que vous êtes prêt et que vous devez payer.

0
Aaron Digulla