t
t
t
t
t t A) Passage et Retour d'Objets
tttt
A) Passage et retour d'objets
Texte original t Traducteur : Jérome QUELIN
t
t
///
Ce chapitre contient 6 pages
1 2 3 4 5 6
\\\
t t t
t t t
t
t t t
So to summarize, if you want a class to be cloneable: t Donc pour résumer, si on veut qu'une classe soit cloneable, il faut :
t t t
  1. Implement the Cloneable interface.
  2. Override clone( ).
  3. Call super.clone( ) inside your clone( ).
  4. Capture exceptions inside your clone( ).
t
  1. Implémenter l'interface Cloneable.
  2. Redéfinir clone().
  3. Appeler super.clone() depuis la méthode clone() de la classe.
  4. Intercepter les exceptions à l'intérieur de la méthode clone().
t t t
This will produce the most convenient effects. t Ceci produira l'effet désiré.
t t t

The copy constructor

t

Le constructeur de copie

t t t
Cloning can seem to be a complicated process to set up. It might seem like there should be an alternative. One approach that might occur to you (especially if you’re a C++ programmer) is to make a special constructor whose job it is to duplicate an object. In C++, this is called the copy constructor. At first, this seems like the obvious solution, but in fact it doesn’t work. Here’s an example: t Le clonage peut sembler un processus compliqué à mettre en oeuvre. On se dit qu'il doit certainement exister une autre alternative, et souvent on envisage (surtout les programmeurs C++) de créer un constructeur spécial dont le travail est de dupliquer un objet. En C++, on l'appelle le constructeur de copie. Cela semble a priori la solution la plus évidente, mais en fait elle ne fonctionne pas. Voici un exemple.
t t t
//: appendixa:CopyConstructor.java
// A constructor for copying an object of the same
// type, as an attempt to create a local copy.

class FruitQualities {
  private int weight;
  private int color;
  private int firmness;
  private int ripeness;
  private int smell;
  // etc.
  FruitQualities() { // Default constructor
    // do something meaningful...
  }
  // Other constructors:
  // ...
  // Copy constructor:
  FruitQualities(FruitQualities f) {
    weight = f.weight;
    color = f.color;
    firmness = f.firmness;
    ripeness = f.ripeness;
    smell = f.smell;
    // etc.
  }
}

class Seed {
  // Members...
  Seed() { /* Default constructor */ }
  Seed(Seed s) { /* Copy constructor */ }
}

class Fruit {
  private FruitQualities fq;
  private int seeds;
  private Seed[] s;
  Fruit(FruitQualities q, int seedCount) {
    fq = q;
    seeds = seedCount;
    s = new Seed[seeds];
    for(int i = 0; i < seeds; i++)
      s[i] = new Seed();
  }
  // Other constructors:
  // ...
  // Copy constructor:
  Fruit(Fruit f) {
    fq = new FruitQualities(f.fq);
    seeds = f.seeds;
    // Call all Seed copy-constructors:
    for(int i = 0; i < seeds; i++)
      s[i] = new Seed(f.s[i]);
    // Other copy-construction activities...
  }
  // To allow derived constructors (or other
  // methods) to put in different qualities:
  protected void addQualities(FruitQualities q) {
    fq = q;
  }
  protected FruitQualities getQualities() {
    return fq;
  }
}

class Tomato extends Fruit {
  Tomato() {
    super(new FruitQualities(), 100);
  }
  Tomato(Tomato t) { // Copy-constructor
    super(t); // Upcast for base copy-constructor
    // Other copy-construction activities...
  }
}

class ZebraQualities extends FruitQualities {
  private int stripedness;
  ZebraQualities() { // Default constructor
    // do something meaningful...
  }
  ZebraQualities(ZebraQualities z) {
    super(z);
    stripedness = z.stripedness;
  }
}

class GreenZebra extends Tomato {
  GreenZebra() {
    addQualities(new ZebraQualities());
  }
  GreenZebra(GreenZebra g) {
    super(g); // Calls Tomato(Tomato)
    // Restore the right qualities:
    addQualities(new ZebraQualities());
  }
  void evaluate() {
    ZebraQualities zq =
      (ZebraQualities)getQualities();
    // Do something with the qualities
    // ...
  }
}

