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

时间:2018-10-23 18:26:57

标签: 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;
    }
}

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

方法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);
    }
}

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

到目前为止的发现

到目前为止,我对这个问题的研究使我相信方法B对于允许方法链接(例如vector1 = vector1.rotate(angle).scale(scalar)具有其自身的优势,更多的是在事物的可读性方面。

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

什么时候使对象不可变,在这种情况下尤其如此? (通常,对于点,向量,多边形等。所有这些类型都应该是不可变的吗?)

编辑:

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

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

这还意味着对于每个实体,这些操作可能每秒可能完成60次(每个游戏滴答声)。这是否意味着每次实例化新对象产生的任何开销都会迅速加起来?

6 个答案:

答案 0 :(得分:4)

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

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

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

答案 1 :(得分:2)

这取决于要实现的应用程序类型。

如果是多线程的情况,其中有很多线程并行执行任务,那么第二种选择将很方便,因为不可变对象本质上是线程安全的,并且可以避免很多潜在的麻烦。但是这种方法也有一个折衷方案,您需要为对象的内部状态的每次修改创建新对象的开销。

另一方面,如果您确定可以避免并行访问的危险,则可以使用第一种方法,因为它避免了许多对象实例化,因此在直接比较中当然会更快。

答案 2 :(得分:2)

  到目前为止,我对这个问题的研究使我相信这种方法   B对于允许方法链接有用,例如

使用A方法,您既可以拥有setter,也可以为这些方法返回修改后的对象。因此,您还将拥有“链接”功能。

所以真正的问题是:

  

什么时候使对象不可变?

您要保证一个对象有多种情况,例如:

  • 对此没有副作用,因为任何可能的修改方法都会创建并返回一个新实例
  • 也不需要为该类的任何实例制作防御副本,因为它实际上是由类本身保证的。
  • 不用担心竞争状况:无法修改实例
  • 不变规则设置

Java 8日期类(LocalDateLocalTime)是不变性相关用法的很好例子。

在您的实际示例中,假设Vector2D是一个API类,可以将其传递给不同类型对象的不同方法。
使用可变类,可以在将对象传递给某些方法时在实例上创建任何副作用。因此,它可能以某种不希望的方式更改了该类某些客户端的对象状态。
为了避免这种情况,该类的客户可以制作防御性副本。但是它需要样板和重复的代码为客户。 对于不可变的类,不会发生此问题。客户端可以根据需要使用实例,而不必担心其他客户端的使用。

答案 3 :(得分:0)

对我而言,这取决于。可变对象对于多线程应用程序来说是危险的,同样,当您要通过方法传递对象并且不想更改它们时,也应该使其变为不可变的。

因此在一般情况下,如果您没有上述任何特殊问题,则可以使用可变对象以避免jvm和gc的麻烦:)

答案 4 :(得分:0)

在方法A中,您仍然可以返回要修改的对象,并使用流畅的API。它不是第二种方法独有的。

关于不变性,主要有两个原因:

  1. 不变的对象更简单,因为一旦创建它们,就没有了 改变他们的状态的方式,所以没有必要使 mutator方法的验证。

  2. 不可移植对象本质上是线程安全的。他们不能是     被多个线程同时访问破坏了。

我建议您阅读《有效的Java-第三版》第17项,以了解有关该主题的更多信息。我相信这对于每个有兴趣成为更好的开发人员的人来说都是一本了不起的书。

答案 5 :(得分:0)

优良作法是尽可能使用不可变对象,这些对象易于维护并且可以使用您以更实用的方式编写代码。

相关问题