没有实现Cloneable接口的对象克隆方法

14

要克隆对象,我需要实现“cloneable”接口吗?因为我的类是一个jar文件(即API),所以无法编辑该类。我听说所有类都扩展了基本的Object类,并且这个Object类实现了Cloneable接口。这是否意味着我们可以直接克隆对象而不需要实现接口?如果是这样,在我的Eclipse中,我没有获得任何克隆对象的选项。有没有其他方法可以在不实现Cloneable接口的情况下克隆对象。请解释。


1
你为什么觉得你必须这样做?与其提供你认为必要的编程步骤来解决问题,不如告诉我们你想解决的整体问题。换句话说,你可能一开始就走错了方向。 - Hovercraft Full Of Eels
根据答案中的评论,我认为OP想要一个复制构造函数。 - clstrfsck
嗨,hovercraft。在这里我清楚地解释了我的问题。我有一个名为XYZ的类,其中有一个方法返回该XYZ类的对象。private XYZ getObject(){ return obj; } 我通过调用此方法创建了一个对象。XYZ obj1 = getObject(); 现在出现了问题。实际上,我想再创建一个对象而不必再次调用该方法。但是我不能在这里编辑XYZ类。我想要做的是XYZ obj2 = (XYZ)obj1.clone(); 但是我无法在我的XYZ类上实现“可克隆”接口。那么是否有其他方法可以为此类创建另一个对象呢? - ran
我认为可以将问题重新表述为: 我们知道clone()方法已经在Object类中定义,而且每个对象都是Object类的子对象,那么:
  1. 为什么我们仍然需要实现Cloneable接口?
  2. 是否有任何对象可以在不实现Cloneable接口的情况下进行克隆?
- dinesh kandpal
8个回答

9

通常最好避免使用 clone(),因为难以正确地实现 (http://www.javapractices.com/topic/TopicAction.do?Id=71)。也许相关类有一个复制构造函数?

或者,如果它实现了 Serializable 或 Externalizable 接口,您可以通过将其写入字节流并重新读取来深层复制它。

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object deepCopy = ois.readObject();

(来源于http://www.jguru.com/faq/view.jsp?EID=20435)这种方法简单快捷,但并不美观…通常情况下我会把它作为最后的手段。


嗨,凯尔。谢谢你的回复。但是我并没有完全理解这个问题。你能否告诉我在我的情况下该如何做呢? - ran
复制构造函数看起来像:public XYZ(XYZ toCopy)。与调用 o.clone() 不同,您将调用 new XYZ(o)。可序列化部分意味着如果 XYZ 可以写入流,则可以将其写入流(实际上只是内存中的字节数组),然后从该流中读取它,从而有效地创建副本。 - kylewm
复制构造函数的限制在于您需要在编写代码时知道类类型。如果我传递XYZ的子类,则public XYZ(toCopy)将无法工作。 - Steve Kuo

7
Java的Object类没有实现Cloneable接口。但是它有clone()方法。但是这个方法是protected的,如果在一个没有实现Cloneable接口的对象上调用它会抛出CloneNotSupportedException异常。所以如果你不能修改要克隆的类,那么你就需要另寻他法来复制该实例。
但需要注意的是,Java中的克隆系统存在许多问题,通常不再使用。请查看Josh Bloch于2002年的interview,其中解释了一些问题。

有没有其他方法可以克隆对象而不实现Cloneable接口...感谢您的回复。 - ran
然而需要注意的是,Java中的克隆系统存在许多漏洞,通常不再使用。是否可以添加该声明的参考资料? - Gaurav Saxena
您可以使用构造函数创建一个新实例,并将所需状态复制到其中。这种方法的可行性实际上取决于您想要复制的对象类别。 - orien
嗨,orien。我现在卡在无法修改要克隆的类的位置。你能否建议我另一种克隆的方法? - ran

3

你的链接已经失效。 - BrainStorm.exe

1
使用反射API可以实现它。

0

不一定需要实现Cloneable接口来克隆一个对象。 你可以在想要克隆的类中编写自己的clone方法。


0

尝试在一个没有实现Cloneable接口的类上调用clone方法会抛出CloneNotSupportedException异常,而且Object类也没有实现Cloneable接口。

这里是Object类的clone方法的javadoc:

CloneNotSupportedException  if the object's class does not
 *               support the <code>Cloneable</code> interface. Subclasses
 *               that override the <code>clone</code> method can also
 *               throw this exception to indicate that an instance cannot
 *               be cloned.

同时,Object#clone方法是受保护的,因此您需要在您的类中实现clone方法并将其设置为public,以便可以访问您的类对象的类调用它。一个很好的例子是ArrayList中实现clone的方式。

ArrayList像下面这样实现了cloneable: public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable

然后实现了clone方法:

/**
 * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
 * elements themselves are not copied.)
 *
 * @return a clone of this <tt>ArrayList</tt> instance
 */
public Object clone() {
try {
    ArrayList<E> v = (ArrayList<E>) 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();
}
}

你好Gaurav,正如我之前所提到的,我无法在我的类上实现Cloneable接口。但是我想创建一个与现有对象具有不同名称的重复对象。有没有其他方法可以做到这一点? - ran
如果你想克隆一个你无法编辑的类的对象,那么答案是否定的。理论上,你可以使用反射来访问类的变量,从而能够获取和复制它的状态,但这取决于安全管理器是否允许你访问类的私有成员。即使在这种情况下,深度复制也可能不可行。 - Gaurav Saxena
在这种情况下,您应该使用复制构造函数而不是 clone()。谷歌搜索的第一个链接是:http://www.javapractices.com/topic/TopicAction.do?Id=12 - clstrfsck

0

Object类中的clone()方法是受保护的,这意味着所有类都将继承它并带有受保护的访问修饰符,因此如果您尝试在未克隆的情况下在该类之外访问它,则不会看到它,而且如果您尝试在未实现Cloneable接口的情况下调用它,它将抛出CloneNotSupportedException异常。

如果您想要创建克隆行为的方式,需要在您的类中编写一个新方法,然后创建该方法中所有字段的副本,这基本上就像创建对象现有状态的新副本。

public class TestCloneable {
private String name = null;

/**
 * @param name the name to set
 */
public void setName(String name) {
    this.name = name;
}

/**
 * @return the name
 */
public String getName() {
    return name;
}


public TestCloneable createCopy(){
    TestCloneable testCloneable = new TestCloneable();
    testCloneable.setName(this.getName());
    return testCloneable;
}

}


0

您可以使用Unsafe来创建一个对象实例,然后通过Java反射将值复制到新实例中。但是正如Unsafe的名称所示,这并不是一个真正好的解决方案。

public static Unsafe unsafe;
static {
    Field f;
    try {
        f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        unsafe = (Unsafe) f.get(null);
    } catch (NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
    }

}

public static <T> T clone(T object) throws InstantiationException {
    T instance = (T) unsafe.allocateInstance(object.getClass());
    copyInto(object,instance);
    return instance;
}

public static void copyInto(Object source,Object destination){
    Class<?> clazz = source.getClass();
    while (!clazz.equals(Object.class)) {
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            try {
                field.set(destination, field.get(source));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        clazz = clazz.getSuperclass();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接