【问题标题】:Implementing clone() for immutable classes为不可变类实现 clone()
【发布时间】:2012-11-03 09:36:42
【问题描述】:

我正在开发一个类库。

  1. 我有一个用于矩阵的抽象基类 Matrix,它提供了一些基本方法的实现。
  2. 派生自 Matrix 的是不同类型矩阵的具体子类。
  3. 我要求矩阵是可克隆的,所以 Matrix 实现了 Cloneable 接口。
  4. 从 Matrix 派生的一些类是不可变的

不可变类的克隆方法是否可以接受,而不是返回对象的克隆,而是返回对象本身?

一些(过度简化的)代码用于澄清:

abstract class Matrix implements Cloneable {
   ...
}

class ImmutableMatrix extends Matrix {
    ImmutableMatrix clone() {
        return this;
    }
    ...
}

class SomeOtherMatrix extends Matrix {
    SomeOtherMatrix clone() {
        SomeOtherMatrix other = super.clone();
        ...
        return other;
    }
    ...
}

【问题讨论】:

  • 为什么一开始就允许你的对象被克隆?
  • 一些操作(如转置)可以通过返回原始矩阵周围的小包装器并即时计算值来轻松实现。此外,您可以在某些情况下减少内存占用(即大型稀疏矩阵上的操作)。只要原始矩阵是不可变的,这就可以正常工作。否则对原始矩阵的任何更改都会对结果产生副作用。但是我现在通过提供一个 getImmutable() 方法解决了这个问题,该方法对于不可变子类只返回对象本身,而可变子类返回一个不可变副本。不再克隆。

标签: java clone class-design immutability


【解决方案1】:

我原以为拨打super.clone() 就足够了。

如果你的类是不可变的,那么它应该在构造时已经克隆了任何可变的类。因此,我认为拥有您班级拥有的任何字段的浅表副本是安全的。

JavaDocs 声明x.clone() != x 是首选。虽然这不是绝对要求,但您返回 this 的计划肯定会违反它。

