Как правильно переопределить метод клонирования?
Мне нужно реализовать глубокий клон в одном из моих объектов, у которого нет суперкласса.
Каков наилучший способ обработки отмеченного CloneNotSupportedException
, созданного суперклассом (который является Object
)?
Сотрудник посоветовал мне обработать его следующим образом:
@Override
public MyObject clone()
{
MyObject foo;
try
{
foo = (MyObject) super.clone();
}
catch (CloneNotSupportedException e)
{
throw new Error();
}
// Deep clone member fields here
return foo;
}
Это кажется хорошим решением для меня, но я хотел выдать его сообществу StackOverflow, чтобы узнать, есть ли другие идеи, которые я могу включить. Спасибо!
Ответы
Ответ 1
Вам нужно использовать clone
? Большинство людей согласны с тем, что Java clone
не работает.
Джош Блох по дизайну - Копировать конструктор против клонирования
Если вы прочитали статью о клонировании в моей книге, особенно если вы читаете между строками, вы будете знать, что я думаю, что clone
глубоко нарушен. [...] Позор, что Cloneable
сломан, но это происходит.
Вы можете прочитать больше дискуссий по этой теме в своей книге "Эффективное Java 2-е издание", пункт 11: разумно переопределить clone
. Он рекомендует вместо этого использовать конструктор копирования или копию factory.
Он продолжал писать страницы страниц о том, как, если вы считаете, вы должны реализовать clone
. Но он закрыл это:
Неужели все эти сложности необходимы? Редко. Если вы расширяете класс, реализующий Cloneable
, у вас мало выбора, кроме как реализовать хорошо выполненный метод clone
. В противном случае вам лучше предоставить альтернативные способы копирования объектов или просто не предоставлять возможности.
Акцент был его, а не моего.
Поскольку вы ясно дали понять, что у вас мало выбора, кроме как реализовать clone
, вот что вы можете сделать в этом случае: убедитесь, что MyObject extends java.lang.Object implements java.lang.Cloneable
. В этом случае вы можете гарантировать, что вы НИКОГДА не поймаете CloneNotSupportedException
. Throwing AssertionError
, как некоторые из них предложили, кажется разумным, но вы также можете добавить комментарий, который объясняет, почему блок catch никогда не будет введен в этом конкретном случае.
В качестве альтернативы, как и другие, вы также можете предложить clone
без вызова super.clone
.
Ответ 2
Иногда проще реализовать конструктор копирования:
public MyObject (MyObject toClone) {
}
Это избавит вас от проблем с обработкой CloneNotSupportedException
, работает с полями final
, и вам не нужно беспокоиться о возвращаемом типе.
Ответ 3
Как работает ваш код, он близок к "каноническому" способу его записи. Тем не менее, я бы выбрал AssertionError
в catch. Он сигнализирует, что эта линия никогда не должна быть достигнута.
catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
Ответ 4
Есть два случая, в которых будет выбрано CloneNotSupportedException
:
- Класс, клонированный, не реализован Cloneable (предполагая, что фактическое клонирование в конечном итоге отбрасывает метод
Object
clone). Если класс, который вы пишете этот метод, реализует Cloneable, этого никогда не произойдет (поскольку любые подклассы наследуют его соответствующим образом).
- Исключением явно выдается реализация - это рекомендуемый способ предотвращения клонирования в подклассе, когда суперкласс является Cloneable.
Последний случай не может произойти в вашем классе (поскольку вы напрямую вызываете метод суперкласса в блоке try
, даже если он вызван из подкласса, вызывающего super.clone()
), а первый не должен, так как ваш класс явно должен реализовать Cloneable.
В принципе, вы должны обязательно регистрировать ошибку, но в этом конкретном случае это произойдет, только если вы испортите определение своего класса. Таким образом, рассматривайте его как проверенную версию NullPointerException (или аналогичную) - она никогда не будет выброшена, если ваш код будет функционален.
В других ситуациях вам нужно будет подготовиться к этой возможности - нет гарантии, что данный объект является клонируемым, поэтому, если вы поймаете исключение, вы должны предпринять соответствующие действия в зависимости от этого условия (продолжить с существующим объектом, взять альтернативная стратегия клонирования, например serialise-deserialise, бросает исключение IllegalParameterException, если ваш метод требует параметра клонированием и т.д. и т.д.).
Изменить. Хотя в целом я должен указать, что да, clone()
действительно сложно реализовать правильно и сложно для вызывающих абонентов, чтобы узнать, будет ли возвращаемое значение тем, что они хотят, вдвойне, когда вы рассмотрите глубокие и мелкие клоны. Часто лучше просто полностью избежать всего этого и использовать другой механизм.
Ответ 5
Используйте сериализацию, чтобы сделать глубокие копии. Это не самое быстрое решение, но оно не зависит от типа.
Ответ 6
Вы можете реализовать защищенные конструкторы копирования следующим образом:
/* This is a protected copy constructor for exclusive use by .clone() */
protected MyObject(MyObject that) {
this.myFirstMember = that.getMyFirstMember(); //To clone primitive data
this.mySecondMember = that.getMySecondMember().clone(); //To clone complex objects
// etc
}
public MyObject clone() {
return new MyObject(this);
}
Ответ 7
public class MyObject implements Cloneable, Serializable{
@Override
@SuppressWarnings(value = "unchecked")
protected MyObject clone(){
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
ByteArrayOutputStream bOs = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bOs);
oos.writeObject(this);
ois = new ObjectInputStream(new ByteArrayInputStream(bOs.toByteArray()));
return (MyObject)ois.readObject();
} catch (Exception e) {
//Some seriouse error :< //
return null;
}finally {
if (oos != null)
try {
oos.close();
} catch (IOException e) {
}
if (ois != null)
try {
ois.close();
} catch (IOException e) {
}
}
}
}
Ответ 8
Насколько мне известно большинство ответов, я должен сказать, что ваше решение также является тем, как это делают фактические разработчики Java API. (Или Джош Блох или Нил Гафтер)
Вот выдержка из openJDK, класс ArrayList:
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
Как вы заметили, и другие упомянутые, CloneNotSupportedException
почти не имеет шансов быть брошенным, если вы заявили, что реализуете интерфейс Cloneable
.
Кроме того, вам не нужно переопределять метод, если вы не делаете ничего нового в переопределенном методе. Вам нужно только переопределить его, когда вам нужно выполнить дополнительные операции над объектом или вам нужно сделать его общедоступным.
В конечном счете, все же лучше избегать этого и делать это, используя другой способ.
Ответ 9
Просто потому, что реализация Java Cloneable нарушена, это не значит, что вы не можете создать свою собственную.
Если бы реальная цель OP заключалась в создании глубокого клона, я думаю, что можно создать такой интерфейс:
public interface Cloneable<T> {
public T getClone();
}
затем используйте ранее созданный конструктор прототипов для его реализации:
public class AClass implements Cloneable<AClass> {
private int value;
public AClass(int value) {
this.vaue = value;
}
protected AClass(AClass p) {
this(p.getValue());
}
public int getValue() {
return value;
}
public AClass getClone() {
return new AClass(this);
}
}
и еще один класс с полем объекта AClass:
public class BClass implements Cloneable<BClass> {
private int value;
private AClass a;
public BClass(int value, AClass a) {
this.value = value;
this.a = a;
}
protected BClass(BClass p) {
this(p.getValue(), p.getA().getClone());
}
public int getValue() {
return value;
}
public AClass getA() {
return a;
}
public BClass getClone() {
return new BClass(this);
}
}
Таким образом вы можете легко глубоко клонировать объект класса BClass без необходимости использования @SuppressWarnings или другого трюкового кода.