Почему этот класс считается изменяемым?

public class A {
    private int[] values;

    public int[] getValues() {
        return values;
    }
}

В книге, которую я читаю, говорится, что он не является неизменным, поскольку values является ссылочным типом. Я не вижу другого способа изменить значения, кроме как создать основной метод внутри класса.


person j.doe    schedule 30.08.2017    source источник


Ответы (4)


Он изменчив, потому что вы можете изменить содержимое values с помощью метода, который вы указали.

A obj = new A(...) // values gets filled here
int[] leak = obj.getValues()
leak[0] = 42

Это будет означать, что ваше свойство values теперь содержит информацию, которая была изменена извне. Чтобы сделать его неизменяемым, вы должны вернуть копию массива, чтобы вы были уверены, что никакой внешний агент не может его изменить, и вы должны сделать его окончательным, чтобы не могли быть созданы подклассы, которые могут раскрыть состояние и снова сделать его изменяемым. :

public final class A {
  private int[] values;

  public int[] getValues() {
    return Arrays.copyOf(values);
  }
}
person Mario F    schedule 30.08.2017
comment
Как вы присваиваете значения элементам нулевого массива? - person Andy Turner; 30.08.2017
comment
Я имею в виду, что values не является окончательным, поэтому ссылка будет начинаться как null, но может быть установлена ​​​​на что угодно в другой части класса. - person Mario F; 31.08.2017
comment
Но в этом случае мы можем легко изменить значения с помощью отражения, не так ли? Я думаю, что мы должны разделить неизменяемые классы и неизменяемые объекты, потому что это неизменяемый класс, но это не неизменяемый объект, потому что значения не являются окончательными. Даже если автор предполагает, что где-то еще мы меняем значения, значит, у нас есть метод или конструктор. Если это конструктор, то, вероятно, все в порядке (значения генерируются, но все же ..reflection), если это метод, мы можем легко его изменить. - person egorlitvinenko; 31.08.2017
comment
@egorlitvinenko каким образом ваша неизменная версия защищена от отражающей модификации? - person Andy Turner; 31.08.2017
comment
@AndyTurner Если в JVM включен SecurityManager, вы не можете изменить конечное поле с отражением. Вы знаете что-то еще? - person egorlitvinenko; 31.08.2017
comment
@egorlitvinenko ты же можешь менять элементы, не так ли? - person Andy Turner; 31.08.2017
comment
@AndyTurner, что ты имеешь в виду под моей неизменной версией? Я имею в виду неизменный класс и объект. Если вы предоставите копию массива, вы не сможете изменить элемент. И в этом ответе я имею в виду, что есть только неизменяемый класс, и Arrays.copyOf не имеет значения, потому что мы можем изменять значения. - person egorlitvinenko; 31.08.2017

На самом деле он не изменяется через массив values, как указано в других ответах: этот массив null, так как он никогда не инициализируется, и вы не можете сделать его ненулевым с помощью результата геттера.

Он изменчив, потому что вы можете создать подкласс и добавить изменяемое состояние:

class AA extends A {
  String foo;
}

AA aa = new AA();
aa.foo = "foo";
A a = aa;
aa.foo = "bar";  // Mutates the state of a.

Обратите внимание, что существует "Стратегия определения неизменяемых объектов" в руководстве по Oracle Java, а также список обязательных свойств неизменяемых объектов в Effective Java 2nd Ed, пункт 15: "Минимизируйте изменчивость".

