Any nice way to make two immutable objects refer to eachother?

后端 未结 7 2328
自闭症患者
自闭症患者 2021-02-13 04:53

Take these two Java classes:

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

class Inventory {
   final User         


        
相关标签:
7条回答
  • 2021-02-13 05:16

    This is one "solution", though the loss of one final is inconvenient.

    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;
       }
    }
    
    0 讨论(0)
  • 2021-02-13 05:22

    Slightly pedantic, but it's not strictly speaking necessary to create one inside the other, if you don't mind a little indirection. They could both be inner classes.

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

    Or even:

    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; }
        ...
    }
    
    0 讨论(0)
  • 2021-02-13 05:22

    If you are only interested in JVM bytecode and don't care about coding in Java specifically, perhaps using Scala or Clojure could help. You'll need some kind of letrec machinery.

    0 讨论(0)
  • 2021-02-13 05:26

    B: "Inventory created by the User is our last hope".
    Y: "No, there is another."

    If you abstract the references to a third party, you can control the relationship therein.

    For example.

    public class User
    {
        private final String identifier;  // uniquely identifies this User instance.
    
        public User(final String 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 (i.e. User must own Inventory).
    public class InventoryReferencer
    {
        private static final Map<String, Inventory> referenceMap = new HashMap<String, Inventory>();
    
        private InventoryReferencer()
        {
            throw ... some exception - helps limit instantiation.
        }
    
        public static void registerBlammoUser(final String identifier)
        {
            InventoryBlammo blammo = new InventoryBlammo();
            referenceMap.add(indentifier, blammo);
        }
    
        public static void registerKapowUser(final String identifier)
        {
            InventoryBlammo kapow = new InventoryKapow();
            referenceMap.add(indentifier, kapow);
        }
    
        public static Inentory getInfentoryForUser(final String 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.
    }
    
    0 讨论(0)
  • 2021-02-13 05:38

    This can only work cleanly if one of the objects is created by the other. For example you can change your User class to something like this (while keeping the Inventory class unchanged):

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

    You need to be careful about accessing the User object in the Inventory constructor, however: it's not fully initialized yet. For example, its inventory field will still be null!

    Ad Update: I've now verified that the bytecode-manipulation approach does not work. I've tried it using Jasmin and it always failed to load with a VerifyError.

    Delving deeper into the issue, I found§ 4.10.2.4 Instance Initialization Methods and Newly Created Objects. This section explains how the JVM ensures that only initialized object instances get passed around.

    0 讨论(0)
  • 2021-02-13 05:40

    You can do it if you don't need to inject one of the objects.

    class User {
       private final Inventory inventory;
       User () {
           inventory = new Inventory(this);
       }
    }
    
    0 讨论(0)
提交回复
热议问题