public class CopyConstructor {
  public static void ripen(Tomato t) {
    // Use the "copy constructor":
    t = new Tomato(t);
    System.out.println("In ripen, t is a " +
      t.getClass().getName());
  }
  public static void slice(Fruit f) {
    f = new Fruit(f); // Hmmm... will this work?
    System.out.println("In slice, f is a " +
      f.getClass().getName());
  }
  public static void main(String[] args) {
    Tomato tomato = new Tomato();
    ripen(tomato); // OK
    slice(tomato); // OOPS!
    GreenZebra g = new GreenZebra();
    ripen(g); // OOPS!
    slice(g); // OOPS!
    g.evaluate();
  }
} ///:~
t
//: appendixa:CopyConstructor.java
// Un constructeur pour copier un objet du même
// type, dans une tentaive de créer une copie locale.

class FruitQualities {
  private int weight;
  private int color;
  private int firmness;
  private int ripeness;
  private int smell;
  // etc...
  FruitQualities() { // Constucteur par défaut.
    // fait des tas de choses utiles...
  }
  // D'autres constructeurs :
  // ...
  // Constructeur de copie :
  FruitQualities(FruitQualities f) {
    weight = f.weight;
    color = f.color;
    firmness = f.firmness;
    ripeness = f.ripeness;
    smell = f.smell;
    // etc...
  }
}

class Seed {
  // Membres...
  Seed() { /* Constructeur par défaut */ }
  Seed(Seed s) { /* Constructeur de copie */ }
}

class Fruit {
  private FruitQualities fq;
  private int seeds;
  private Seed[] s;
  Fruit(FruitQualities q, int seedCount) {
    fq = q;
    seeds = seedCount;
    s = new Seed[seeds];
    for(int i = 0; i < seeds; i++)
      s[i] = new Seed();
  }
  // Autres constructeurs :
  // ...
  // Constructeur de copie :
  Fruit(Fruit f) {
    fq = new FruitQualities(f.fq);
    seeds = f.seeds;
    // Appelle le constructeur de copie sur toutes les Seed :
    for(int i = 0; i < seeds; i++)
      s[i] = new Seed(f.s[i]);
    // D'autres activités du constructeur de copie...
  }
  // Pour permettre aux constructeurs dérivés (ou aux
  // autres méthodes) de changer les qualités :
  protected void addQualities(FruitQualities q) {
    fq = q;
  }
  protected FruitQualities getQualities() {
    return fq;
  }
}

class Tomato extends Fruit {
  Tomato() {
    super(new FruitQualities(), 100);
  }
  Tomato(Tomato t) { // Constructeur de copie.
    super(t); // Transtypage pour le constructeur de copie parent.
    // D'autres activités du constructeur de copie...
  }
}

class ZebraQualities extends FruitQualities {
  private int stripedness;
  ZebraQualities() { // Constructeur par défaut.
    // Fait des tas de choses utiles...
  }
  ZebraQualities(ZebraQualities z) {
    super(z);
    stripedness = z.stripedness;
  }
}

class GreenZebra extends Tomato {
  GreenZebra() {
    addQualities(new ZebraQualities());
  }
  GreenZebra(GreenZebra g) {
    super(g); // Appelle Tomato(Tomato)
    // Restitue les bonnes qualités :
    addQualities(new ZebraQualities());
  }
  void evaluate() {
    ZebraQualities zq =
      (ZebraQualities)getQualities();
    // Utilise les qualités
    // ...
  }
}

