【问题标题】:Why people are so afraid of using clone() (on collection and JDK classes)?为什么人们如此害怕使用 clone()(在集合和 JDK 类上)?
【发布时间】:2011-02-05 13:53:49
【问题描述】:

我多次争辩说,使用clone() 并不是一个糟糕的做法。是的,我知道这些论点。 Bloch said 这很糟糕。他确实做到了,但他说实施clone() 很糟糕。另一方面,使用克隆,特别是如果它由受信任的库(例如 JDK)正确实现,是可以的。

就在昨天,我讨论了an answer of mine,这只是表明使用clone() 代替ArrayList 是可以的(我猜因此没有得到任何支持)。

如果我们查看ArrayList@author,我们可以看到一个熟悉的名字——Josh Bloch。所以ArrayList(和其他集合)上的clone() 非常好(看看他们的实现)。

Calendar 和大多数 java.langjava.util 类也是如此。

那么,给我一个理由为什么不使用 clone() 与 JDK 类?

【问题讨论】:

  • 昨天有人提议克隆数组来复制它们(而不是 System.arraycopy 或 Arrays.copyOf)。得到了我的 +1。 stackoverflow.com/questions/2589741/…
  • 记得我告诉一位同事不要到处添加克隆方法。但我很难解释为什么:)
  • clone() 没问题。不过很多人都搞错了。我发现它非常适合模板(即工厂和类合二为一 - 少得多的样板废话)。顺便说一句,布洛赫先生的书不应被视为福音,请使用您自己的判断。思考练习:为什么HashMap.keySet/valuesArrayList.subList 不能序列化?

标签: java


【解决方案1】:

那么,给我一个为什么不在 JDK 类中使用 clone() 的理由?

  • 给定一个ArrayList 引用,您需要一个getClass 检查来检查它是否不是JDK 类的子类。然后什么?不能信任潜在的子类。据推测,子类在某些方面会有不同的行为。
  • 它要求引用比List 更具体。有些人不介意,但大多数人认为这是个坏主意。
  • 您将不得不处理一个演员表,一个不安全的演员表。

【讨论】:

  • 转换将是您调用clone() 的类型,因此它实际上是安全的(尽管会出现警告)。但无论如何,好点;)
  • 只有在 clone 方法中才需要强制转换。由于支持 Java 1.5 协变返回类型,这意味着您的 clone() 方法可以返回超类返回类型的任何子类型。
  • @Helper 方法:由于向后兼容,ArrayList(以及其他“集合和 JDK 类”)不会以这种方式覆盖 clone
【解决方案2】:

根据我的经验,clone() 的问题出现在派生类上。

比如说,ArrayList 实现了 clone(),它返回一个 ArrayList 的对象。

假设ArrayList 有一个派生类,即MyArrayList。如果MyArrayList 不覆盖 clone() 方法,那将是一场灾难。 (默认情况下它继承ArrayList的代码)。

MyArrayList 的用户可能期望 clone() 返回一个MyArrayList 的对象;然而,事实并非如此。

这很烦人:如果基类实现了 clone(),那么它的派生类必须一直覆盖 clone()。

【讨论】:

  • ArrayList.clone被正确实现时(即它调用super.clone()来创建克隆而不是自己实例化它),那么MyArrayList.clone()返回一个MyArrayList目的。但是,共享引用仍然可能存在问题。
【解决方案3】:

我会用这个人自己的一句话来回答(Josh Bloch on Design - Copy Constructor versus Cloning):

我不再使用Cloneable 的东西很少。我经常在具体类上提供public clone 方法,因为人们期望它。

没有比这更明确的了:clone() 在他的 Collection Framework 类中提供因为人们期望它。如果人们不再期待它,他会很乐意把它扔掉。让人们停止期待它的一种方法是教育人们停止使用它,而不是提倡使用它。

当然,Bloch 自己也说过(不是准确的引用,而是接近)“API 就像性:犯一个错误,你会终生支持它”。任何public clone() 都可能永远无法收回。不过,这还不足以成为使用它的充分理由。

【讨论】:

    【解决方案4】:

    为了不鼓励其他经验不足的开发人员自己实现Clone()。我曾与许多开发人员合作过,他们的编码风格很大程度上是从他们使用过的(有时很糟糕的)代码中复制而来的。

    【讨论】:

      【解决方案5】:

      它不强制实施者是做深拷贝还是浅拷贝。

      【讨论】:

      • 它确实 - (来自 Object.clone() 文档)“因此,此方法执行此对象的“浅拷贝”,而不是“深拷贝”操作。”。无论如何,ArrayList.clone()(点赞)已经实现并且可以预测。
      • 一般合同是 clone() 是浅的 - 如果您打算执行深层复制,您最好使用正确命名的方法,如 deepCopy 左右。
      【解决方案6】:

      如果您不检查此克隆方法的实现,使用克隆可能会非常危险...因为您可以假设克隆 impl 可以以不同的方式执行...浅克隆或深层克隆...以及一些开发人员可能并不总是检查他们将检索什么样的克隆......

      在大型应用程序、大型团队中,这也是有风险的,因为在使用克隆时,如果您修改克隆实现,您可能会修改应用程序行为并产生新的错误......并且必须检查调用克隆的所有位置(但是对于其他对象方法,例如 equals、toString... 可以是相同的...

      在修改小子类A的clone时(例如从Deep到Shallow clone),如果B的一个实例有A的引用和一个深的clone impl,那么由于A中引用的对象不是浅克隆的, B 克隆将不再是深度克隆(对于任何引用 B 实例的类来说都是一样的......)。 处理克隆方法的深度并不容易。

      此外,当您有一个接口(扩展 Clonable)和许多(许多!)实现时,有时您会检查一些 impl 并看到克隆很深,并在接口上调用克隆,但您不能确定在运行时所有 impl 都确实有深度克隆,并且可以引入错误...

      认为最好为每个方法实现一个 shallowClone 和一个 deepClone 方法,并且在非常特定的需求下实现自定义方法(例如,您想要一个克隆并将此克隆的深度限制为 2,为在所有相关的类别中)。

      不要认为在 JDK 类上使用克隆方法是一件大事,因为它不会被其他开发人员更改,或者至少不会经常更改。但是,如果您在编译时不知道类的实现,最好不要在 JDK 类上调用 clone。我的意思是在 ArrayList 上调用克隆不是问题,但在 Collection 上调用它可能很危险,因为另一个开发人员可能会引入另一个 Collection 实现(他甚至可以扩展 Collection impl),并且没有什么告诉你这个 impl 的克隆会像你期望的那样工作......

      【讨论】:

        【解决方案7】:

        如果你的集合中的类型是可变的,你必须担心是否只是集合本身会被克隆,或者元素是否也会被克隆......因此实现你自己的函数,你知道是否元素或仅克隆容器将使事情变得更加清晰。

        【讨论】:

          猜你喜欢
          • 2014-01-09
          • 2012-02-07
          • 1970-01-01
          • 2014-03-16
          • 2011-08-13
          • 1970-01-01
          • 1970-01-01
          • 2014-06-23
          • 1970-01-01
          相关资源
          最近更新 更多