Java - 修改对象的内部数据,或使用修改后的数据创建新对象

Jak*_*nce 3 java oop immutability

问题

在Java中,按照标准的代码风格和设计原则,什么时候修改对象的内部数据比较合适,什么时候用修改后的数据新建一个对象比较合适呢?

例如,假设我有一个类 Vector2D,它包含一个 x 分量和一个 y 分量,我想旋转该向量:

方法A

public class Vector2D{
    private int x;
    private int y;
    public Vector2D(int x, int y){
        this.x = x;
        this.y = y;
    }
    public void rotate(double angle){
        //code for finding rotated vector
        x = rotated_x;
        y = rotated_y;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后可以旋转 Vector2D 对象 vector1.rotate(angle)

方法B

public class Vector2D{
    private final int x;
    private final int y;
    public Vector2D(int x, int y){
        this.x = x;
        this.y = y;
    }
    public Vector2D rotate(double angle){
        //code for finding rotated vector
        Vector2D rotated = new Vector2D(rotated_x, rotated_y);
        return(rotated);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后可以旋转 Vector2D 对象 vector1 = vector1.rotate(angle)

迄今为止的发现

到目前为止,我对这个问题的研究使我相信方法 B 对于允许方法链很有用,例如vector1 = vector1.rotate(angle).scale(scalar),它有自己的好处,更多的是在可读性方面。

这也让我意识到方法 B 允许您使对象不可变,所以我想通过扩展我的问题也是:

什么时候让对象不可变是合适的,尤其是在这个例子中?(一般来说,对于点、向量、多边形等。所有这些类型都应该是不可变的吗?)

编辑:

我在创建这篇文章时想到的项目是一个游戏,其中实体由多边形表示,并使用向量进行操作。

我相信由于预期的应用程序,确保类是线程安全的应该不是问题,因为 Vector 和 Polygon 数据无论如何都应该由逻辑线程更改。

这也意味着对于每个实体,这些操作可能每秒执行 60 次(每个游戏周期)。这是否意味着每次实例化新对象的任何开销都会迅速增加?

Enn*_*oji 5

通常,您应该更喜欢使对象不可变。您可能想要选择可变对象的原因是:

  1. 这些对象有很多状态,因此创建起来很昂贵。例如,想象一个巨大的矩阵。如果您不允许突变,则每次都必须复制矩阵,这可能会非常昂贵(参见pandas例如 - 它提供了两种选择)。
  2. 使其不可变使得 API 非常不直观。这也取决于语言的历史。例如,Java 中的大多数排序库都会改变集合而不是返回副本。如果您打算实施shuffle,即使您不担心(1),也最好改变列表,因为如果您返回副本会令人惊讶。

一些语言和库提出了第三种方式,其中变异返回对象的新“版本”,同时共享先前的状态(请参阅参考资料)。这解决了 (1),从而使您可以从不变性中受益,而无需进行大量复制。有人认为这意味着您实际上应该始终选择不变性,因为随着 (1) 的消失,除了熟悉之外,不再有任何理由选择可变性。实际上,有些算法在没有可变状态的情况下很难表达(例如,参见this)。所以我们可能会继续拥有可变状态。