public class CopyConstructor {
  public static void ripen(Tomato t) {
    // Utilise le « constructeur de copie » :
    t = new Tomato(t);
    System.out.println("In ripen, t is a " +
      t.getClass().getName());
  }
  public static void slice(Fruit f) {
    f = new Fruit(f); // Hmmm... est-ce que cela va marcher ?
    System.out.println("In slice, f is a " +
      f.getClass().getName());
  }
  public static void main(String[] args) {
    Tomato tomato = new Tomato();
    ripen(tomato); // OK
    slice(tomato); // OOPS!
    GreenZebra g = new GreenZebra();
    ripen(g); // OOPS!
    slice(g); // OOPS!
    g.evaluate();
  }
} ///:~
t t t
This seems a bit strange at first. Sure, fruit has qualities, but why not just put data members representing those qualities directly into the Fruit class? There are two potential reasons. The first is that you might want to easily insert or change the qualities. Note that Fruit has a protected addQualities( ) method to allow derived classes to do this. (You might think the logical thing to do is to have a protected constructor in Fruit that takes a FruitQualities argument, but constructors don’t inherit so it wouldn’t be available in second or greater level classes.) By making the fruit qualities into a separate class, you have greater flexibility, including the ability to change the qualities midway through the lifetime of a particular Fruit object. t Ceci semble un peu étrange à première vue. Bien sûr, un fruit a des qualités, mais pourquoi ne pas mettre les données membres représentant ces qualités directement dans la classe Fruit ? Deux raisons à cela. La première est qu'on veut pouvoir facilement insérer ou changer les qualités. Notez que Fruit possède une méthode protected addQualities() qui permet aux classes dérivées de le faire (on pourrait croire que la démarche logique serait d'avoir un constructeur protected dans Fruit qui accepte un argument FruitQualities, mais les constructeurs ne sont pas hérités et ne seraient pas disponibles dans les classes dérivées). En créant une classe séparée pour la qualité des fruits, on dispose d'une plus grande flexibilité, incluant la possibilité de changer les qualités d'un objet Fruit pendant sa durée de vie.
t t t
The second reason for making FruitQualities a separate object is in case you want to add new qualities or to change the behavior via inheritance and polymorphism. Note that for GreenZebra (which really is a type of tomato—I’ve grown them and they’re fabulous), the constructor calls addQualities( ) and passes it a ZebraQualities object, which is derived from FruitQualities so it can be attached to the FruitQualities reference in the base class. Of course, when GreenZebra uses the FruitQualities it must downcast it to the correct type (as seen in evaluate( )), but it always knows that type is ZebraQualities. t La deuxième raison pour laquelle on a décidé de créer une classe FruitQualities est dans le cas où on veut ajouter de nouvelles qualités ou en changer le comportement via héritage ou polymorphisme. Notez que pour les GreenZebra (qui sont réellement un type de tomates - j'en ai cultivé et elles sont fabuleuses), le constructeur appelle addQualities() et lui passe un objet ZebraQualities, qui est dérivé de FruitQualities et peut donc être attaché à la référence FruitQualities de la classe de base. Bien sûr, quand GreenZebra utilise les FruitQualities il doit le transtyper dans le type correct (comme dans evaluate()), mais il sait que le type est toujours ZebraQualities.
t t t
You’ll also see that there’s a Seed class, and that Fruit (which by definition carries its own seeds)[82] contains an array of Seeds. t Vous noterez aussi qu'il existe une classe Seed, et qu'un Fruit (qui par définition porte ses propres graines)  name="fnB82">[82]contient un tableau de Seeds.
t t t
Finally, notice that each class has a copy constructor, and that each copy constructor must take care to call the copy constructors for the base class and member objects to produce a deep copy. The copy constructor is tested inside the class CopyConstructor. The method ripen( ) takes a Tomato argument and performs copy-construction on it in order to duplicate the object: t Enfin, vous noterez que chaque classe dispose d'un constructeur de copie, et que chaque constructeur de copie doit s'occuper d'appeler le constructeur de copie de la classe de base et des objets membres pour réaliser une copie profonde. Le constructeur de copie est testé dans la classe CopyConstructor. La méthode ripen() accepte un argument Tomato et réalise une construction de copie afin de dupliquer l'objet :
t t t
t = new Tomato(t);
t
t = new Tomato(t);
t t t
while slice( ) takes a more
generic Fruit object and also duplicates it:

t tandis que slice( ) accepte un objet Fruit plus générique et le duplique aussi :
t t t
f = new Fruit(f);
t
f = new Fruit(f);
t t t
These are tested with different kinds of
Fruit in main( ). Here’s the output:

t Ces deux méthodes sont testées avec différents types de Fruit dans main(). Voici la sortie produite :
t t t
In ripen, t is a Tomato
In slice, f is a Fruit
In ripen, t is a Tomato
In slice, f is a Fruit
t
In ripen, t is a Tomato
In slice, f is a Fruit
In ripen, t is a Tomato
In slice, f is a Fruit
t t t
This is where the problem shows up. After the copy-construction that happens to the Tomato inside slice( ), the result is no longer a Tomato object, but just a Fruit. It has lost all of its tomato-ness. Further, when you take a GreenZebra, both ripen( ) and slice( ) turn it into a Tomato and a Fruit, respectively. Thus, unfortunately, the copy constructor scheme is no good to us in Java when attempting to make a local copy of an object. t C'est là que le problème survient. Après la construction de copie réalisée dans slice() sur l'objet Tomato, l'objet résultant n'est plus un objet Tomato, mais seulement un Fruit. Il a perdu toute sa tomaticité. De même, quand on prend une GreenZebra, ripen() et slice() la transforment toutes les deux en Tomato et Fruit, respectivement. La technique du constructeur de copie ne fonctionne donc pas en Java pour créer une copie locale d'un objet.
t t t

Why does it work in C++ and not Java?

t

Pourquoi cela fonctionne-t-il en C++ et pas en Java ?

t t t
The copy constructor is a fundamental part of C++, since it automatically makes a local copy of an object. Yet the example above proves that it does not work for Java. Why? In Java everything that we manipulate is a reference, while in C++ you can have reference-like entities and you can also pass around the objects directly. That’s what the C++ copy constructor is for: when you want to take an object and pass it in by value, thus duplicating the object. So it works fine in C++, but you should keep in mind that this scheme fails in Java, so don’t use it. t Le constructeur de copie est un mécanisme fondamental en C++, puisqu'il permet de créer automatiquement une copie locale d'un objet. Mais l'exemple précédent prouve que cela ne fonctionne pas en Java. Pourquoi ? En Java, toutes les entités manipulées sont des références, tandis qu'en C++ on peut manipuler soit des références sur les objets soit les objets directement. C'est le rôle du constructeur de copie en C++ : prendre un objet et permettre son passage par valeur, donc dupliquer l'objet. Cela fonctionne donc très bien en C++, mais il faut garder présent à l'esprit que ce mécanisme est à proscrire en Java.
t t t

Read-only classes

t

Classes en lecture seule

t t t
While the local copy produced by clone( ) gives the desired results in the appropriate cases, it is an example of forcing the programmer (the author of the method) to be responsible for preventing the ill effects of aliasing. What if you’re making a library that’s so general purpose and commonly used that you cannot make the assumption that it will always be cloned in the proper places? Or more likely, what if you want to allow aliasing for efficiency—to prevent the needless duplication of objects—but you don’t want the negative side effects of aliasing? t Bien que la copie locale produite par clone() donne les résultats escomptés dans les cas appropriés, c'est un exemple où le programmeur (l'auteur de la méthode) est responsable des effets secondaires indésirables de l'aliasing. Que se passe-t-il dans le cas où on construit une bibliothèque tellement générique et utilisée qu'on ne peut supposer qu'elle sera toujours clonée aux bons endroits ? Ou alors, que se passe-t-il si on veut permettre l'aliasing dans un souci d'efficacité - afin de prévenir la duplication inutile d'un objet - mais qu'on n'en veut pas les effets secondaires négatifs ?
t t t
One solution is to create immutable objects which belong to read-only classes. You can define a class such that no methods in the class cause changes to the internal state of the object. In such a class, aliasing has no impact since you can read only the internal state, so if many pieces of code are reading the same object there’s no problem. t Une solution est de créer des objets immuables appartenant à des classes en lecture seule. On peut définir une classe telle qu'aucune méthode de la classe ne modifie l'état interne de l'objet. Dans une telle classe, l'aliasing n'a aucun impact puisqu'on peut seulement lire son état interne, donc même si plusieurs portions de code utilisent le même objet cela ne pose pas de problèmes.
t t t
As a simple example of immutable objects, Java’s standard library contains “wrapper” classes for all the primitive types. You might have already discovered that, if you want to store an int inside a container such as an ArrayList (which takes only Object references), you can wrap your int inside the standard library Integer class: t Par exemple, la bibliothèque standard Java contient des classes name="Index2266">« wrapper »  pour tous les types fondamentaux. Vous avez peut-être déjà découvert que si on veut stocker un int dans un conteneur tel qu'une ArrayList (qui n'accepte que des références sur un Object), on peut insérer l'int dans la classe Integer de la bibliothèque standard :
t t t
//: appendixa:ImmutableInteger.java
// The Integer class cannot be changed.
import java.util.*;