【讨论】:

    【解决方案2】:

    只需在不可变类的 clone() 实现中返回 this

    【讨论】:

      【解决方案3】:

      虽然您可以让不可变类简单地实现 clone 以返回对自身的引用,但我真的认为在可能或可能不可变的事物上使用 clone 并没有多大价值,因为缺少某种使可变的方法处理不可变的事物,反之亦然。

      我认为您的基础Matrix 类最好包含方法IsImmutableIsWritable,以及AsImmutableAsMutableAsNewMutable 方法;它还应该包括读取和写入矩阵的方法(尽管在不可写矩阵上调用“write”方法会引发异常)。

      定义静态方法CreateImmutableMatrixCreateMutableMatrix,给定Matrix,将创建一个新的不可变或可变矩阵,该矩阵已使用适当的数据进行预初始化。

      可变类应该实现AsImmutable 将自己传递给CreateImmutableMatrixAsMutable 将自己返回,AsNewMutable 将自己传递给CreateMutableMatrix

      不可变类应该实现AsImmutable 来返回自己,AsMutable 来调用AsNewMutable,以及AsNewMutable 来将自己传递给CreateMutableMatrix

      只读包装器应实现 AsImmutable 以在包装对象上调用 AsImmutable,并实现 AsMutableAsNewMutable 以在包装对象上调用 AsNewMutable

      接收可能需要或不需要复制或变异的矩阵的对象可以简单地将其存储在字段中(例如Foo)。如果需要对矩阵进行变异,可以将Foo替换为Foo.AsMutable()。如果需要复制包含矩阵的对象,则应将副本中的字段替换为Foo.AsImmutable()Foo.AsNewMutable(),具体取决于副本中的字段是否可能需要更改。

      【讨论】:

      • 感谢您的回复。这很好地描述了我在此期间实施的解决方案。
      • @supercat 我正在为具有父视图界面的不可变/可变类实现类似的方案(我还看到了这篇文章:programmers.stackexchange.com/questions/221762/…)。我看到了方法AsNewMutableAsImmutable 的用途,但我不确定AsMutable. 在可变对象上调用这样的方法是否表明您应该将该对象作为其可变版本传递?在对象上调用此方法意味着您可能有副作用,也可能没有。
      • @AlexanderKaratarakis:我们的想法是,在保证(1)不可变或(2)保持宇宙中对可变对象的唯一引用的引用上调用AsMutable有问题 - 很可能是通过在不可变对象上调用“AsMutable”而产生的,并且从那时起就没有共享过。在宇宙唯一对可变对象的引用上调用AsNewMutable 会起作用,但它会不必要地慢。如果代码正在克隆一个对象,例如十个“昂贵”的属性,很可能之前只需要更改一两个...
      • ...对象被再次克隆,在创建属性时使用AsImmutable,然后在需要修改它们时使用AsMutable,最终会生成任何已变异属性的额外副本在对象被再次克隆之前,但将避免需要对未修改的事物进行新副本。这会对效率产生巨大影响,尤其是在使用嵌套集合时。如果例如一个集合有十个节点,每个节点包含十个项目,两个叶节点将在克隆操作之间修改,在克隆上使用AsImmutable,在...时使用AsMutable...
      • ...进行更改意味着进行两次修改将需要制作两个叶节点和一个或两个中级节点的可变副本,并且下一个克隆操作必须制作不可变副本那些。对所有内容使用AsNewImmutable 意味着更改不需要任何复制,但下一个克隆操作必须复制所有 100 个叶节点和所有 10 个中级节点。因此,使用AsMutable 会将 110 次复制操作变为 4 次。
      【解决方案4】:

      你的类不是严格不可变的,因为它不是最终的:可以有可变的子类。

      如果有人想继承ImmutableMatrix,如果你只返回this,他将无法通过调用super.clone()来实现clone()。所以在这种情况下你应该打电话给super.clone()

      但是,如果你的课程是决赛,我看不出有什么理由不直接返回 this

      【讨论】:

      • 如果类的合同规定已观察到的状态的任何方面都不会改变,那么任何违反它的子类都会被破坏。尽管 any 可继承类几乎不可能保证子类将遵守其契约,但通常的做法是在非安全相关的上下文中假设派生类会这样做。如果这不是常见的做法,继承将毫无用处。在这方面,我认为没有理由认为不变性与任何其他合同承诺存在根本不同。
      • @supercat 即使子类是不可变的,也可以通过调用 super.clone() 实现克隆
      • 如果子类是不可变的,即使不是基类的所有派生类都是不可变的,它也可以通过简单地执行return this; 来实现clone。如果类由合同指定是不可变的,那么即使类是可继承的,将clone 实现为return this 也是可以接受的,前提是派生的合同文档类可能不会合法地做任何会克隆到不同的事情。
      【解决方案5】:

      是的,如果我们看到 String(不可变类)的行为,如果内容相同,则返回相同的对象,所以我认为你是对的,clone() 方法应该只返回这个。

      【讨论】:

      • 是字符串不可克隆,但如果两个对象的内容相同且不可变,则应返回相同的对象而不是创建其他对象,以节省内存
      • 我也是这么想的。最后我求助于不同的东西。让 Matrix 实现 Cloneable 的要求是因为某些方法必须创建作为参数传递的矩阵的不可变副本。所以我在这些方法中克隆了矩阵参数。现在,我在矩阵类中添加了一个抽象的getImmutable() 方法并改用它。这使我不必为不可变类创建不必要的克隆,而可变类可以通过创建矩阵的不可变副本来提供自己的此方法的实现。
      猜你喜欢
      • 2012-07-06
      • 2012-08-26
      • 2012-08-03
      • 2010-10-27
      • 1970-01-01
      • 1970-01-01
      • 2016-11-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多