Любой хороший способ сделать два неизменных объекта ссылаются друг на друга? - PullRequest
14 голосов
/ 02 ноября 2011

Возьмите эти два 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 для обоих объектов сначала, затем шага 2 для обоих, используя ссылки из шага 1. Конечно, что-то подобное будет довольно громоздким, и эта часть вопроса является академической.

(* Потому что рефлексия может создать проблемы с менеджером по безопасности)

(** говорит, что мои ограниченные знания)

Ответы [ 7 ]

13 голосов
/ 02 ноября 2011

Это может работать чисто, только если один из объектов создан другим. Например, вы можете изменить класс User на что-то вроде этого (оставив класс Inventory без изменений):

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

Вы должны быть осторожны с доступом к объекту User в конструкторе Inventory, однако: он еще не полностью инициализирован. Например, его inventory поле все равно будет null!

Обновление объявления: Теперь я убедился, что подход манипуляции с байт-кодом не работает . Я пробовал использовать Jasmin , и он всегда не загружался с VerifyError.

Углубившись в проблему, я обнаружил § 4.10.2.4 Методы инициализации экземпляра и вновь созданные объекты . В этом разделе объясняется, как JVM обеспечивает передачу только инициализированных экземпляров объекта.

7 голосов
/ 02 ноября 2011

Вы можете сделать это, если вам не нужно вводить один из объектов.

class User {
   private final Inventory inventory;
   User () {
       inventory = new Inventory(this);
   }
}
3 голосов
/ 02 ноября 2011
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;
    }
}

Это лучшее, что я могу придумать. Может быть, есть лучшая модель.

1 голос
/ 02 ноября 2011

Немного педантичный, но строго говоря, нет необходимости создавать одно внутри другого, если вы не возражаете против небольшой косвенности. Они оба могут быть внутренними классами.

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; }
    ...
}
0 голосов
/ 02 ноября 2011

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.
}
0 голосов
/ 02 ноября 2011

Если вы заинтересованы только в байт-коде JVM и не заботитесь о программировании на Java, возможно, может помочь использование Scala или Clojure .Вам понадобится какое-то letrec оборудование.

0 голосов
/ 02 ноября 2011

Это одно «решение», хотя потеря одного 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;
   }
}
...