有什么好方法可以让两个不可变对象互相引用吗?

发布于 2024-12-13 15:10:53 字数 622 浏览 0 评论 0原文

以这两个Java类为例:

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

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

有没有办法不使用反射* 来完成这个任务吗?我实际上并不希望如此,但问一下也没什么坏处。

更新:由于字节码构造有两个步骤(1.分配对象,2.调用构造函数**),这可以(ab)用于通过手写字节码或自定义编译器来执行此操作吗?我说的是首先对两个对象执行步骤 1,然后使用步骤 1 中的引用对两个对象执行步骤 2。当然,类似的事情会相当麻烦,而且问题的这一部分是学术性的。

(* 因为反射可能会给安全经理带来麻烦)

(** 说我的知识有限)

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)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

预谋 2024-12-20 15:10:53

只有当其中一个对象由另一个对象创建时,这才能干净地工作。例如,您可以将 User 类更改为类似以下内容(同时保持 Inventory 类不变):

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

您需要小心访问 User但是,Inventory 构造函数中的 > 对象尚未完全初始化。例如,其 inventory 字段仍将为 null

广告更新:我现在已经验证字节码操作方法不起作用。我已经使用 Jasmin 尝试过,但它总是无法加载 VerifyError

深入研究这个问题,我发现§ 4.10.2.4 实例初始化方法和新创建的对象。本节说明 JVM 如何确保仅传递已初始化的对象实例。

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.

堇色安年 2024-12-20 15:10:53

如果您不需要注入其中一个对象,则可以这样做。

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

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);
   }
}
囚你心 2024-12-20 15:10:53
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;
    }
}

这是我能想到的最好的。也许有更好的模式。

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.

谷夏 2024-12-20 15:10:53

有点迂腐,但严格来说,如果您不介意一点间接的话,则没有必要在另一个内部创建一个。它们都可能是内部类。

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

或者甚至:

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

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; }
    ...
}
假扮的天使 2024-12-20 15:10:53

这是一种“解决方案”,尽管失去一个final 会带来不便。

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

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;
   }
}
瞎闹 2024-12-20 15:10:53

如果您只对 JVM 字节码感兴趣而不关心 Java 编码,那么使用 Scala 或 Clojure 可能会有所帮助。您需要某种 letrec 机制。

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.

怕倦 2024-12-20 15:10:53

B:“用户创建的库存是我们最后的希望”。
Y:“不,还有另一个。”

如果你将引用抽象为第三方,你就可以控制其中的关系。

例如。

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

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.
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文