public class ImmutableInteger {
  public static void main(String[] args) {
    ArrayList v = new ArrayList();
    for(int i = 0; i < 10; i++)
      v.add(new Integer(i));
    // But how do you change the int
    // inside the Integer?
  }
} ///:~
t
//: appendixa:ImmutableInteger.java
// La classe Integer ne peut pas être modifiée.
import java.util.*;

public class ImmutableInteger {
  public static void main(String[] args) {
    ArrayList v = new ArrayList();
    for(int i = 0; i < 10; i++)
      v.add(new Integer(i));
    // Mais comment changer l'int à
    // l'intérieur de Integer?
  }
} ///:~
t t t
The Integer class (as well as all the primitive “wrapper” classes) implements immutability in a simple fashion: they have no methods that allow you to change the object. t La classe Integer (de même que toutes les classes « wrapper » pour les scalaires) implémentent l'immuabilité d'une manière simple : elles ne possèdent pas de méthodes qui permettent de modifier l'objet.
t t t
If you do need an object that holds a primitive type that can be modified, you must create it yourself. Fortunately, this is trivial: t Si on a besoin d'un objet qui contient un scalaire qui peut être modifié, il faut la créer soi-même. Heureusement, ceci se fait facilement :
t t t
//: appendixa:MutableInteger.java
// A changeable wrapper class.
import java.util.*;

