Java 中的不可变数据 - 静态或实例运算符?

Immutable data in Java - static or instance operators?

想象一下任何 Java class 是完全不可变的。我将使用以下示例:

public class Point2D {
  public final int x;
  public final int y;

  public Point2D(final int x, final int y) {
    this.x = x;
    this.y = y;
  }
}

现在考虑在此 class 上添加一个运算符:一种采用一个或多个 Point2D 实例的方法,以及 returns 一个新的 Point2D.

这有两种可能性 - 静态方法或实例方法:

public static Point2D add(final Point2D first, final Point2D second) {
  return new Point2D(first.x + second.x, first.y + second.y);
}

public Point2D add(final Point2D other) {
  return new Point2D(this.x + other.x, this.y + other.y);
}

是否有任何理由选择一个而不是另一个?两者之间有什么区别吗?据我所知,它们的行为是相同的,所以任何差异都必须是它们的效率,或者它们作为程序员的易用性。

使用静态方法可以防止两件事:

  • 用大多数模拟框架模拟 class
  • 覆盖子中的方法class

根据具体情况,这些事情可能没问题,但长期 运行 也会造成严重的悲伤。

因此,就我个人而言,我只在有充分理由的情况下才使用 static。

尽管如此,鉴于问题中的特定 Point2D class,我会 倾向于 实际使用静态方法。这个 class 闻起来应该有 "value" 语义,所以相同坐标的两个点是相等的并且具有相同的哈希码。我也不明白您将如何有意义地扩展此 class。

想象一下 Matrix2D class。在那里考虑 subclasses 可能很有意义,例如 SparseMatrix。然后,您很可能想要重写计算密集型方法!

两者之间没有实际区别。最重要的是 OO 设计和可读性方面。

操作的静态版本似乎更符合静态工厂模式。除了使用通用的设计模式外,它是一种清晰的创建设计,似乎符合其意图:创建一个新对象。

另一方面,当涉及到不可变对象时,创建新对象的实例方法非常实用。最好的例子是 String 方法(String.concat(string) 等)。在我看来,这更多是一个实用性问题(你不想改变对象的状态;你需要扩充它,但操作必须产生一个新实例)。

Is there any reason to pick one over the other?

可能存在其中一个比另一个更适合的情况(例如,我更喜欢静态方法而不是流管道缩减中的实例版本 - 例如),但没有明显的绝对偏好在这里申领。所以...

  • 我会使用静态方法进行工厂操作(尽管为了清楚起见,我会将该方法称为更像 create...newInstance...
  • 我会使用实例方法进行转换操作 return 新实例以避免改变对象。

首先也是最重要的,如果它是不可变的,则使其对其他人不可用class。尽管您可以隐藏构造函数,但通常使用 final。在这种情况下不是特别相关,但是静态创建方法允许公共值成为重用实例,选择专业实现并省略丑陋的菱形 (<>) 符号。 (如果您调用静态创建方法 of,则在使用类型名称进行限定时很明显可以使用。)

加法一般写成中缀。如果涉及子表达式,这将使客户端代码看起来更好,尽管 Java 语法仍然会强制您在任何地方都使用括号。静态方法需要对客户端进行限定或 import static(如果该方法的名称类似于 and,后者就没有多大帮助,如果有其他静态方法不存在,则 'import *' 是不好的没有资格就没有意义)。

为对象在某种意义上是函数附带的情况保留静态方法。例如 Stringjoinformat.

至于测试,应该没有必要模拟值 class 或静态方法。不可变类型应该有可信的实现,因此不能被其他人子类型化。