Как сохранить параметр объекта неизменным в выполняемом классе в Java?


У меня есть управляемый класс, такой как:

Class R1 implements Runnable {
  private static final Log LOGGER = LogFactory.getLog(R1.class);
  private final ObjectClass obj;
  private final SomeService service;

  public R1(ObjectClass obj, SomeService service) {
     this.obj = obj;
     this.service = service;
  }

  @override
  public void run() {
    String value = this.obj.getSomeValue();
    LOGGER.debug("Value is " + value);
    // some actions, such as:
    // service.someMethod(obj);
  }
}

Я использую объект ExecutorService для выполнения R1 и помещаю R1 в очередь. Но позже вне R1 я изменяю значение в ObjectClass, который я передал в R1, поэтому действия в R1 после getSomeValue () ведут себя не так, как я ожидал. Если я хочу сохранить значение ObjectClass object в R1 неизменным, что я могу сделать? Предположим, объект большой и имеет множество методов get и set.

Чтобы сделать задачу более ясной, мне нужно передать obj в a объект класса обслуживания, который также используется в качестве параметра в классе runnable. Я изменил исходные коды соответствующим образом.

7 5

7 ответов:

Согласно комментариям, очевидно, Мое предлагаемое решение имеет проблемы.

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


Я предлагаю вам реализовать метод clone, который создает новый пример.

Http://download.oracle.com/javase/1,5,0/docs/api/java/lang/Cloneable.html

Проблема здесь в том, что вы передали экземпляр в свой R1class, но это все тот же единственный экземпляр, поэтому изменения в нем повлияют на все остальное. Таким образом, реализация метода клонирования позволит вам легко создать копию вашего экземпляра, которая может быть использована в вашем классе R1, позволяя вам вносить дальнейшие изменения в ваш оригинал.

В вашем классе R1,

public R1(ObjectClass obj) {
   //this.obj = obj;
   this.obj = obj.clone();
}

P.S. Вы должны реализовать этот метод самостоятельно. Это не просто автоматически даст вам глубокую копию.

В зависимости от характера вашей программы, есть несколько вариантов.

Вы можете "разумно переопределить клонирование" (пункт 11 вэффективной Java ) и клонировать объект, прежде чем передать его исполняемому. Если переопределение clone не работает для вас, возможно, лучше сделать одно из следующих действий:

  1. создайте новый экземпляр объекта вручную и скопируйте значения из obj.
  2. добавьтеподмножество данных, содержащихся в obj. Так что вместо передавая obj в конструктор, вы бы передали в someValue. Я бы поддержал этот метод, чтобы Вы только снабжали R1 необходимыми данными, а не всем объектом.

Альтернативно, если не имеет значения, что данные в obj изменяются до выполнения R1, то вам только Нужно убедиться, что obj не изменяется , Пока R1 исполняет. В этом случае вы можете добавить ключевое слово synchronize в метод getSomeValue()., и тогда придется R1 синхронизировать на obj Вот так:

@Override
public void run() {
  synchronize (obj) {
    String value = obj.getSomeValue();
  }
  // some actions.
}

Передайте объект конструктору и не сохраняйте ссылку на него.

Если объект слишком большой,

Может быть, лучше использовать неизменяемый ParameterObject с достаточным количеством данных / методов.

Если возможно, попробуйте сделать свой ObjectClass неизменяемым. (изменения состояния не поддерживаются). В Java вы должны "сделать это сами"; нет понятия объекта const (как в C++)

Возможно, вы можете иметь свой orig ObjectClass, но создать новый класс ImmutableObjectClass, который принимает ваш orig в ctor.

Предположение: вас не волнует, если R1 работает со старыми данными.

Затем вы можете изменить свой код на:

public class R1 implements Runnable {
  private final String value;

  // Option 1: Pull out the String in the constructor.
  public R1(ObjectClass obj) {
    this.value = obj.getSomeValue(); // Now it is immutable
  }

  // Option 2: Pass the String directly into the constructor.
  public R1(String value) {
    this.value = value; // This constructor has no coupling
  }

  @Override public void run() {
    // Do stuff with value
  }
}

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

"Проблема" здесь заключается в том, что в соответствии с моделью памяти Java потоки могут (и делают) кэшировать значения полей. Это означает, что если один поток обновляет поле (объекта ObjectClass), другие потоки не будут "видеть" изменения - они все равно будут смотреть на свое кэшированное (устаревшее) значение.

Чтобы сделать изменения видимыми в потоках, у вас есть два варианта:

  1. сделайте поля, которые вы будете изменять в ObjectClass volatile - ключевое слово volatile заставляет потоки не кэшировать значение поля (т. е. всегда использовать последнее значение)
  2. синхронизировать доступ, как чтение, так и запись, к полям-все изменения, сделанные в синхронизированном блоке, видны другим потокам, синхронизирующимся с тем же объектом блокировки (если вы синхронизируете методы, объект this используется в качестве блокировки)