class IntValue {
  int n;
  IntValue(int x) { n = x; }
  public String toString() {
    return Integer.toString(n);
  }
}

public class MutableInteger {
  public static void main(String[] args) {
    ArrayList v = new ArrayList();
    for(int i = 0; i < 10; i++)
      v.add(new IntValue(i));
    System.out.println(v);
    for(int i = 0; i < v.size(); i++)
      ((IntValue)v.get(i)).n++;
    System.out.println(v);
  }
} ///:~
t
//: appendixa:MutableInteger.java
// Une classe wrapper modifiable.
import java.util.*;

class IntValue {
  int n;
  IntValue(int x) { n = x; }
  public String toString() {
    return Integer.toString(n);
  }
}

public class MutableInteger {
  public static void main(String[] args) {
    ArrayList v = new ArrayList();
    for(int i = 0; i < 10; i++)
      v.add(new IntValue(i));
    System.out.println(v);
    for(int i = 0; i < v.size(); i++)
      ((IntValue)v.get(i)).n++;
    System.out.println(v);
  }
} ///:~
t t t
Note that n is friendly to simplify coding. t Notez que n est amical pour simplifier le codage.
t t t
IntValue can be even simpler if the default initialization to zero is adequate (then you don’t need the constructor) and you don’t care about printing it out (then you don’t need the toString( )): t IntValue peut même être encore plus simple si l'initialisation à zéro est acceptable (auquel cas on n'a plus besoin du constructeur) et qu'on n'a pas besoin d'imprimer cet objet (auquel cas on n'a pas besoin de toString()) :
t t t
class IntValue { int n; }
t
class IntValue { int n; }
t t t
Fetching the element out and casting it
is a bit awkward, but that’s a feature of ArrayList, not of
IntValue.
t La recherche de l'élément et son transtypage par la suite est un peu lourd et maladroit, mais c'est une particularité de ArrayList et non de IntValue.
t t t


Creating read-only classes


t

Créer des classes en lecture seule

t t t
It’s possible to create your own
read-only class. Here’s an example:

t Il est possible de créer ses propres classes en lecture seule. Voici un exemple :
t t t
//: appendixa:Immutable1.java
// Objects that cannot be modified
// are immune to aliasing.

public class Immutable1 {
  private int data;
  public Immutable1(int initVal) {
    data = initVal;
  }
  public int read() { return data; }
  public boolean nonzero() { return data != 0; }
  public Immutable1 quadruple() {
    return new Immutable1(data * 4);
  }
  static void f(Immutable1 i1) {
    Immutable1 quad = i1.quadruple();
    System.out.println("i1 = " + i1.read());
    System.out.println("quad = " + quad.read());
  }
  public static void main(String[] args) {
    Immutable1 x = new Immutable1(47);
    System.out.println("x = " + x.read());
    f(x);
    System.out.println("x = " + x.read());
  }
} ///:~
t
//: appendixa:Immutable1.java
// Des objets qu'on ne peut modifier
// ne craignent pas l'aliasing.

