15

Take these two Java classes:

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

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

Is there any way without using reflection* to pull this off? I don't actually expect it is, but it can't hurt to ask.

Update: Since in bytecode construction has two steps (1. allocate object, 2. call constructor**) could this be (ab)used to do this, with handwritten bytecode or a custom compiler? I'm talking about performing step 1 for both objects first, then step 2 for both, using references from step 1. Of course something like that would be rather cumbersome, and this part of the question is academic.

(* Because reflection may give trouble with a security manager)

(** Says my limited knowledge)

Community
  • 1
  • 1
Bart van Heukelom
  • 43,244
  • 59
  • 186
  • 301
  • 1
    circular dependencies are not often a good idea – Bozho Nov 02 '11 at 10:13
  • I'm sorry, could you elaborate a bit on the problem. I don't see what the problem is, since you already have a reference to the other object? – Anders Nov 02 '11 at 10:13
  • @Anders the problem is that both require a reference to the other at construction time, and such a reference is only available after construction. – Bart van Heukelom Nov 02 '11 at 10:17

7 Answers7

13

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.

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
  • Any idea about bytecode tricks (see updated question)? What I describe there is probably impossible though. – Bart van Heukelom Nov 02 '11 at 10:39
  • @BartvanHeukelom: theoretically it could work. I don't know if the JVM really likes a `new` without the correct `invokespecial` to invoke the constructor. Let me try that ... – Joachim Sauer Nov 02 '11 at 11:26
  • There were changes to 1.4 (after JVSpec 2nd Ed.) that allowed certain access to initialise final fields outside of the class. I can't remember the exact changes. – Tom Hawtin - tackline Nov 02 '11 at 14:11
  • @TomHawtin-tackline the [changes made in 1.4](https://bugs.openjdk.java.net/browse/JDK-4030374) were about assigning fields before the super constructor call, but did not affect the specification; it was HotSpot’s verifier which didn’t conform. – Holger Sep 18 '19 at 16:00
8

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);
   }
}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
3
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;
    }
}

That's the best I can think of. Maybe there's a better pattern.

G_H
  • 11,739
  • 3
  • 38
  • 82
1

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; }
    ...
}
Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • I find this a pretty good solution. The relationship between user and inventory is something that has it's own purpose and therefore deserves its own class. Referring to another object may also mean that you need to go the extra way to ask someone to whom an instance is referring to. – SpaceTrucker Nov 19 '13 at 09:40
  • Well thought, however there is still a problem since your third immutable object is escaping in its constructor. – Alexandre Dupriez Feb 21 '14 at 09:19
  • @AlexandreDupriez Which? `final` field semantics, etc., of `owner` and `inventory` are irrelevant. They are covered by those of `BadlyNamedClass`. (`String` which is typically implemented using a `char[]` is the canonical example of this.) – Tom Hawtin - tackline Feb 21 '14 at 09:44
  • @TomHawtin-tackline: I mean the `this` in the constructor of `BadlyNamedClass`. You may argue that you know that `User` and `Inventory` won't do something harmful with the uninstantiated `this` `BadlyNamedClass`, but that's because you have the full control over the implementation. What would it be if `User` and `Inventoy` were client objects which enforced this pattern? What do you think? – Alexandre Dupriez Feb 25 '14 at 10:03
  • @AlexandreDupriez They could expose themselves through the passed in arguments or a global of some form, but that's an orthogonal issue. Those would be broken classes whatever any other code did to them. – Tom Hawtin - tackline Feb 25 '14 at 11:01
  • @TomHawtin-tackline: Erf... Not sure I understand well. But yes, indeed it is another issue. – Alexandre Dupriez Feb 25 '14 at 15:10
0

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;
   }
}
Bart van Heukelom
  • 43,244
  • 59
  • 186
  • 301
  • 1
    Right. You no longer have two immutable objects, which the title of your question sort of states as a premise ;) – aioobe Nov 02 '11 at 10:19
  • @aioobe Indeed, so I edited it and put "solution" in quotes. It's still immutable for the outside world though, because there is no setter they can use. – Bart van Heukelom Nov 02 '11 at 10:21
  • Right, unless you consider other classes in the same package to be the outside world :P – aioobe Nov 02 '11 at 10:27
  • @aioobe One could use the rather shady method of checking the caller using a new exception's stracktrace. However, that would couple the class to the factory, in which case the class might as well be its own factory. A slightly better trick would be to deprecate the setter and suppress the warning in the place where it may be used. – Bart van Heukelom Nov 02 '11 at 10:29
0

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.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
0

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.
}
DwB
  • 37,124
  • 11
  • 56
  • 82