Нужно ли защищенное копирование для построения неизменяемого класса с * не окончательными *, хотя неизменяемыми полями?
Не следует раскрывать ссылку на не конечное поле, если вы хотите создать неизменяемый класс - но даже для неизменяемых объектов, таких как Strings?
public final class Test { // Test class is meant to be immutable
private String s; // CAN'T MAKE THIS FINAL
void onCreate(String s) { // a callback called ONCE after construction
this.s = new String(s); // do I need to do this ? (protect me from me)
}
public String getS() {
return new String(s); //do I need to do this ?(protect me from the world)
}
}
4 ответа
Не имеет значения, является ли этот класс неизменным (для любого определения неизменяемого). В частности, это не имеет значения, если ссылка s
когда-либо изменяется, чтобы указать на другую строку. Строковый объект является неизменным, поэтому вам не нужно его копировать. Без защитного копирования, абоненты getS
получит ссылки на тот же строковый объект, который используется Test
методы и другие вызывающие getS
, Это не имеет значения, потому что ничего, что они делают с этой строкой, не повлияет на другие ссылки. Это было бы пустой тратой времени и памяти.
1 Я игнорирую рефлексию. Код, который злонамеренно использует отражение, как это, может сломать почти все, и не написан случайно или трудно обнаружить. Бесполезно даже беспокоиться об этом случае.
Теоретически, посредством небезопасной публикации можно увидеть пример Test
класс с неинициализированным (null
) s
который также можно увидеть с правильно инициализированным s
, Это можно исправить, сделав s
volatile
,
Однако, если у вас есть какой-то обратный вызов, я думаю, вы захотите еще раз взглянуть на свой дизайн.
Если бы вы должны были сделать класс Serializable
тогда у тебя будет намного больше проблем.
Я не думаю, что это необходимо. Даже в документации сказано:
Строки постоянны; их значения не могут быть изменены после их создания. Поскольку объекты String являются неизменяемыми, они могут использоваться совместно.
Поэтому после создания объекта String его значение никогда не изменяется. Если мы хотим "изменить" значение переменной, создается новый объект String. Такие как в toUpperCase
Метод, исходная строка не изменяется, но создается новая копия.
РЕДАКТИРОВАТЬ:
При рассмотрении строк литералы помещаются в общий пул, а это означает, что:
String h = "HELLO";
String h1 = "HELLO";
и то и другое s1
а также s2
ссылаются на тот же объект.
вы можете попробовать, что следующий код возвращает true
:
String h = "HELLO";
String h1 = "HELLO";
boolean r = (h==h1);
System.out.println(r);
Однако вы можете изменить значение String
Значение с помощью отражения:
java.lang.reflect.Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
valueField.set("Original", "Modified".toCharArray());
Технически, если вы действительно хотите неизменный класс в Java, вы должны убедиться, что экземпляр вашего класса не может быть изменен после его создания. Поэтому все его поля могут быть окончательными, и, если они, например, "выставлены" миру через геттеры, эти поля должны быть либо неизменяемыми сами (как строки), либо не возвращаться во внешний мир (храниться в секрете и создавать защитные копии). из них в геттеры), поэтому исходное значение поля остается прежним. Эта неизменность не должна быть склонной к нарушению путем наследования и от этого класса.
Вы можете прочитать об этом в Effective Java - книге Джошуа Блоха или сделать некоторые заметки из Интернета, например, здесь.
Что касается вашего недавнего обновления поста, вот предложение, которое гарантирует, что инициализация была сделана только один раз:
private String s; // CAN'T MAKE THIS FINAL
private boolean stringWasSet = false;
public void onCreate(String s) { // a callback called ONCE after construction
if (!stringWasSet) {
this.s = s; // No need for defensive copy here, if the variable itself is immutable, like String
stringWasSet = true;
}
}
public String getS() {
return s; // No need for defensive copy here, if the variable itself is immutable, like String
}