public class Immutable1 {
  private int data;
  public Immutable1(int initVal) {
    data = initVal;
  }
  public int read() { return data; }
  public boolean nonzero() { return data != 0; }
  public Immutable1 quadruple() {
    return new Immutable1(data * 4);
  }
  static void f(Immutable1 i1) {
    Immutable1 quad = i1.quadruple();
    System.out.println("i1 = " + i1.read());
    System.out.println("quad = " + quad.read());
  }
  public static void main(String[] args) {
    Immutable1 x = new Immutable1(47);
    System.out.println("x = " + x.read());
    f(x);
    System.out.println("x = " + x.read());
  }
} ///:~
t t t
All data is private, and you’ll see that none of the public methods modify that data. Indeed, the method that does appear to modify an object is quadruple( ), but this creates a new Immutable1 object and leaves the original one untouched. t Toutes les données sont private, et aucune méthode public ne modifie les données. En effet, la méthode qui semble modifier l'objet, quadruple(), crée en fait un nouvel objet Immutable1 sans modifier l'objet original.
t t t
The method f( ) takes an Immutable1 object and performs various operations on it, and the output of main( ) demonstrates that there is no change to x. Thus, x’s object could be aliased many times without harm because the Immutable1 class is designed to guarantee that objects cannot be changed. t La méthode f() accepte un objet Immutable1 et effectue diverses opérations avec, et la sortie de main() démontre que x ne subit aucun changement. Ainsi, l'objet x peut être aliasé autant qu'on le veut sans risque puisque la classe Immutable1 a été conçue afin de guarantir que les objets ne puissent être modifiés.
t t t

The drawback to immutability

t

L'inconvénient de l'immuabilité

t t t
Creating an immutable class seems at first to provide an elegant solution. However, whenever you do need a modified object of that new type you must suffer the overhead of a new object creation, as well as potentially causing more frequent garbage collections. For some classes this is not a problem, but for others (such as the String class) it is prohibitively expensive. t Créer une classe immuable semble à première vue une solution élégante. Cependant, dès qu'on a besoin de modifier un objet de ce nouveau type, il faut supporter le coût supplémentaire de la création d'un nouvel objet, ce qui implique aussi un passage plus fréquent du ramasse-miettes. Cela n'est pas un problème pour certaines classes, mais cela est trop coûteux pour certaines autres (telles que la classes String).
t t t
The solution is to create a companion class that can be modified. Then, when you’re doing a lot of modifications, you can switch to using the modifiable companion class and switch back to the immutable class when you’re done. t La solution est de créer une classe compagnon qui, elle, peut être modifiée. Ainsi, quand on effectue beaucoup de modifications, on peut basculer sur la classe compagnon modifiable et revenir à la classe immuable une fois qu'on en a terminé.
t t t
The example above can be modified to show this: t L'exemple ci-dessus peut être modifié pour montrer ce mécanisme :
t t t
//: appendixa:Immutable2.java
// A companion class for making
// changes to immutable objects.

class Mutable {
  private int data;
  public Mutable(int initVal) {
    data = initVal;
  }
  public Mutable add(int x) {
    data += x;
    return this;
  }
  public Mutable multiply(int x) {
    data *= x;
    return this;
  }
  public Immutable2 makeImmutable2() {
    return new Immutable2(data);
  }
}

public class Immutable2 {
  private int data;
  public Immutable2(int initVal) {
    data = initVal;
  }
  public int read() { return data; }
  public boolean nonzero() { return data != 0; }
  public Immutable2 add(int x) {
    return new Immutable2(data + x);
  }
  public Immutable2 multiply(int x) {
    return new Immutable2(data * x);
  }
  public Mutable makeMutable() {
    return new Mutable(data);
  }
  public static Immutable2 modify1(Immutable2 y){
    Immutable2 val = y.add(12);
    val = val.multiply(3);
    val = val.add(11);
    val = val.multiply(2);
    return val;
  }
  // This produces the same result:
  public static Immutable2 modify2(Immutable2 y){
    Mutable m = y.makeMutable();
    m.add(12).multiply(3).add(11).multiply(2);
    return m.makeImmutable2();
  }
  public static void main(String[] args) {
    Immutable2 i2 = new Immutable2(47);
    Immutable2 r1 = modify1(i2);
    Immutable2 r2 = modify2(i2);
    System.out.println("i2 = " + i2.read());
    System.out.println("r1 = " + r1.read());
    System.out.println("r2 = " + r2.read());
  }
} ///:~
t
//: appendixa:Immutable2.java
// Une classe compagnon pour modifier
// des objets immuables.

