Копирование объектов в 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
пожалуйста, объясните мне, почему изменение любого из объектов влияет на другой. Я понимаю, что значение переменной text хранится в одном и том же месте в памяти для обоих объектов.
почему значения переменных независимы, но коррелированы для объектов?
кроме того, как дублировать SomeObject, если простое назначение не выполняет эту работу?
16 ответов:
каждая переменная в 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);
ответ @ 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 expectedint a = new Integer(5);1-Первый оператор создает целочисленный объект со значением 5. Затем, при присвоении его переменной
a, целочисленный объект будет распакован и сохранен вaкак примитивный.после создания целочисленного объекта и до задание:
после присваивания:
int b = a;2 - это будет просто прочитать значение
aи затем хранить его вb.(целочисленный объект теперь имеет право на сбор мусора, но не обязательно сбор мусора еще на этом этапе)
b = b + b;3-это считывает значение
bдважды, сложите их вместе и поместите новое значение вb.
С другой стороны:
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 expectedSomeObject s1 = new SomeObject("first");1 - создает новый экземпляр
SomeObjectкласс, и присваивает его ссылкеs1.
SomeObject s2 = s1;2 - это ссылка
s2указывает на объект, которыйs1указывает на.
s2.setText("second");3-Когда вы используете сеттеры на ссылке, он будет изменять объект на что указывает ссылка.
System.out.println(s1.getText()); System.out.println(s2.getText());4-оба должны печатать
second, так как две ссылкиs1иs2ссылаются на тот же объект (как показано на предыдущем рисунке).
при этом
SomeObject s1 = new SomeObject("first"); SomeObject s2 = s1;у вас есть 2 ссылки на один и тот же объект. Это означает, что какой бы ссылочный объект вы ни использовали, внесенные изменения будут видны при использовании второй ссылки.
подумайте об этом так: у вас есть один телевизор в комнате, но два пульта дистанционного управления: неважно, какой пульт вы используете, вы все равно будете вносить изменения в один и тот же базовый объект (телевизор).
С Десять Лучших Ошибок Java Программисты Делают:
6-путаница при передаче по значению и передаче по ссылке
это может быть проблемой для диагностики, потому что, когда вы смотрите в коде, вы можете быть уверены, что пройдя по ссылке, но найти что его на самом деле передается по значению. Java использует оба, поэтому вам нужно чтобы понять, когда вы проходите мимо значения, и когда вы проходите мимо ссылка.
при передаче примитивного типа данных, например char, int, float или дважды, к функции, то вы передаете по значению. Это означает, что копия типа данных дублируется и передается в функцию. Если функция выбирает, чтобы изменить это значение, оно будет изменять только копирование. Как только функция завершится, и управление будет возвращено возвращаемая функция," реальная " переменная будет нетронутой, и нет изменения будут сохранены. Если вам нужно чтобы изменить примитивные данные введите, сделайте его возвращаемым значением для функции или оберните его внутри объект.
, потому что
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);
код
s1иs2это то же самое объект (вы создали только один объект с помощьюnew) и пустьs2указывают на тот же объект в следующей строке. Поэтому при сменеtextОн изменяет оба, если вы ссылаетесь на значение черезs1иs2.The
+оператор на целых числах создает новая объект, он не изменяет существующий объект (поэтому добавление 5+5 не дает 5 новое значение 10...).
это потому, что JVM хранит указатель на
s1. Когда вы звонитеs2 = s1, вы в основном говорят, чтоs2указатель (т. е. адрес памяти) имеет то же значение, что и дляs1. Поскольку они оба указывают на одно и то же место в памяти, они представляют собой одно и то же.The
=оператор присваивает значение указателя. Он не копирует объект.клонирование объектов является сложным делом само по себе. Каждый объект имеет метод clone (), который вы может возникнуть соблазн использовать, но он делает мелкую копию (что в основном означает, что он делает копию объекта верхнего уровня, но любой объект, который содержится в нем, не клонируется). Если вы хотите поиграть с копиями объектов, обязательно прочитайте Joshua Bloch's Эффективная Java.
давайте начнем со второго примера:
это первый оператор присваивает новый объект
s1SomeObject s1 = new SomeObject("first");при выполнении задания во втором операторе (
SomeObject s2 = s1), ты говоришьs2указывать на один и тот же объектs1в настоящее время указывает на, так что у вас есть две ссылки на один и тот же объект.обратите внимание, что вы не продублировали
SomeObject, а две переменные указывают на один объект. Так что если вы изменитьs1илиs2, вы фактически изменяете один и тот же объект (обратите внимание, если вы сделали что-то вродеs2 = new SomeObject("second")теперь они будут указывать на различные объекты).в вашем первом примере,
aиbявляются примитивными значениями, поэтому изменение одного не влияет на другие.под капотом Java все объекты работают с использованием pass by value. Для объектов вы передаете" значение", которое вы передаете, - это местоположение объекта в памяти (поэтому он, похоже, имеет аналогичное эффект прохождения по ссылке). Примитивы ведут себя по-разному и просто передают копию значения.
ответы выше объясняют поведение, которое вы видите.
в ответ на "Кроме того, как дублировать SomeObject, если простое назначение не выполняет эту работу?"- попробуйте найти
cloneable(это интерфейс java, который обеспечивает один из способов копирования объектов) и 'copy constructors' (альтернативный и, возможно, лучший подход)
назначение объекта ссылке не клонирует ваш объект. Ссылки как указатели. Они указывают на объект, и когда операции вызываются, это делается на объект, на который указывает указатель. В вашем примере s1 и s2 указывают на один и тот же объект, а сеттеры изменяют состояние одного и того же объекта, и изменения видны по ссылкам.
измените класс, чтобы создать новую ссылку вместо использования того же самого:
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
int a = new Integer(5)в приведенном выше случае создается новое целое число. Здесь целое-это не примитивный тип и значение внутри него преобразуется (в 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 и С2 refrencing один и тот же объект.
вместе с конструктором копирования вы можете скопировать объект с помощью метода clone (), если вы java пользователь. Клон можно использовать следующим образом -
SomeObject s3 = s1.clone();для получения дополнительной информации о clone () это полезная ссылка http://en.wikipedia.org/wiki/Clone_%28Java_method%29
потому что
s1иs2работают только как ссылки на ваши объекты. При назначенииs2 = s1вы только назначаете ссылку, что означает, что оба будут указывать на один и тот же объект в памяти (объект, который имеет текущий текст "первый").когда вы делаете сеттер сейчас, либо на s1 или s2, оба будут изменять один и тот же объект.
когда вы назначаете и объект переменной, вы действительно назначаете ссылку на этот объект. Так что обе переменные
s1иs2ссылаются на один и тот же объект.
Так как объекты были связаны ссылкой. Поэтому, если вы пишете s2=s1, будет скопирована только ссылка. Его, как и в C, вы обрабатывали только с адресами памяти. И если вы скопируете адрес памяти и измените значение за этим адресом, оба указателя (ссылки) изменят одно значение в этой точке в памяти.
вторая строка (
SomeObject s2 = s1;) просто присваивает вторую переменную первой. Это приводит ко второй переменной, указывающей на тот же экземпляр объекта, что и первый.







Comments