Java автоматически обновляет все ссылки при замене (не изменении) объекта?

Итак, у меня есть ArrayList объектов моего собственного класса:

public class A
{...}

Теперь предположим, что у меня есть переменная, которая содержит один из этих объектов из списка. Теперь я знаю, что если я изменю этот объект (например, установлю поле для этого объекта), это изменит память, в которой хранится сам объект, поэтому обе ссылки (переменная и внутренняя переменная массива, которая содержит объект в списке) по-прежнему идентичны, и оба по-прежнему показывают один и тот же, теперь измененный, объект. Нормальное поведение!

Но я также хочу, чтобы это было так, когда я заменяю объект в списке, на который указывает моя переменная. Вот когда я делаю

myList.set(theObjectPosition, newInstanceOfA);

... в этом случае я также хочу, чтобы моя переменная автоматически имела ссылку на этот новый экземпляр. Каков был бы самый простой способ реализовать такое поведение?

Вот почему мне это нужно:

Короче говоря, у меня есть набор классов, которые обрабатывают отмену/возврат изменений, внесенных в ArrayList (которые я не расширил, это все равно не решит проблему?). Классы называются

UndoAction, Add, Remove, Modify (the latter 3 inherit from UndoAction)

Все классы имеют методы undo() и redo().

Пример описания (при необходимости код находится в конце):

Скажем, я хочу добавить элемент в список: я делаю new Add(myObject), затем я хочу изменить элемент в списке, поэтому я делаю new Modify(myObject), после чего он создает DeepCopy состояния объекта в качестве резервной копии , после чего я ТОГДА изменяю сам объект (после вызова new Modify(..)). Затем я делаю undo() для объекта Modify, поэтому он делает list.set(somePos, previousDeepCopy) (следовательно, этот пост, глубокая копия, сделанная по своей природе, является новым экземпляром, который все портит!!!)....

Итак, вы можете себе представить, что этот list.set создает некоторые проблемы. Любая ссылка на замененный объект исчезнет. Таким образом, я не могу эффективно работать с таким списком, если он всегда заменяет ссылки на такие объекты, поэтому мой менеджер отмен таким образом обречен на провал.

Итак, как мне бороться с этими недостатками дизайна? Любые предложения приветствуются.


