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

Java-修改对象';的内部数据,或使用修改后的数据创建新对象 问题,java,oop,immutability,Java,Oop,Immutability,在Java中,根据标准代码样式和设计原则,何时更适合修改对象的内部数据,何时更适合使用修改后的数据创建新对象 例如,假设我有一个类Vector2D,它包含一个x分量和一个y分量,我想旋转这个向量: 接近A 然后可以使用vector1旋转Vector2D对象。旋转(角度) 方法B 然后可以使用vector1=vector1旋转Vector2D对象。旋转(角度) 迄今为止的调查结果 到目前为止,我对这个问题的研究使我相信方法B对于允许方法链接非常有用,例如vector1=vector1.rotate

在Java中,根据标准代码样式和设计原则,何时更适合修改对象的内部数据,何时更适合使用修改后的数据创建新对象

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

接近A 然后可以使用
vector1旋转Vector2D对象。旋转(角度)

方法B 然后可以使用
vector1=vector1旋转Vector2D对象。旋转(角度)

迄今为止的调查结果 到目前为止,我对这个问题的研究使我相信方法B对于允许方法链接非常有用,例如
vector1=vector1.rotate(angle)、scale(scale)
,它有自己的优点,更多的是在可读性方面

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

什么时候使对象不可变是合适的,特别是在本例中?(一般来说,对于点、向量、多边形等,所有这些类型是否都是不可变的?)

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

我相信,由于预期的应用程序,确保类是线程安全的不应该是一个问题,因为向量和多边形数据无论如何只能由逻辑线程更改


这也意味着,这些操作可能每秒为每个实体执行60次(每个游戏滴答声)。这是否意味着每次实例化新对象所产生的任何开销都可能迅速增加?

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

如果是多线程的情况,您有许多线程并行执行任务,那么第二个选项会很方便,因为不可变对象本质上是线程安全的,您将避免许多潜在的麻烦。但这种方法也带来了一种折衷,即每次修改对象的内部状态都会产生创建新对象的开销


另一方面,如果您确信可以避免并行访问的危险,那么您可以使用第一种方法,这当然比直接比较快,因为它避免了大量对象实例化。

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

因此,一般来说,如果您没有上述任何特殊问题,您可以使用可变对象来避免为自己、jvm和gc做额外的工作:)

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

通过一种方法,您可以拥有setter并返回这些方法的修改对象。因此,您还可以使用“链接”功能

因此,真正的问题是:

什么时候使对象不可变是合适的

因为您希望为一个对象保证多个方面,例如:

  • 对它没有任何副作用,因为任何可能的修改方法都将创建并返回一个新实例
  • 也不需要为类的任何实例制作防御副本,因为它实际上是由类本身保证的
  • 不用担心竞争条件:无法修改实例
  • 不变规则设置
Java8日期类(
LocalDate
LocalTime
)是不变性相关用法的好例子

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

在方法A中,您仍然可以返回正在修改的对象并使用fluent API。这并不是第二种方法所独有的

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

  • 不可变对象更简单,因为一旦它们被创建,就没有 改变自己状态的方法,所以没有必要做出改变 对mutator方法的验证

  • 不可变对象本质上是线程安全的。不可能 被多个线程并发访问而损坏


  • 我建议您阅读有效的Java-第三版,第17项,以了解更多有关该主题的信息。我相信这是一本很棒的书,适合所有对成为更好的开发人员感兴趣的人。

    一般来说,你应该更喜欢使对象不可变。选择可变对象的原因可能是:

  • 对象有很多状态,因此创建成本很高。想象一个巨大的矩阵。如果你不允许变异,你将不得不每次复制矩阵,这可能会非常昂贵(例如,请参见
    pandas
    ——它提供了两种选择)
  • 使其不可变会使API非常不直观。这也取决于语言的历史。例如,Java中的大多数排序库都会对集合进行变异,而不是返回副本。如果要实现shuffle,即使您不担心(1),也最好改变列表,因为如果您返回一个副本,这会让人感到惊讶
  • 一些语言和图书馆提出了第三种方法,即
    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;
        }
    }
    
    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);
        }
    }