web-dev-qa-db-fra.com

Toutes les propriétés d'un objet immuable doivent-elles être définitives?

Les objets immuables doivent-ils que toutes les propriétés soient final?

Selon moi pas. Mais je ne sais pas si j'ai raison.

47
DRastislav

La principale différence entre un objet immuable (toutes les propriétés finales) et un objet effectivement immuable (les propriétés ne sont pas finales, mais ne peut pas être modifié) est la publication en toute sécurité.

Vous pouvez publier en toute sécurité un objet immuable dans un contexte multithread sans avoir à vous soucier de l'ajout de synchronisation, grâce aux garanties fournies par le modèle de mémoire Java pour les champs finaux :

les champs finaux permettent également aux programmeurs d'implémenter des objets immuables sécurisés pour les threads sans synchronisation. Un objet immuable thread-safe est considéré comme immuable par tous les threads, même si une course de données est utilisée pour transmettre des références à l'objet immuable entre les threads. Cela peut fournir des garanties de sécurité contre l'utilisation abusive d'une classe immuable par un code incorrect ou malveillant. Les champs finaux doivent être utilisés correctement pour fournir une garantie d'immutabilité. 

En remarque, cela permet également de renforcer l’immuabilité (si vous essayez de muter ces champs dans une future version de votre classe parce que vous avez oublié qu’elle devrait être immuable, elle ne sera pas compilée).


Clarifications

  • Rendre tous les champs d'un objet final ne le rend pas immuable - vous devez également vous assurer que (i) son état ne peut pas changer (par exemple, si l'objet contient un final List, aucune opération de mutation (add, remove .. .) doit être fait après la construction) et (ii) vous ne laissez pas this s'échapper pendant la construction
  • Un objet effectivement immuable est thread-safe une fois qu'il a été publié en toute sécurité
  • Exemple de publication non sécurisée:

    class EffectivelyImmutable {
        static EffectivelyImmutable unsafe;
        private int i;
        public EffectivelyImmutable (int i) { this.i = i; }
        public int get() { return i; }
    }
    
    // in some thread
    EffectivelyImmutable.unsafe = new EffectivelyImmutable(1);
    
    //in some other thread
    if (EffectivelyImmutable.unsafe != null
        && EffectivelyImmutable.unsafe.get() != 1)
        System.out.println("What???");
    

    Ce programme pourrait théoriquement imprimer What???. Si i était définitif, ce ne serait pas une issue légale.

50
assylias

Vous pouvez facilement garantir l'immuabilité par la seule encapsulation, donc ce n'est pas nécessaire:

// This is trivially immutable.
public class Foo {
    private String bar;
    public Foo(String bar) {
        this.bar = bar;
    }
    public String getBar() {
        return bar;
    }
}

Cependant, vous devez aussi garantir que l'encapsulation soit garantie dans certains cas, donc n'est pas suffisant:

public class Womble {
    private final List<String> cabbages;
    public Womble(List<String> cabbages) {
        this.cabbages = cabbages;
    }
    public List<String> getCabbages() {
        return cabbages;
    }
}
// ...
Womble w = new Womble(...);
// This might count as mutation in your design. (Or it might not.)
w.getCabbages().add("cabbage"); 

Ce n'est pas une mauvaise idée de le faire pour détecter certaines erreurs triviales et pour démontrer clairement votre intention, mais "tous les champs sont finaux" et "la classe est immuable" ne sont pas des déclarations équivalentes.

15
millimoose

Immuable = non modifiable. Rendre les propriétés définitives est donc une bonne idée. Si toutes les propriétés d'un objet ne sont pas protégées contre toute modification, je ne dirais pas que l'objet est immuable.

MAIS un objet est également immuable s'il ne fournit aucun paramètre pour ses propriétés privées.

5
Kai

Les objets immuables ne DOIVENT en aucun cas être modifiés après leur création. finale aide bien sûr à y parvenir. Vous garantissez qu'ils ne seront jamais changés. MAIS que se passe-t-il si vous avez un tableau final dans votre objet? Bien sûr, la référence n'est pas modifiable, mais les éléments le sont. Regardez ici à peu près la même question que j'ai aussi posée:

Lien

5
Eugene

