Что это за полевая копия, сделанная Object.clone()?
В Effective Java автор утверждает, что:
Если класс реализует Cloneable, метод Object клона возвращает полевую копию объекта; в противном случае он создает исключение CloneNotSupportedException.
Что я хотел бы знать, так это то, что он имеет в виду под полевым копированием. Означает ли это, что если класс имеет в памяти X байтов, он просто скопирует этот фрагмент памяти? Если да, то могу ли я предположить, что все типы значений исходного класса будут скопированы в новый объект?
class Point implements Cloneable{
private int x;
private int y;
@Override
public Point clone() {
return (Point)super.clone();
}
}
Если что Object.clone()
делает поле за полем копию Point
класс, я бы сказал, что мне не нужно явно копировать поля x
а также y
в связи с тем, что приведенный выше код будет более чем достаточно, чтобы сделать клон Point
учебный класс. То есть следующий бит кода является избыточным:
@Override
public Point clone() {
Point newObj = (Point)super.clone();
newObj.x = this.x; //redundant
newObj.y = this.y; //redundant
}
Я прав?
Я знаю, что ссылки на клонированный объект будут автоматически указывать туда, куда указывали ссылки на исходный объект, я просто не уверен, что конкретно происходит с типами значений. Если бы кто-то мог четко заявить, что Object.clone()
Спецификация алгоритма (на простом языке) была бы отличной.
4 ответа
Да, копирование поля за полем означает, что при создании нового (клонированного) объекта JVM будет копировать значение каждого поля из исходного объекта в клонированный объект. К сожалению, это означает, что у вас есть мелкая копия. Если вам нужна глубокая копия, вы можете переопределить метод клонирования.
class Line implements Cloneable {
private Point start;
private Point end;
public Line() {
//Careful: This will not happen for the cloned object
SomeGlobalRegistry.register(this);
}
@Override
public Line clone() {
//calling super.clone is going to create a shallow copy.
//If we want a deep copy, we must clone or instantiate
//the fields ourselves
Line line = (Line)super.clone();
//assuming Point is cloneable. Otherwise we will
//have to instantiate and populate it's fields manually
line.start = this.start.clone();
line.end = this.end.clone;
return line;
}
}
Также еще одна важная вещь о клонировании - конструктор клонированного объекта никогда не вызывается (копируются только поля). Поэтому, если конструктор инициализирует внешний объект или регистрирует этот объект в некотором реестре, то этого не произойдет для клонированного объекта.
Я лично предпочитаю не использовать клонирование Java. Вместо этого я обычно создаю свои собственные методы "дублирования".
Это означает неглубокую копию - поля копируются, но если у вас есть какие-либо ссылки, то, на что они указывают, не копируются - у вас будет две ссылки на один и тот же объект, один в старом объекте и один в новом, клонированном объект. Однако для полей, имеющих примитивные типы, это сами данные, поэтому они копируются независимо.
newObj.x = this.x; //redundant
newObj.y = this.y; //redundant
это верно - они избыточны, так как они уже будут скопированы методом clone() объекта.
Думая об этом как о копии данных, это правильно. Примитивные типы копируются, и ссылки также копируются, поэтому они указывают на один и тот же объект. Например,
class A implements Cloneable {
Object someObject;
}
A a = new A();
a.someObject = new Object();
A cloneA = (A)a.clone();
assert a.someObject==cloneA.someObject;
Клон по умолчанию выполняет поверхностное копирование значений. Для примитивных значений этого достаточно, и никакой дополнительной работы не требуется.
Для объектов мелкое копирование означает копирование только ссылки. Поэтому в этих случаях обычно требуется глубокая копия. Исключением является случай, когда ссылка указывает на неизменный объект. Неизменяемые объекты не могут изменять свое видимое состояние, поэтому их ссылки можно безопасно копировать. Например, это относится к перечислениям типа String, Integer, Float, (если они не были изменены по ошибке).