person Andy Turner    schedule 30.08.2017
comment
int[] array = (new A()).getValues(); array = new int[5](); сам массив тоже изменчив... - person geneSummons; 31.08.2017
comment
@geneSummons ага. И каково значение array? - person Andy Turner; 31.08.2017
comment
ну технически это правильно. Но в любой реальной программе values, вероятно, будет заполнен некоторыми данными, которые вызывающая сторона может изменить. Так что да, поскольку этот класс не является изменяемым (при условии, что вы сделаете его final), но на практике он все еще не предназначен для неизменности. - person Mario F; 31.08.2017
comment
Почему вы создаете еще один класс и предполагаете, что он все равно должен быть неизменным? - person egorlitvinenko; 31.08.2017
comment
@egorlitvinenko Я создаю еще один подкласс, чтобы показать, что он изменяемый. - person Andy Turner; 31.08.2017
comment
@geneSummons, а каково значение массива в A после того, как вы напишете array = new int[5];? Вы просто переназначаете локальную переменную, а не переменную-член. - person Andy Turner; 31.08.2017
comment
О, я понимаю. Immutable Objects/Immutable Classes, да, вы частично правы. И вы частично не правы, потому что мы можем изменить foo через отражение в A. - person egorlitvinenko; 31.08.2017
comment
@Andy Turner Поскольку объявление члена массива в классе A не равно final, любая ссылка на экземпляр этого массива может быть повторно инициализирована новым значением ссылки. Повторная инициализация может повлиять или не повлиять на ссылку внутри экземпляра класса A, это зависит от области действия блока кода, в котором происходит повторная инициализация. - person geneSummons; 31.08.2017
comment
@geneSumons попробуйте. Разместите ссылку на демонстрацию ideone, демонстрирующую поведение, которое вы описываете. (вот один, который я сделал ранее) - person Andy Turner; 31.08.2017
comment
@AndyTurner Интерфейс, который определяет A, по-прежнему неизменяем, values является частным. Поправьте меня, если я ошибаюсь (не занимаюсь Java регулярно), но любой неокончательный класс Java (по вашему) определению изменчив. - person jasper; 31.08.2017
comment
@джаспер правильно. Ознакомьтесь с пунктом 15 Effective Java 2nd Ed: это пункт 2 в списке. - person Andy Turner; 31.08.2017
comment
@AndyTurner ideone.com/yn9C9T - person geneSummons; 31.08.2017
comment
@geneSummons вы знаете, что у ideone отключены утверждения, верно? ideone.com/lITL0w. - person Andy Turner; 31.08.2017
comment
@AndyTurner Я не знал, что на ideone.com утверждения отключены по умолчанию. Я также не знал, что в моей локальной среде IDE также по умолчанию отключены утверждения для запуска DEBUG проектов Java. Я исправил сенсей. - person geneSummons; 31.08.2017
comment
@AndyTurner Сейчас этой книги нет в наличии. Размышляя об этом, я бы по-прежнему считал A неизменным. Состояние, которое представляет A (ошибочно названное interface в моем предыдущем комментарии), не может быть изменено. Любой потребитель A может (с точки зрения (не)изменяемости) не быть нарушен путем передачи ему экземпляра производного типа. Есть ли у вас доступный источник пунктов, предположительно сделанных в Effective Java? - person jasper; 31.08.2017
comment
@jasper найдите PDF-файл EJ2 в своей любимой поисковой системе. Это на странице 73. Не забывайте, что интерфейс A также включает в себя все методы Object, так что вы можете открыть изменяемое состояние, например. через toString() или hashCode(). - person Andy Turner; 31.08.2017

values, будучи массивом, является ссылочным типом. Вы можете узнать больше о ссылках здесь. Это означает, что когда getValues() возвращает values, он фактически возвращает указатель на него (который каждый раз разыменовывается, поскольку в Java нет явных указателей). Хотя саму ссылку нельзя переназначить, поскольку values является частной, ее содержимое можно изменить.

Таким образом, что-то вроде a.getValues()[0]++ будет увеличивать первый элемент values, если он не пуст.

person Calvin Li    schedule 30.08.2017

Вы можете получить ссылку и изменить массив элементов:

 A.getValues()[0] = -100500

Вы можете использовать отражение для изменения поля значений или где-то еще (возможно, автор имеет в виду) или с наследованием, как показано ниже.

Например,

Неизменяемый класс:

import java.util.Arrays;

public final class A {
    private int[] values;

    public int[] getValues() {
        return values;
    }
}

Неизменяемый объект:

import java.util.Arrays;

public class A {
    private final int[] values;

    public A(int[] values) {
        this.values = null == values ? null : Arrays.copyOf(values);
    }

    public int[] getValues() {
        return Arrays.copyOf(values);
    }
}

Неизменный класс и объект:

import java.util.Arrays;

public final class A {
    private final int[] values;

    public A(int[] values) {
        this.values = null == values ? null : Arrays.copyOf(values);
    }

    public int[] getValues() {
        return Arrays.copyOf(values);
    }
}
person egorlitvinenko    schedule 30.08.2017
comment
Это не неизменно. Вам нужно взять защитную копию values в ctor, и создать класс final. - person Andy Turner; 31.08.2017
comment
Да, вы правы, я поправил. - person egorlitvinenko; 31.08.2017