Un bon moyen de faire en sorte que deux objects immuables se réfèrent

Prenez ces deux classes Java:

class User { final Inventory inventory; User (Inventory inv) { inventory = inv; } } class Inventory { final User owner; Inventory (User own) { owner = own; } } 

Existe-t-il un moyen de ne pas utiliser la reflection * ? En fait, je ne m’attends pas à ce que ce soit le cas, mais rien ne fait mal de demander.

Mise à jour: Étant donné que la construction de code intermédiaire comporte deux étapes (1. atsortingbuer un object, 2. constructeur de l’appel **), cela pourrait-il (ab) être utilisé à cette fin, avec du code secondaire manuscrit ou un compilateur personnalisé? Je parle d’exécuter l’étape 1 pour les deux objects d’abord, puis l’étape 2 pour les deux, en utilisant les références de l’étape 1. Bien entendu, une telle démarche serait assez lourde et cette partie de la question est théorique.

(* Parce que la reflection peut poser problème avec un responsable de la sécurité)

(** Dit ma connaissance limitée)

Cela ne peut fonctionner correctement que si l’un des objects est créé par l’autre. Par exemple, vous pouvez modifier votre classe d’ User comme suit (tout en conservant la classe d’ Inventory inchangée):

 class User { private final Inventory inventory; User () { inventory = new Inventory(this); } } 

Vous devez toutefois faire attention à l’access à l’object User dans le constructeur Inventory : il n’est pas encore complètement initialisé. Par exemple, son champ d’ inventory sera toujours null !

Mise à jour de l’annonce: j’ai maintenant vérifié que l’approche de manipulation par code intermédiaire ne fonctionnait pas . Je l’ai essayé en utilisant Jasmin et il a toujours échoué à charger avec un VerifyError .

En approfondissant le sujet, j’ai trouvé le § 4.10.2.4 Méthodes d’initialisation d’instance et objects nouvellement créés . Cette section explique comment la machine virtuelle Java garantit que seules les instances d’object initialisées sont transmises.

Vous pouvez le faire si vous n’avez pas besoin d’injecter l’un des objects.

 class User { private final Inventory inventory; User () { inventory = new Inventory(this); } } 
 class User { private final Inventory inventory; User (/*whatever additional args are needed to construct the inventory*/) { //populate user fields inventory = new Inventory(this); } } class Inventory { private final User owner; Inventory (User own) { owner = own; } } 

C’est le mieux que je puisse penser. Peut-être qu’il y a un meilleur modèle.

Légèrement pédant, mais il n’est pas à proprement parler nécessaire de créer l’un à l’intérieur de l’autre, si vous le voulez bien. Ils pourraient tous deux être des classes intérieures.

 public class BadlyNamedClass { private final User owner; private final Inventory inventory; public BadlyNamedClass() { this.owner = new User() { ... has access to BadlyNamedClass.this.inventory; }; this.inventory = new Inventory() { ... has access to BadlyNamedClass.this.owner; }; } ... } 

Ou même:

 public class BadlyNamedClass { private final User owner; private final Inventory inventory; public BadlyNamedClass() { this.owner = new User(this); this.inventory = new Inventory(this); } public User getOwner() { return owner; } public Inventory getInventory() { return inventory; } ... } 

C’est une “solution”, bien que la perte d’un final soit pas pratique.

 class User { Inventory inventory; User () { } // make sure this setter is only callable from where it should be, // and is called only once at construction time setInventory(inv) { if (inventory != null) throw new IllegalStateException(); inventory = inv; } } class Inventory { final User owner; Inventory (User own) { owner = own; } } 

Si vous êtes uniquement intéressé par le bytecode de la machine virtuelle Java et que le codage Java ne vous intéresse pas, utiliser Scala ou Clojure pourrait peut-être vous aider. Vous aurez besoin d’une sorte de machine letrec .

B: “L’inventaire créé par l’utilisateur est notre dernier espoir”.
Y: “Non, il y en a un autre.”

Si vous résumez les références à une tierce partie, vous pouvez contrôler la relation entre elles.

Par exemple.

 public class User { private final Ssortingng identifier; // uniquely identifies this User instance. public User(final Ssortingng myIdentifier) { identifier = myIdentifier; InventoryReferencer.registerBlammoUser(identifier); // Register the user with the Inventory referencer. } public Inventory getInventory() { return InventoryReferencer.getInventoryForUser(identifier); } } public interface Inventory // Bam! { ... nothing special. } // Assuming that the Inventory only makes sence in the context of a User (ie User must own Inventory). public class InventoryReferencer { private static final Map referenceMap = new HashMap(); private InventoryReferencer() { throw ... some exception - helps limit instantiation. } public static void registerBlammoUser(final Ssortingng identifier) { InventoryBlammo blammo = new InventoryBlammo(); referenceMap.add(indentifier, blammo); } public static void registerKapowUser(final Ssortingng identifier) { InventoryBlammo kapow = new InventoryKapow(); referenceMap.add(indentifier, kapow); } public static Inentory getInfentoryForUser(final Ssortingng identifier) { return referenceMap.get(identifier); } } // Maybe package access constructors. public class InventoryBlammo implements Inventory { // a Blammo style inventory. } public class InventoryKapow implements Inventory { // a Kapow style inventory. }