class Mutable {
  private int data;
  public Mutable(int initVal) {
    data = initVal;
  }
  public Mutable add(int x) {
    data += x;
    return this;
  }
  public Mutable multiply(int x) {
    data *= x;
    return this;
  }
  public Immutable2 makeImmutable2() {
    return new Immutable2(data);
  }
}

public class Immutable2 {
  private int data;
  public Immutable2(int initVal) {
    data = initVal;
  }
  public int read() { return data; }
  public boolean nonzero() { return data != 0; }
  public Immutable2 add(int x) {
    return new Immutable2(data + x);
  }
  public Immutable2 multiply(int x) {
    return new Immutable2(data * x);
  }
  public Mutable makeMutable() {
    return new Mutable(data);
  }
  public static Immutable2 modify1(Immutable2 y){
    Immutable2 val = y.add(12);
    val = val.multiply(3);
    val = val.add(11);
    val = val.multiply(2);
    return val;
  }
  // Ceci produit le même résultat :
  public static Immutable2 modify2(Immutable2 y){
    Mutable m = y.makeMutable();
    m.add(12).multiply(3).add(11).multiply(2);
    return m.makeImmutable2();
  }
  public static void main(String[] args) {
    Immutable2 i2 = new Immutable2(47);
    Immutable2 r1 = modify1(i2);
    Immutable2 r2 = modify2(i2);
    System.out.println("i2 = " + i2.read());
    System.out.println("r1 = " + r1.read());
    System.out.println("r2 = " + r2.read());
  }
} ///:~
t t t
Immutable2 contains methods that, as before, preserve the immutability of the objects by producing new objects whenever a modification is desired. These are the add( ) and multiply( ) methods. The companion class is called Mutable, and it also has add( ) and multiply( ) methods, but these modify the Mutable object rather than making a new one. In addition, Mutable has a method to use its data to produce an Immutable2 object and vice versa. t Immutable2 contient des méthodes qui, comme précédemment, préservent l'immuabilité des objets en créant de nouveaux objets dès qu'une modification est demandée. Ce sont les méthodes add() et multiply(). La classe compagnon est appelée Mutable, et possède aussi des méthodes add() et multiply(), mais ces méthodes modifient l'objet Mutable au lieu d'en créer un nouveau. De plus, Mutable possède une méthode qui utilise ses données pour créer un objet Immutable2 et vice-versa.
t t t
The two static methods modify1( ) and modify2( ) show two different approaches to producing the same result. In modify1( ), everything is done within the Immutable2 class and you can see that four new Immutable2 objects are created in the process. (And each time val is reassigned, the previous object becomes garbage.) t Les deux méthodes static modify1() et modify2() montrent deux approches différentes pour arriver au même résultat. Dans modify1(), tout est réalisé dans la classe Immutable2 et donc quatre nouveaux objets Immutable2 sont créés au cours du processus (et chaque fois que val est réassignée, l'instance précédente est récupérée par le ramasse-miettes).
t t t
In the method modify2( ), you can see that the first action is to take the Immutable2 y and produce a Mutable from it. (This is just like calling clone( ) as you saw earlier, but this time a different type of object is created.) Then the Mutable object is used to perform a lot of change operations without requiring the creation of many new objects. Finally, it’s turned back into an Immutable2. Here, two new objects are created (the Mutable and the result Immutable2) instead of four. t Dans la méthode modify2(), on peut voir que la première action réalisée est de prendre l'objet Immutable2 y et d'en produire une forme Mutable (c'est comme si on appelait clone() vue précédemment, mais cette fois un différent type d'objet est créé). L'objet Mutable est alors utilisé pour réaliser un grand nombre d'opérations sans nécessiter la création de nombreux objets. Puis il est retransformé en objet Immutable2. On n'a donc créé que deux nouveaux objets (l'objet Mutable et le résultat Immutable2) au lieu de quatre.
t t t
t t t
t t
\\\
///
t t t
t
     
Sommaire Le site de Bruce Eckel