是直接修改对象还是返回对象的修改克隆更好?

时间:2014-12-06 18:23:46

标签: java object clone immutability

问题是我无法修改我自己内部写入的对象,从而制作了一个我修改并返回的克隆。但是,在其他函数中我直接修改调用该方法的对象。我希望,并且人们已经建议,保持一致,因此用户可以使用返回的克隆执行任何他们喜欢的操作,而无需修改实际对象。

我必须在某些功能中返回对象的修改克隆,因为它没有办法解决它(我意识到)。除了标题中的问题之外,最好是采用一致的做事方式(导致我即使是最轻微的改变也会返回克隆),或者如果我有不同的方式来响应同一类中的用户,那还可以吗?

[编辑]这样做更好:

 public Image fillWithColor(Color fillColor) {
    Image newImage = new Image(this.getWidth(), this.getHeight(), this.getType());

    for (int x = 0; x < this.getWidth(); x++) {
        for (int y = 0; y < this.getHeight(); y++) {
            newImage.setPixel(x, y, fillColor.getRGB());
        }
    }

    return newImage;
}

或者这个:

public void fillWithColor(Color fillColor) {
    for (int x = 0; x < this.getWidth(); x++) {
        for (int y = 0; y < this.getHeight(); y++) {
            this.setPixel(x, y, fillColor.getRGB());
        }
    }
}

2 个答案:

答案 0 :(得分:1)

由于各种重要原因,大趋势是将尽可能多的数据视为只读。甚至数据库今天都这样做。

你显然已经意识到不受控制的数据修改会让你陷入困境。很好。尝试将数据设计为不可变对象,从长远来看,许多事情将变得更加容易。请注意,Java中的某些数据结构本质上是可变的(数组,哈希表等),并且意味着并且预计会发生变异。

在上面的例子中,我选择了第一个变体。为什么?复制图像可能需要几微秒和一些RAM,而不是就地更新。但是你可以保留旧的图像,根据你的应用,这可能是有益的。此外,您可以在10个不同的线程中并行使用10种不同的填充颜色对同一图像进行着色,并且不存在锁定问题。

话虽如此,仍然无法像#34那样回答你的问题;它总是更好......&#34;。这取决于您的问题,您的环境,编程语言,您正在使用的库以及更多因素。

所以,让我们说,不可变数据在大多数情况下都是可取的,除非有严重的理由反对它。 (在执行时间上节省几微秒通常不是一个严重的原因。)

换句话说,应该有充分的理由使数据类型可变,而不变性应该是默认值。不幸的是,Java不是支持这种方法的语言,相反,默认情况下一切都是可变的,并且需要花费一些力气才能使它与众不同。

答案 1 :(得分:1)

唯一正确的答案是:

取决于”。

这就是工程学的全部意义所在。做出正确的交易。假设您是一名在该市工作的工程师,并负责为市中心设计新的垃圾箱。你有一些决定要做。你想让它们变大,所以它们可以包含大量垃圾,并且在繁忙的日子里不会溢出。但是你也想让它们变小,这样它们就不会占用人行道上的大量空间。你想让它们变亮,这样它们在排空时可以很容易地处理,而且很重,所以它们不会被风吹过或被流氓踢过。因此,这不是一个大或小,重或轻的问题,而是多大和多重的问题。

在软件工程中,还有许多相互排斥的品质,您必须在项目中做出正确的选择。一些例子:

  • 延迟与吞吐量 - 您希望对请求做出快速反应,还是希望完成大量工作;
  • 内存与cpu - 你可以使用大量内存作为查找表,还是会烧掉cpu时间来计算答案。

不可变与可变

不可变类型的优点是它们是线程安全的。线程A和B都可以引用同一个对象,但仍然可以确保它的值不会在不使用锁的情况下意外更改。如果线程A想要更改值,那么它可以通过更改它保存到新对象的引用来实现;线程B仍然很高兴地抓住原始对象。

让对象的值意外更改不仅是并发编程中的问题,而且当您的类的用户不期望它时也会发生。这就是defensive copying概念的原因。

java中的stock Date类是可变的。因此,请考虑使用Person getter和getBirthDate() setter的setBirthDate()类。作为Person课程的用户,您只能通过使用设定者来更改此人的出生日期,但如果您的获取者未返回防御性副本,则该课程的用户也可以更改意外地通过更改从Date收到的getBirthDate()对象。

因此,不可变类型使程序成为线程安全(r)且不易出错,因此通常是一个好主意,但您不能总是使用它们。您的fillWithColor()函数是一个不太可行的示例。

Canvas类是一个可变对象。您将拥有fillWithColor()功能,还有drawLine()drawElipse()drawText()等等。使用这些功能构建图形可能需要多次调用。考虑绘制一个“no parking”流量标志:

  1. 填充背景色
  2. 画一个红色圆圈
  3. 在其中画一个白色圆圈
  4. 在里面画一个黑色的
  5. 在其上画一条红色的对角线
  6. 如果您的Canvas类是不可变的,则需要五倍的内存量并处理五倍像素数。这真是一个微不足道的例子。考虑猎豹的this SVG图像。背面的每个位置都是对绘图功能的调用。

    取决于

    我会说你应该尽可能使用不可变类型,并且在不可以的地方使用不可变类型。通常,这种差异沿着小数据类型而非大数据类型。

    如果您的类型引用的数据结构小到足以成为值类型,那么它应该是一个不可变的引用类型。就像java Date应该是不可变的一样,毕竟它只有8个字节。 如果你的类型引用了一些大的东西,并且你需要对它进行许多操作,那么你必须务实并使它成为一个可变类型。与您的Canvas示例一样,在所有图像都可以是兆字节之后。

    需要可变和不可变。