Некоторый код:

 protected abstract class UndoAction {
    protected HashSet<Integer> ids = new HashSet<Integer>();
    protected Marker marker;

    public UndoAction(List<E> l) {
      for (E e : l) {
        if (!entities.contains(e)) {
          entities.add(e);
          trace.append(entities.indexOf(e), new Stack<UndoAction>());
        }
        ids.add(entities.indexOf(e));
      }
    }

    public boolean sameAffectedTargets(UndoAction undoAction) {
      if (this.ids.containsAll(undoAction.ids) && undoAction.ids.containsAll(this.ids))
        return true;
      return false;
    }

    public Marker getMarker() {
      return new Marker(this);
    }

    public void firstRun() {
    }

    public void undo() {
    }

    public void redo() {
    }

    protected List<Data> getCopyEntities() {
      List<Data> l = new ArrayList<Data>();
      for (Integer id : ids) {
        E e = entities.get(id);
        int pos = adapterList.indexOf(e);
        l.add(new Data(id, pos, e));
      }
      return l;
    }

    protected List<Data> getDeepCopyEntities() {
      List<Data> l = new ArrayList<Data>();
      for (Integer id : ids) {
        E e = DeepCopy.copy(entities.get(id));
        int pos = adapterList.indexOf(entities.get(id));
        l.add(new Data(id, pos, e));
      }
      return l;
    }

    public void addEntities(List<Data> datas) {
      for (Data d : datas)
        d.addEntity();
    }

    public void setEntities(List<Data> datas) {
      for (Data d : datas)
        d.setEntity();
    }

    public void removeEntities(List<Data> datas) {
      for (Data d : datas)
        d.removeEntity();
    }

    protected class Data {
      public int id;
      public int pos;
      public E entity;

      public void addEntity() {
        entities.set(this.id, this.entity);
        adapterList.add(this.pos, this.entity);
      }

      public void setEntity() {
        entities.set(this.id, this.entity);
        E oldEntity = adapterList.get(this.pos);
        adapterList.set(this.pos, this.entity);
        notifyEntityReplaced(oldEntity, adapterList.get(this.pos), this.pos);
      }

      public void removeEntity() {
        entities.set(this.id, null);
        adapterList.remove(this.entity);
      }

      public Data(int id, int pos, E entity) {
        this.id = id;
        this.pos = pos;
        this.entity = entity;
      }
    }
  }

  protected class Add extends UndoAction {
    protected List<Data> addBackup;

    public Add(List<E> l) {
      super(l);
    }

    @Override
    public void undo() {
      super.undo();
      addBackup = getCopyEntities();
      removeEntities(addBackup);
    }

    @Override
    public void firstRun() {
      super.firstRun();
      adapterList.addAll(entities);
    }

    @Override
    public void redo() {
      super.redo();
      addEntities(addBackup);
    }
  }

  // call before modifying
  protected class Modify extends UndoAction {
    protected List<Data> beforeDeepCopies;
    protected List<Data> afterDeepCopies;

    public Modify(List<E> l) {
      super(l);
    }

    @Override
    public void undo() {
      super.undo();
      if (!skipModifying) {
        if (afterDeepCopies == null)
          afterDeepCopies = getDeepCopyEntities();
        setEntities(beforeDeepCopies);
      }
    }

    @Override
    public void firstRun() {
      super.firstRun();
      if (!skipModifying) // TODO
        beforeDeepCopies = getDeepCopyEntities();
    }

    @Override
    public void redo() {
      super.redo();
      if (!skipModifying)
        setEntities(afterDeepCopies);
    }
  }

  protected class Remove extends UndoAction {
    protected List<Data> removeBackup;

    public Remove(List<E> l) {
      super(l);
    }

    public List<E> getRemoved() {
      List<E> l = new ArrayList<E>();
      for (Data data : removeBackup)
        l.add(data.entity);
      return l;
    }

    @Override
    public void undo() {
      super.undo();
      addEntities(removeBackup);
    }

    @Override
    public void firstRun() {
      super.firstRun();
      removeBackup = getCopyEntities();
    }

    @Override
    public void redo() {
      super.redo();
      removeEntities(removeBackup);
    }
  }

person cdbeelala89    schedule 30.01.2013    source источник
comment
Я правильно понимаю, что проблема в том, что вы также хотите иметь возможность переделать и операцию, которую вы отменили? Я не уверен, что правильно понял - разве ваша операция Modify не должна просто возвращать старый объект, если вы выполняете отмену ()?   -  person kutschkem    schedule 30.01.2013


Ответы (4)


Я думаю, вам нужен дополнительный уровень косвенности. Вместо того, чтобы другой код ссылался на «A», вы, вероятно, захотите, чтобы он ссылался на «Stack‹A›» и использовал «peek()» для ссылки на текущее значение (когда вы изменяете значение в массиве, вы будете вероятно, хотите "push()" новое значение).

Однако ради чистоты кода вы можете сделать это немного аккуратнее, превратив «A» в интерфейс и создав реализацию «A» (назовем ее «DelegatingStackAImpl»), которая будет предоставлять тот же интерфейс, что и «A». за исключением поддержки «push()» и «pop()» и переадресации вызовов тому экземпляру, который в данный момент находится на вершине стека. Таким образом, вы не нарушите существующее использование «А», но при этом будете поддерживать этот дополнительный уровень косвенности.

person Michael Aaron Safyan    schedule 30.01.2013

Если ссылки на объекты в Java (ClassName) похожи на указатели C (ClassName*), вам, похоже, нужно поведение, подобное указателям C на указатели (ClassName**).

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

public class AHolder{
    public A aRef; // or private with getter setter.
}
person Karthik T    schedule 30.01.2013

Самый простой способ - просто убедиться, что ваш класс является "изменчивым", то есть вы можете изменить внутреннее состояние с помощью одного из новых объектов.

myOldObject.replaceValues(myNewObject);

Если сам объект по какой-то причине нельзя сделать изменяемым, просто создайте вокруг него обертку.

person SJuan76    schedule 30.01.2013

Если на объект, который вы хотите заменить, ссылаются только через его интерфейс, вы можете использовать динамический прокси-сервер JDK в первую очередь, который делегирует фактическую реализацию. Чтобы изменить фактическую реализацию, вам нужно изменить обработчик вызова прокси.

person SpaceTrucker    schedule 30.01.2013