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

时间:2018-12-09 17:44:21

标签: java static immutability

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

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;
  }
}

现在考虑在此类上添加一个运算符:一种方法,该方法接受一个或多个Point2D的实例,并返回一个新的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);
}

有没有理由选择一个?两者之间有什么区别吗?据我所知,它们的行为是相同的,因此任何差异都必须在于它们的效率或作为程序员使用它们的容易程度。

3 个答案:

答案 0 :(得分:4)

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

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

根据具体情况,这些事情可能还可以,但是从长远来看,它们也会带来严重的悲伤。

因此,就我个人而言,我仅在确实有充分理由时才使用静态。

尽管如此,给定问题中特定的Point2D类,我会尝试实际使用静态方法。此类闻起来像应该具有“值”语义,以便相同坐标的两个点相等并且具有相同的哈希码。我也看不到您将如何有意义地扩展此类。

想象一个Matrix2D类。在那里考虑子类(例如SparseMatrix)可能很有意义。然后,很可能您想覆盖计算密集型方法!

答案 1 :(得分:2)

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

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

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

  

有什么理由选择一个吗?

在某些情况下,一种方法比另一种方法更合适(例如,在流管道的简化中,我更喜欢使用静态方法而不是实例版本),但是没有明显的绝对优先选择在这里声称。所以...

  • 我会在工厂操作中使用静态方法(尽管为了清楚起见,我会将该方法称为create...newInstance...
  • 我将使用实例方法进行转换操作,这些操作将返回新的实例以避免变异对象。

答案 2 :(得分:1)

首先,如果它是不可变的,请使其不能为其他子类所用。尽管可以隐藏构造函数,但通常使用final。在这种情况下不是特别相关,但是静态创建方法允许将公共值用作重用实例,选择专业实现,并避免使用丑陋的菱形(<>)表示法。 (如果您调用静态创建方法of,则在使用类型名称进行限定时可以使用它。)

加法通常写为infix。如果涉及到子表达式,尽管Java语法仍然会迫使您到处都有括号,但这将使客户端代码看起来更好。静态方法需要客户端的限定条件或import static(如果该方法的名称类似于and,则后者并没有什么帮助,而如果有其他静态方法不支持,则'import *'是不好的)没有资格就有意义。)

在某种意义上,对象是函数附带的情况下,请保留静态方法。例如String的{​​{1}}和join

对于测试,不必模拟值类或静态方法。不可变类型应该具有可信的实现,因此不能被其他子类型取代。