Déclarer simplement un objet en tant que final ne le rend pas immuable en soi. Prenons par exemple cette classe :

import Java.util.Date;

/**
* Planet is an immutable class, since there is no way to change
* its state after construction.
*/
public final class Planet {

  public Planet (double aMass, String aName, Date aDateOfDiscovery) {
     fMass = aMass;
     fName = aName;
     //make a private copy of aDateOfDiscovery
     //this is the only way to keep the fDateOfDiscovery
     //field private, and shields this class from any changes that 
     //the caller may make to the original aDateOfDiscovery object
     fDateOfDiscovery = new Date(aDateOfDiscovery.getTime());
  }

  /**
  * Returns a primitive value.
  *
  * The caller can do whatever they want with the return value, without 
  * affecting the internals of this class. Why? Because this is a primitive 
  * value. The caller sees its "own" double that simply has the
  * same value as fMass.
  */
  public double getMass() {
    return fMass;
  }

  /**
  * Returns an immutable object.
  *
  * The caller gets a direct reference to the internal field. But this is not 
  * dangerous, since String is immutable and cannot be changed.
  */
  public String getName() {
    return fName;
  }

//  /**
//  * Returns a mutable object - likely bad style.
//  *
//  * The caller gets a direct reference to the internal field. This is usually dangerous, 
//  * since the Date object state can be changed both by this class and its caller.
//  * That is, this class is no longer in complete control of fDate.
//  */
//  public Date getDateOfDiscovery() {
//    return fDateOfDiscovery;
//  }

  /**
  * Returns a mutable object - good style.
  * 
  * Returns a defensive copy of the field.
  * The caller of this method can do anything they want with the
  * returned Date object, without affecting the internals of this
  * class in any way. Why? Because they do not have a reference to 
  * fDate. Rather, they are playing with a second Date that initially has the 
  * same data as fDate.
  */
  public Date getDateOfDiscovery() {
    return new Date(fDateOfDiscovery.getTime());
  }

  // PRIVATE //

  /**
  * Final primitive data is always immutable.
  */
  private final double fMass;

  /**
  * An immutable object field. (String objects never change state.)
  */
  private final String fName;

  /**
  * A mutable object field. In this case, the state of this mutable field
  * is to be changed only by this class. (In other cases, it makes perfect
  * sense to allow the state of a field to be changed outside the native
  * class; this is the case when a field acts as a "pointer" to an object
  * created elsewhere.)
  */
  private final Date fDateOfDiscovery;
}
5
user195488

La classe string est immuable mais le hash de la propriété n'est pas final 

C'est possible, mais avec certaines règles/restrictions et l'accès aux propriétés/champs mutables doit fournir le même résultat à chaque fois que nous y accédons.

Dans la classe String, le hashcode est en fait calculé sur le tableau final de caractères qui ne changera pas si String a été construit. Par conséquent, la classe immuable peut contenir des champs/propriétés mutables, mais elle doit s’assurer que l’accès à field/property produira le même résultat à chaque accès.

Pour répondre à votre question, il n'est pas obligatoire d'avoir tous les champs finaux dans une classe immuable.

Pour en savoir plus, visitez ici [blog]: http://javaunturnedtopics.blogspot.in/2016/07/string-is-immutable-and-property-hash.html

2
Tarun Bedi

Non.

Par exemple, voir l'implémentation de Java.lang.String. Les chaînes sont immuables en Java, mais le champ hash n'est pas final (il est calculé paresseusement la première fois que hashCode est appelé puis mis en cache). Mais cela fonctionne car hash ne peut prendre qu'une seule valeur différente de celle définie par défaut qui est la même à chaque fois qu’elle est calculée.

2
ZhekaKozlov

Ce n'est pas nécessaire, vous pouvez obtenir les mêmes fonctionnalités en rendant membre un membre non final mais privé et en ne les modifiant que dans le constructeur. Ne leur fournissez pas la méthode de définition et s'il s'agit d'un objet mutable, ne perdez jamais de référence pour ce membre. 

N'oubliez pas que vous définissez une variable de référence finale pour vous assurer uniquement que cette valeur ne sera pas réaffectée, mais que vous pourrez toujours modifier les propriétés individuelles d'un objet pointé par cette variable de référence. C'est l'un des points clés.

0
amitkumar12788