【问题标题】:Why are Java enums not clonable?为什么 Java 枚举不可克隆?
【发布时间】:2009-11-26 12:49:06
【问题描述】:

现在改变问题为时已晚,但更准确的应该是问“为什么 clone() 不允许单例?”。 copy() 方法会更方便。


Java中的枚举不能被克隆有什么原因吗?

手册指出

这保证了枚举永远不会被克隆,这是保持它们的“单例”状态所必需的。

但返回实例本身也会保留其状态,并且我可以像处理其他可克隆对象一样处理关联的枚举。

有人可能会说

[clone()] 的一般意图是,对于任何对象 x,表达式: x.clone() != x 是真的,[...]

但对于单身人士,我希望x.clone() == x 是真的。如果实例本身会被返回,那么单例模式将对引用对象透明。

那么当指定clone() 时,为什么不允许克隆枚举,或者他们忘记考虑单例和不可变?

【问题讨论】:

  • 有了枚举,有什么可以克隆的?

标签: java enums clone


【解决方案1】:

如果x.clone() == x,克隆单例的目的是什么?你不能直接使用x吗?

严格来说,如果你想克隆一些东西执行x.clone() == x,唯一可以作为克隆结果的对象是x本身:

def clone() {
  return this;
}

这可能会产生误导......


如果您正在设计某些东西并基于clone() 进行区分,那么恕我直言,您做错了...

【讨论】:

  • @Christian,您的代码是否处理其他未实现 Cloneable 的对象,或者所有内容都必须是可克隆的?
【解决方案2】:

如果您的克隆方法返回 this 实例而不是一个不同的对象,那么它就不是克隆,是吗?

The Javadoc 说:

按照惯例,返回的对象是 这种方法应该独立于 这个对象(正在被克隆)。

枚举不应该被克隆,因为每个值都应该只有一个实例。

编辑:回应以下评论:

这正是我所批评的。为什么 不返回相同的实例,如果有 不能换一个吗?

因为它没有真正的意义。如果它是同一个对象,那么它不是克隆。 Javadocs 还说:

总体意图是,对于任何 对象 x,表达式:

x.clone() != x
将是真的,并且 表达式:
x.clone().getClass() == x.getClass()
将是真的,但是这些 不是绝对的要求。

因此,clone() 方法的意图是返回一个不同的对象。不幸的是,它说这不是绝对要求,这使您的建议有效,但我仍然认为这是不明智的,因为拥有返回this 的克隆方法没有用。如果您正在做一些可疑的事情,例如在枚举常量中具有可变状态或在它们上同步,它甚至可能会导致问题。此类代码的行为会有所不同,具体取决于 clone 方法是进行了正确的克隆还是只是返回了this

当枚举本质上不可克隆时,您并没有真正解释为什么要将枚举视为Cloneable。想要一个不遵守公认约定的克隆方法似乎是解决您的方法的一些更基本问题的技巧。

【讨论】:

  • 因为clone方法的“契约”是返回一个副本。
  • @Stephen C:是的,但不是每个对象都必须遵守该约定,即不是每个类都应该实现 Cloneable。
  • 我认为你引用的 Javadoc 确实支持 OP 点:99% 的 Java 程序员会考虑对象状态的独立性,即修改 x 的状态不会影响(先前的结果)x.clone() 的状态。从这个意义上说,通过return this 实现clone()非常合理的。我想说的是,任何依赖于clone() 非身份结果的代码比将 immutable singleton 实例传递给(第 3 方?)尝试clone() 的方法,例如存储对象状态。
【解决方案3】:

但对于单身人士,我希望 x.clone() == x 是真的。

可能想要,但我认为以下代码会中断很奇怪:

interface Foo extends Cloneable { public int getX(); public void setX(int x);  }
enum FooSingleton implements Foo { 
    INSTANCE; 
    private int x;
    public int getX(){ return x; }
    public void setX(int x){ this.x = x; }
}
class FooClass implements Foo { 
    private int x;
    public int getX(){ return x; }
    public void setX(int x){ this.x = x; }
}

boolean omg(Foo f){
    Foo c = f.clone();
    c.setX(c.getX() + 1);
    return c.getX() != f.getX();   
}
assert omg(new FooClass());        // OK
assert omg(FooSingleton.INSTANCE); // WTF?

(当然,由于clone()只给出浅拷贝,即使是正确的实现也可能导致上述代码出错。)

另一方面,我可以同意将操作克隆到 return this 对于不可变对象是有意义的,并且枚举确实应该是不可变的。现在,在编写clone() 的合同时,他们显然没有考虑不可变,或者他们不想要语言不支持的概念(即不可变类型)的特殊情况。

所以,clone() 就是这样,你不能很好地去改变自 Java 1.0 以来就存在的东西。我很确定在某个地方,有代码完全依赖 clone() 返回一个新的、不同的对象,可能作为 IdentityHashMap 或其他东西的键。

【讨论】:

    【解决方案4】:

    我猜他们不想在指定clone() 时将单例视为特殊情况。那会使规范复杂化。所以现在库开发人员必须将它们视为特例,但对于我们其他人来说,我们可以信任 x.clone() != x,这很好。

    【讨论】:

      【解决方案5】:

      您自己对问题的回答是最好的。一般来说,人们期望clone() 回馈一个不同的对象。 Cloneable 本身的语义这样更有意义。 (“对象是可克隆的......哦,我必须能够制作副本。”)我想不出一个重要的情况,但这就是 Cloneable 的预期语义。

      我认为即使他们考虑单例,他们也不会改变它。毕竟,程序员有责任通过选择性地添加(并可能覆盖)Cloneable 接口来决定哪些可以克隆,哪些不能克隆,并且大多数程序员也不会将Cloneable 接口添加到单例中。

      【讨论】:

      • 对于不可变对象,实际上与正确实现clone() 和仅实现return this 的唯一区别是x.clone() != x 的结果,您没有真正的理由关心它。如果您处于可能拥有可变或不可变对象的情况,只需制作一个实际副本。不可变对象的副本很便宜 - 您不必向下递归对象图,只需将字段分配为相同即可。
      【解决方案6】:

      但对于单身人士,我希望 x.clone() == x 是真的。

      不,那不会是克隆。所以,对于单身人士,你想要这个:

      public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException(); 
      }
      

      【讨论】:

      • 是的,你应该这样做。一个克隆应该返回一个对单例没有意义的克隆。
      • 为什么要公开clone
      • 可能是因为这是覆盖clone() 时的约定,请参阅java.sun.com/javase/6/docs/api/java/lang/Cloneable.html
      • @TomHawtin-tackline:如果正在编写通用实用程序来尝试克隆对象,则必须知道每个非空字段以标识永远不会更改的对象 in任何会影响字段持有者状态的方式,或者可以通过某种方式进行克隆。由于没有其他用于识别不可变类型的标准约定,因此让它们实现 clone 因此它返回 this 使得通用克隆方法更容易完成应该完成的工作。
      • @TomHawtin-tackline:此外,不可变单例通常用于也可能使用可变对象的情况。例如,接受List<String> 的代码可能会被赋予一个可变列表、一个“特殊实例化”的不可变列表或一个单例不可变列表(例如一个空列表)。如果一个对象想要的是一个列表的私有对象,该列表将始终包含与给定列表相同的内容,则能够在它收到的任何列表上调用clone——可变与否——但没有它如果列表是可变的,则创建一个冗余对象——这似乎是最明智的行为。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-07
      • 1970-01-01
      • 2010-10-22
      • 1970-01-01
      • 1970-01-01
      • 2017-12-05
      相关资源
      最近更新 更多