91 votes

Pourquoi déclarer final une classe immuable en Java ?

J'ai lu que pour faire un classe immuable en Java, nous devons faire ce qui suit,

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

Pourquoi l'étape 3 est-elle nécessaire ? Pourquoi dois-je marquer la classe final ?

4voto

Roger Lindsjö Points 6514

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 à toute classe qui s'attend à Immutable, et elle ne se comportera pas comme le contrat attendu.

4voto

Tarun Bedi Points 51

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

Prenons un exemple de la bibliothèque standard elle-même : BigInteger est immuable mais il ne l'est pas final .

En fait, l'immuabilité est un concept selon lequel une fois qu'un objet est créé, il ne peut plus être modifié.

Réfléchissons du point de vue de la JVM. Du point de vue de la JVM, un objet de cette classe doit être entièrement construit avant qu'un thread puisse y accéder et l'état de l'objet ne doit pas changer après sa construction.

L'immuabilité 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 pouce qui font que le compilateur reconnaît que la classe est immuable et elles sont les suivantes :

  • Tous les non private Les champs doivent être final
  • Assurez-vous qu'aucune méthode de la classe ne peut modifier les champs de l'objet, que ce soit directement ou indirectement.
  • Toute référence d'objet définie dans la classe ne peut être modifiée depuis l'extérieur de la classe.

Pour plus d'informations, voir cette URL .

2voto

valijon Points 407

Disons que vous avez la classe suivante :

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class PaymentImmutable {
    private final Long id;
    private final List<String> details;
    private final Date paymentDate;
    private final String notes;

    public PaymentImmutable (Long id, List<String> details, Date paymentDate, String notes) {
        this.id = id;
        this.notes = notes;
        this.paymentDate = paymentDate == null ? null : new Date(paymentDate.getTime());
        if (details != null) {
            this.details = new ArrayList<String>();

            for(String d : details) {
                this.details.add(d);
            }
        } else {
            this.details = null;
        }
    }

    public Long getId() {
        return this.id;
    }

    public List<String> getDetails() {
        if(this.details != null) {
            List<String> detailsForOutside = new ArrayList<String>();
            for(String d: this.details) {
                detailsForOutside.add(d);
            }
            return detailsForOutside;
        } else {
            return null;
        }
    }

}

Ensuite, vous l'étendez et brisez son immuabilité.

public class PaymentChild extends PaymentImmutable {
    private List<String> temp;
    public PaymentChild(Long id, List<String> details, Date paymentDate, String notes) {
        super(id, details, paymentDate, notes);
        this.temp = details;
    }

    @Override
    public List<String> getDetails() {
        return temp;
    }
}

Nous le testons ici :

public class Demo {

    public static void main(String[] args) {
        List<String> details = new ArrayList<>();
        details.add("a");
        details.add("b");
        PaymentImmutable immutableParent = new PaymentImmutable(1L, details, new Date(), "notes");
        PaymentImmutable notImmutableChild = new PaymentChild(1L, details, new Date(), "notes");

        details.add("some value");
        System.out.println(immutableParent.getDetails());
        System.out.println(notImmutableChild.getDetails());
    }
}

Le résultat de la sortie sera :

[a, b]
[a, b, some value]

Comme vous pouvez le voir, alors que la classe originale reste immuable, les classes enfant peuvent être mutables. Par conséquent, dans votre conception, vous ne pouvez pas être sûr que l'objet que vous utilisez est immuable, à moins que vous ne rendiez votre classe finale.

0voto

Ted Hopp Points 122617

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

Maintenant un objet qui est assignable à une variable de type Foo n'est plus garanti comme étant mmutable. Cela peut causer des problèmes avec des choses comme le hachage, l'égalité, la concurrence, etc.

0voto

Aaron Digulla Points 143830

Le design en soi n'a aucune valeur. Le design est toujours utilisé pour atteindre un objectif. Quel est l'objectif ici ? Voulons-nous réduire le nombre de surprises dans le code ? Voulons-nous éviter les bogues ? Suivons-nous aveuglément des règles ?

En outre, le design a toujours un coût. Toute conception qui mérite ce nom implique un conflit d'objectifs. .

Dans cette optique, vous devez trouver des réponses à ces questions :

  1. Combien de bogues évidents cela évitera-t-il ?
  2. Combien de bugs subtils cela va-t-il empêcher ?
  3. Combien de fois cela rendra-t-il d'autres codes plus complexes (= plus sujets aux erreurs) ?
  4. Cela rend-il les tests plus faciles ou plus difficiles ?
  5. Quelle est la qualité des développeurs de votre projet ? De combien de conseils avec un marteau de forgeron ont-ils besoin ?

Disons que vous avez de nombreux développeurs juniors dans votre équipe. Ils essaieront désespérément n'importe quelle chose stupide juste parce qu'ils ne connaissent pas encore de bonnes solutions à leurs problèmes. Rendre la classe finale pourrait prévenir les bogues (bien) mais pourrait aussi leur faire trouver 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 qu'il ait été utilisé partout mais il est facile de faire une final classe non final plus tard si vous découvrez que vous devez le prolonger.

Si vous utilisez correctement les interfaces, vous pouvez éviter le problème du "je dois rendre ceci mutable" en utilisant toujours l'interface, puis en ajoutant ultérieurement une implémentation mutable lorsque le besoin s'en fait sentir.

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

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