【问题标题】:Implementing clone on a LinkedList在 LinkedList 上实现克隆
【发布时间】:2011-02-27 06:26:59
【问题描述】:

我正在尝试在 DoubleLinkedList 上实现 clone() 方法。现在的问题是,按照“约定”来实现它比仅仅创建一个新的DoubleLinkedList 并用我当前的 DoubleLinkedList 的所有元素填充它要麻烦得多。

这样做有什么不方便的地方吗?

这是我目前的做法:

@Override
public DoubleLinkedList<T> clone() {
    DoubleLinkedList<T> dll = new DoubleLinkedList<T>();

    for (T element : dll) {
        dll.add(element);
    }

    return dll;
}

这是约定俗成的内容:

@Override
public DoubleLinkedList<T> clone() {
    try {
        DoubleLinkedList<T> dll = (DoubleLinkedList<T>)super.clone();
        //kinda complex code to copy elements
        return dll;
    } catch (CloneNotSupportedException e) {
        throw new InternalError(e.toString());
    }
}

【问题讨论】:

    标签: java cloning cloneable


    【解决方案1】:

    如果您通过创建一个新列表并添加源中的所有元素来做到这一点,如果您随后执行以下操作:

    DoubleLinkedList<Foo> l1 = new DoubleLinkedList<Foo>();
    l1.add (new Foo(params));
    DoubleLinkedList<Foo> l2 = l1.clone();
    Foo foo = l2.get(0);
    foo.setProperty("new Value");
    

    foo.property 在两个列表中都是“新值”(反之亦然;如果您在 l1 中更改它,更改将出现在 l2 中)。正确的方法是实际克隆每个元素并添加克隆以确保列表是独立的。请注意,仅当您更改元素的属性时才会发生这种情况,而不是在您从列表中添加、移动、删除它们时发生。

    编辑:刚刚意识到由于它是一个链表,下一个/上一个元素是元素的属性,所以即使添加,删除,都会影响两个列表。

    【讨论】:

    • 让克隆实现浅拷贝没有错。事实上,Java API 中的LinkedList 就是这样做的!
    • 我没说错;他问是否有任何不便,我指出了这一点,因为有些人可能认为这是一个(有些人可能不认为),所以他可以自己决定这样做没关系。
    【解决方案2】:

    正如您正确指出的那样,惯例是始终在 clone() 的实现开始时调用 super.clone()。来自API docs on Object#clone():

    按照约定,返回的对象应该通过调用super.clone来获取。如果一个类及其所有超类(Object 除外)都遵守这个约定,那么 x.clone().getClass() == x.getClass() 就是这种情况。

    您的第一次尝试(不使用super.clone())有以下问题:

    假设我有

    class IntDoubleLinkedList extends DoubleLinkedList<Integer> implements Cloneable
    

    (并且 IntDoubleLinkedList 不会费心覆盖 clone())然后我运行以下代码:

    IntDoubleLinkedList idll = new IntDoubleLinkedList();
    IntDoubleLinkedList idll2 = (IntDoubleLinkedList) idll.clone();
    

    会发生什么? your DoubleLinkedList 的 clone 方法将被执行,如果它没有通过 super.clone(),则返回一个 DoubleLinkedList 的实例,而该实例又不能被强制转换为IntDoubleLinkedList。 ClassCastException 将被抛出!

    那么super.clone() 是如何解决这个问题的呢?好吧,如果每个人都遵守在覆盖的克隆方法中调用 super.clone() 的约定,那么最终将调用 Object.clone(),并且此实现将创建一个适当类型的实例(在本例中为 IntDoubleLinkedList)!

    【讨论】:

    • 请注意,此约定仅在类不是最终类时才适用/重要。如果 DoubleLinkedList 是最终的,那么您就知道最终类型并且可以通过任何适合的机制进行克隆(例如,参见 Effective Java 2nd Ed,第 7 项:“如果类是最终的,则克隆甚至可以返回由构造函数创建的对象。”)
    • 这很好。通过 super.clone() 只是对扩展我们班级的人的帮助,而不是对“我们自己”的帮助。不过,不允许列表实现的子类化似乎很奇怪。
    • 第 17 条,“为继承设计和记录或禁止它” :) “为继承设计和记录”比人们想象的要困难得多,并且会导致一些细微的错误。有一个论点是,默认值应该是使类成为最终的,并且只有在仔细考虑后才将其删除....但是,是的,我明白你的意思。我只是在扮演魔鬼的拥护者。
    【解决方案3】:

    “约定”之所以调用super.clone(),是为了确保克隆对象的最终类型与被克隆对象匹配。例如,如果您在 clone() 方法中实例化您自己的新 DoubleLinkedList,现在这很好,但稍后如果子类未能覆盖 clone(),它将最终返回一个克隆,它是 DoubleLinkedList 而不是它自己的类。 (它也可能无法克隆其附加字段,如果有的话!所以有更大的问题。)

    从这个意义上说,传统的方法更受欢迎,而且确实很笨重。

    然而,这两种实现都有一个类似的问题:您没有深度复制数据结构。克隆人只是一个肤浅的警察。这可能不是调用者所期望的。您需要检查并用该值的克隆替换 DoubleLinkedList 中的每个值,对于其他非原始字段也是如此。

    从这个意义上说,传统方法在这里会给出错误的结果!你需要第三种方式。您的第一种方法可能几乎可以工作,除了您需要添加 element.clone() 之外。

    【讨论】:

    • 我会期待一个浅拷贝,因为这是 API 列表实现所提供的(例如 LinkedList:“返回此 LinkedList 的浅拷贝。(元素本身不是克隆。)")
    • 第一种方法(不调用 super.clone())不遵循约定。请参阅有关 Object#clone() 的文档和我的答案。
    • 查看我对 aioobe 关于约定的评论——如果类是最终类且不能被子类化,则不需要遵循。
    【解决方案4】:

    正如其他人所解释的,如果你要覆盖 clone,你应该遵守它的合同。

    如果您喜欢当前的方式,只需将 DoubleLinkedList 改为 Cloneable 并将您的实现转换为复制构造函数或静态工厂方法。静态工厂方法还有一个额外的好处,那就是为泛型类型参数提供一点类型推断。

    附: LinkedList 是双向链表。

    【讨论】:

    • 你的回答听起来有点矛盾。让它像复制构造函数一样工作并不强调应该通过clone合约中建议的super.clone:按照惯例,应该通过调用super.clone来获得返回的对象。
    • 我告诉@devoured elysium 忘掉clone 而是将其编写为复制构造函数或静态工厂方法,因为他已经想到了一个完全可以接受的实现在其中之一。我会更新我的答案(我希望)更清楚。
    • +1 为您的 P.S.,我希望 OP 将其作为一种学习练习,而不是因为他们认为必须这样做,因为 LinkedList 只是单链接的。 :S
    猜你喜欢
    • 2017-09-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-23
    • 2014-12-19
    • 2023-03-15
    • 2016-06-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多