Дублирование объектов в Java
Я узнал, что когда вы изменяете переменную в Java, она не меняет переменную, на которой она основана на
int a = new Integer(5);
int b = a;
b = b + b;
System.out.println(a); // 5 as expected
System.out.println(b); // 10 as expected
Я принял аналогичную вещь для объектов. Рассмотрим этот класс.
public class SomeObject {
public String text;
public SomeObject(String text) {
this.setText(text);
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
После того, как я попробовал этот код, я смутился.
SomeObject s1 = new SomeObject("first");
SomeObject s2 = s1;
s2.setText("second");
System.out.println(s1.getText()); // second as UNexpected
System.out.println(s2.getText()); // second as expected
Пожалуйста, объясните мне, почему изменение любого из объектов влияет на другое. Я понимаю, что значение переменной текста сохраняется в одном и том же месте в памяти для обоих объектов.
Почему значения для переменных независимы, но коррелируются для объектов?
Также, как дублировать SomeObject, если простое присваивание не выполняет работу?
Ответы
Ответ 1
Каждая переменная в Java является ссылкой. Поэтому, когда вы делаете
SomeClass s2 = s1;
вы просто указываете s2
на тот же объект, на который указывает s1
. Фактически вы назначаете значение ссылки s1 (которое указывает на экземпляр SomeClass
) на s2. Если вы измените s1
, будет также изменен s2
(поскольку он указывает на тот же объект).
Существует исключение, примитивные типы: int, double, float, boolean, char, byte, short, long
. Они хранятся по значению. Поэтому при использовании =
вы присваиваете только значение, но не можете указывать на один и тот же объект (потому что это не ссылки). Это означает, что
int b = a;
устанавливает значение b
в значение a
. Если вы измените a
, b
не изменится.
В конце дня все назначается по значению, это просто значение ссылки, а не значение объекта (за исключением примитивных типов, как указано выше).
Итак, в вашем случае, если вы хотите сделать копию s1
, вы можете сделать это следующим образом:
SomeClass s1 = new SomeClass("first");
SomeClass s2 = new SomeClass(s1.getText());
В качестве альтернативы вы можете добавить конструктор копирования в SomeClass
, который принимает экземпляр в качестве аргумента и копирует его в свой собственный экземпляр.
class SomeClass {
private String text;
// all your fields and methods go here
public SomeClass(SomeClass copyInstance) {
this.text = new String(copyInstance.text);
}
}
С этим вы можете легко скопировать объект:
SomeClass s2 = new SomeClass(s1);
Ответ 2
@brimborium ответ очень хороший (+1 для него), но я просто хочу подробнее рассказать об этом, используя некоторые цифры. Сначала возьмем примитивное назначение:
int a = new Integer(5);
int b = a;
b = b + b;
System.out.println(a); // 5 as expected
System.out.println(b); // 10 as expected
int a = new Integer(5);
1. Первый оператор создает объект Integer значения 5. Затем, назначив его переменной a
, объект Integer будет распакован и сохранен в a
как примитив.
После создания объекта Integer и перед назначением:
![enter image description here]()
После назначения:
![enter image description here]()
int b = a;
2- Это просто прочитает значение a
, а затем сохранит его в b
.
(Объект Integer теперь имеет право на сбор мусора, но не обязательно собирать мусор еще в этот момент)
![enter image description here]()
b = b + b;
3- Это дважды читает значение b
, добавляет их вместе и помещает новое значение в b
.
![enter image description here]()
С другой стороны:
SomeObject s1 = new SomeObject("first");
SomeObject s2 = s1;
s2.setText("second");
System.out.println(s1.getText()); // second as UNexpected
System.out.println(s2.getText()); // second as expected
SomeObject s1 = new SomeObject("first");
1- Создает новый экземпляр класса SomeObject
и присваивает ему ссылку s1
.
![enter image description here]()
SomeObject s2 = s1;
2- Это приведет к тому, что ссылка s2
указывает на объект, на который указывает s1
.
![enter image description here]()
s2.setText("second");
3- Когда вы используете сеттеры для ссылки, он изменит объект, на который ссылается ссылка.
![enter image description here]()
System.out.println(s1.getText());
System.out.println(s2.getText());
4 Оба должны печатать second
, так как две ссылки s1
и s2
относятся к одному и тому же объекту (как показано на предыдущем рисунке).
Ответ 3
Когда вы это сделаете
SomeObject s1 = new SomeObject("first");
SomeObject s2 = s1;
у вас есть 2 ссылки на один и тот же объект. Это означает, что какой бы ссылочный объект вы используете, сделанные вами изменения будут видны при использовании второй ссылки.
Подумайте об этом так: у вас есть один телевизор в комнате, но два пульта дистанционного управления: неважно, какой удаленный вы используете, вы все равно будете вносить изменения в один и тот же базовый объект (телевизор).
Ответ 4
Когда вы пишете:
SomeObject s1 = new SomeObject("first");
s1
не является SomeObject
. Это ссылка на объект SomeObject
.
Следовательно, если вы назначаете s2 в s1, вы просто назначаете ссылки/дескрипторы, а базовый объект тот же. Это важно в Java. Все передается по значению, но вы никогда не пропускаете объекты вокруг - только ссылки на объекты.
Поэтому, когда вы назначаете s1 = s2
, а затем вызываете метод на s2
, который изменяет объект, базовый объект изменяется и отображается, когда вы ссылаетесь на объект через s1
.
Это один аргумент для неизменности объектов. Делая объекты неизменными, они не будут меняться под вами и, таким образом, будут вести себя более предикативно. Если вы хотите скопировать объект, самым простым/наиболее практичным методом является создание метода copy()
, который просто создает новую версию и копирует ее по полям. Вы можете делать умные копии, используя сериализацию/отражение и т.д., Но это более сложное, очевидно.
Ответ 5
От Десять ошибок Программисты Java делают:
6 - Путаница при прохождении по значению и передача по ссылке
Это может быть неприятной проблемой для диагностики, потому что, когда вы смотрите в коде, вы можете быть уверены, что его передача по ссылке, но найти что его фактически передают по значению. Java использует оба, поэтому вам нужно понимать, когда вы проходите по значению, и когда вы проходите мимо ссылка.
Когда вы передаете примитивный тип данных, например char, int, float или double, к функции, то вы проходите по значению. Это означает, что копия типа данных дублируется и передается функции. Если функция решает изменить это значение, она будет изменять копировать только. Как только функция завершается, и управление возвращается на возвращающая функция, "реальная" переменная будет нетронутой, и нет изменения будут сохранены. Если вам нужно изменить примитивные данные тип, сделать это возвращаемым значением для функции или обернуть ее внутри объект.
Поскольку int
является примитивным типом, int b = a;
является копией по значению, что означает, что a
и b
- это два разных объекта, но с одинаковым значением.
SomeObject s2 = s1;
сделайте s1
и s2
две ссылки одного и того же объекта, поэтому, если вы измените один, другой будет изменен.
Хорошим решением является реализация другого конструктора, подобного этому:
public class SomeObject{
public SomeObject(SomeObject someObject) {
setText(someObject.getText());
}
// your code
}
Затем используйте его так:
SomeObject s2 = new SomeObject(s1);
Ответ 6
В вашем коде s1
и s2
будут одни и те же объекты (вы только создали один объект с помощью new
), и вы можете s2
указать на тот же объект в следующей строке. Поэтому, когда вы меняете text
, он меняет оба значения, если вы ссылаетесь на значение через s1
и s2
.
Оператор +
для целых чисел создает новый объект, он не меняет существующий объект (поэтому добавление 5 + 5 не дает 5 нового значения 10...).
Ответ 7
Это потому, что JVM хранит указатель на s1
. Когда вы вызываете s2 = s1
, вы в основном говорите, что указатель s2
(т.е. Адрес памяти) имеет то же значение, что и для s1
. Поскольку оба они указывают на одно и то же место в памяти, они представляют собой одно и то же.
Оператор =
назначает значения указателя. Он не копирует объект.
Клонирование объектов - это сложный вопрос сам по себе. У каждого объекта есть метод clone(), который может возникнуть у вас, но он делает мелкую копию (что в основном означает, что она делает копию объекта верхнего уровня, но любой объект, содержащийся в нем, не клонируется). Если вы хотите поиграть с копиями объектов, обязательно прочитайте Joshua Bloch Эффективная Java.
Ответ 8
Начните со второго примера:
Этот первый оператор присваивает новый объект s1
SomeObject s1 = new SomeObject("first");
Когда вы выполняете присвоение во втором выражении (SomeObject s2 = s1
), вы указываете, что s2
указывает на тот же объект, на который в данный момент указывается s1
, поэтому у вас есть две ссылки на один и тот же объект.
Обратите внимание, что вы не дублировали SomeObject
, а две переменные просто указывали на один и тот же объект. Поэтому, если вы изменяете s1
или s2
, вы фактически изменяете один и тот же объект (обратите внимание, если вы сделали что-то вроде s2 = new SomeObject("second")
, они теперь будут указывать на разные объекты).
В вашем первом примере a
и b
являются примитивными значениями, поэтому изменение одного не повлияет на другое.
Под капотом Java все объекты работают с использованием pass by value. Для объектов вы передаете "значение", которое вы передаете, - это местоположение объекта в памяти (поэтому он, похоже, имеет аналогичный эффект прохождения по ссылке). Примитивы ведут себя по-разному и просто передают копию значения.
Ответ 9
Ответы, приведенные выше, объясняют поведение, которое вы видите.
В ответ на "Также, как дублировать SomeObject, если простое назначение не выполняет эту работу?" - попробуйте найти cloneable
(это java-интерфейс, который обеспечивает один способ копирования объектов) и copy constructors
(альтернативный и, возможно, лучший подход)
Ответ 10
Назначение объекта для ссылки не клонирует ваш объект. Ссылки похожи на указатели. Они указывают на объект, и когда операции вызывается, это делается на объекте, указанном указателем. В вашем примере s1 и s2 указывают на один и тот же объект, а сеттеры изменяют состояние одного и того же объекта, а изменения видны через ссылки.
Ответ 11
Измените свой класс, чтобы создать новую ссылку вместо того, чтобы использовать один и тот же:
public class SomeObject{
public String text;
public SomeObject(String text){
this.setText(text);
}
public String getText(){
return text;
}
public void setText(String text){
this.text = new String(text);
}
}
Вы можете использовать что-то вроде этого (я не претендую на идеальное решение):
public class SomeObject{
private String text;
public SomeObject(String text){
this.text = text;
}
public SomeObject(SomeObject object) {
this.text = new String(object.getText());
}
public String getText(){
return text;
}
public void setText(String text){
this.text = text;
}
}
Использование:
SomeObject s1 = new SomeObject("first");
SomeObject s2 = new SomeObject(s1);
s2.setText("second");
System.out.println(s1.getText()); // first
System.out.println(s2.getText()); // second
Ответ 12
int a = new Integer(5)
В вышеприведенном случае создается новое целое число. Здесь Integer является непримитивным типом и
значение внутри него преобразуется (в int) и присваивается int 'a'.
SomeObject s1 = new SomeObject("first");
SomeObject s2 = s1;
В этом случае оба s1 и s2 являются ссылочными типами. Они не созданы, чтобы содержать значение, подобное примитивным типам, скорее они содержат ссылку на некоторый объект. Для понимания мы можем рассматривать ссылку как ссылку, которая указывает, где я могу найти ссылку на объект.
Здесь ссылка s1 сообщает нам, где мы можем найти значение "first" (которое фактически хранится в памяти компьютера в экземпляре SomeObject). В других словах s1 - это адрес объекта класса "SomeObject", Следующим присваиванием -
SomeObject s2 = s1;
мы просто копируем значение, хранящееся в s1-s2, и теперь мы знаем, что s1 содержит адрес строки "first".
После этого экземпляра println() производит тот же вывод, что и s1 и s2, переопределяя один и тот же объект.
Наряду с конструктором копирования вы можете скопировать объект с помощью метода clone(), если вы являетесь java
пользователь. Клон можно использовать следующим образом:
SomeObject s3 = s1.clone();
Для получения дополнительной информации о clone() это полезная ссылка http://en.wikipedia.org/wiki/Clone_%28Java_method%29
Ответ 13
Вторая строка (SomeObject s2 = s1;
) просто назначает вторую переменную первой. Это приводит к тому, что вторая переменная указывает на тот же экземпляр объекта, что и первый.
Ответ 14
Это потому, что s1
и s2
работают только как ссылки на ваши объекты. При назначении s2 = s1
вы назначаете только ссылку, что означает, что оба будут указывать на один и тот же объект в памяти (объект, который имеет текущий текст "первым" ).
Когда вы сделаете сеттер сейчас, либо на s1, либо на s2, оба будут изменять один и тот же объект.
Ответ 15
При назначении и объекте переменной вы действительно назначаете ссылку на этот объект. Таким образом, обе переменные s1
и s2
ссылаются на один и тот же объект.
Ответ 16
Так как объекты ссылались на ссылку. Поэтому, если вы пишете s2 = s1, будет скопирована только ссылка. Как и в C, вы только обрабатывали адреса памяти. И если вы скопируете адрес памяти и измените значение за этим адресом, оба указателя (ссылки) изменят одно значение в этой точке памяти.