Любой хороший способ сделать два неизменных объекта касаются друг друга?
Возьмите эти два класса Java:
class User {
final Inventory inventory;
User (Inventory inv) {
inventory = inv;
}
}
class Inventory {
final User owner;
Inventory (User own) {
owner = own;
}
}
Есть ли способ без использования отражения *, чтобы снять это? На самом деле я этого не ожидаю, но это не может помешать спросить.
Обновление: поскольку в построении байткода есть два шага (1. выделить объект, 2. call constructor **), может ли это быть (ab) использоваться для этого, с рукописным байт-кодом или пользовательским компилятором? Я говорю о выполнении первого шага для обоих объектов, а затем для шага 2 для обоих, используя ссылки с шага 1. Конечно, что-то подобное было бы довольно громоздким, и эта часть вопроса является академической.
(* Потому что отражение может вызвать проблемы с менеджером безопасности)
(** говорит мои ограниченные знания)
Ответы
Ответ 1
Это может работать только чисто, если один из объектов создается другим. Например, вы можете изменить класс User
на что-то вроде этого (сохраняя класс Inventory
неизменным):
class User {
private final Inventory inventory;
User () {
inventory = new Inventory(this);
}
}
Однако вам нужно быть осторожным при доступе к объекту User
в конструкторе Inventory
: он еще не полностью инициализирован. Например, поле Inventory
все равно будет null
!
Обновление объявлений. Теперь я подтвердил, что метод управления байтовым кодом не работает . Я пробовал его с помощью Jasmin, и он всегда не загружался с помощью VerifyError
.
Вникая в проблему, я нашел § 4.10.2.4 Методы инициализации экземпляра и недавно созданные объекты. В этом разделе объясняется, как JVM обеспечивает передачу только инициализированных экземпляров объектов.
Ответ 2
Вы можете сделать это, если вам не нужно вводить один из объектов.
class User {
private final Inventory inventory;
User () {
inventory = new Inventory(this);
}
}
Ответ 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;
}
}
Это лучшее, что я могу придумать. Может быть, лучший образец.
Ответ 4
Слегка педантичный, но не строго говоря, необходимо создать одно внутри другого, если вы не возражаете против небольшой косвенности. Они оба могут быть внутренними классами.
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; }
...
}
Ответ 5
Это одно "решение", хотя потеря одного 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;
}
}
Ответ 6
Если вас интересует только байт-код JVM и не интересуется кодированием в Java, возможно, использование Scala или Clojure может помочь. Вам понадобится какая-то техника letrec
.
Ответ 7
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.
}