默认情况下,Java方法应该是静态的吗?不访问状态的帮助器方法应为静态方法。

2022-08-31 14:57:28

假设你正在A类中编写方法,foo永远不会访问A的任何状态。你对foo做了什么,或者它的行为一无所知。它可以做任何事情。foo()

foo应该始终是静态的,而不管任何其他考虑因素吗?为什么不呢?

似乎我的类总是积累许多私有的帮助器方法,因为我分解任务并应用唯一写入一次的原则。其中大多数不依赖于对象的状态,但在类自己的方法之外永远不会有用。默认情况下,它们应该是静态的吗?最终使用大量内部静态方法是错误的吗?


答案 1

为了回答标题上的问题,通常,默认情况下,Java方法不应该是静态的。Java是一种面向对象的语言。

但是,您所说的内容有点不同。你特别谈到了帮助器方法。

对于仅将值作为参数并返回值而不访问状态的帮助器方法,它们应该是静态的。私有和静态。让我强调一下:

不访问状态的帮助器方法应为静态方法。


1.主要优点:代码更具表现力。

使这些方法成为静态方法至少有一个主要优点:您可以在代码中完全明确该方法不需要知道任何实例状态

代码不言自明。对于其他会阅读您的代码的人来说,事情变得更加明显,甚至在将来的某个时候对您来说也是如此。

2.另一个优点:代码可以更容易推理。

如果你确保该方法不依赖于外部或全局状态,那么它是一个纯函数,即数学意义上的函数:对于相同的输入,你可以确保获得始终相同的输出。

3. 优化优势

如果该方法是静态的并且是纯函数,那么在某些情况下,可以对其进行记忆以获得一些性能提升(在更改使用更多内存时)。

4. 字节码级差异

在字节码级别,如果将帮助器方法声明为实例方法或静态方法,则将获得两个完全不同的东西。

为了帮助使本节更易于理解,让我们使用一个示例:

public class App {
    public static void main(String[] args) {
        WithoutStaticMethods without = new WithoutStaticMethods();
        without.setValue(1);
        without.calculate();

        WithStaticMethods with = new WithStaticMethods();
        with.setValue(1);
        with.calculate();
    }
}

class WithoutStaticMethods {

    private int value;

    private int helper(int a, int b) {
        return a * b + 1;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public int calculate() {
        return helper(value, 2 * value);
    }
}

class WithStaticMethods {

    private int value;

    private static int helper(int a, int b) {
        return a * b + 1;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public int calculate() {
        return helper(value, 2 * value);
    }
}

我们感兴趣的行是对类和 的调用。helper(...)WithoutStaticMethodsWithStaticMethods

不使用静态方法

在第一种情况下,如果没有静态方法,当您调用帮助器方法时,JVM 需要将引用推送到实例以将其传递给 。看看该方法的代码:invokespecialcalculate()

 0 aload_0
 1 aload_0
 2 getfield #2 <app/WithoutStaticMethods.value>
 5 iconst_2
 6 aload_0
 7 getfield #2 <app/WithoutStaticMethods.value>
10 imul
11 invokespecial #3 <app/WithoutStaticMethods.helper>
14 ireturn

位于 0(或 1)处的指令将加载对堆栈上实例的引用,稍后将由 使用。此指令将该值作为函数的第一个参数,并且永远不会使用它,正如我们在这里看到的那样:aload_0invokespecialhelper(...)

0 iload_1
1 iload_2
2 imul
3 iconst_1
4 iadd
5 ireturn

看到没有了吗?它已被不必要地加载。iload_0

使用静态方法

现在,如果您声明帮助器方法,静态,则该方法将如下所示:calculate()

 0 aload_0
 1 getfield #2 <app/WithStaticMethods.value>
 4 iconst_2
 5 aload_0
 6 getfield #2 <app/WithStaticMethods.value>
 9 imul
10 invokestatic #3 <app/WithStaticMethods.helper>
13 ireturn

区别是:

  • 少了一个指令aload_0
  • 现在调用帮助器方法invokestatic

好吧,帮助器函数的代码也略有不同:没有作为第一个参数,所以参数实际上位于位置0和1,正如我们在这里看到的那样:this

0 iload_0
1 iload_1
2 imul
3 iconst_1
4 iadd
5 ireturn

结论

从代码设计的角度来看,声明帮助器方法为静态更有意义:代码不言自明,它包含更多有用的信息。它指出它不需要实例状态即可工作。

在字节码级别,正在发生的事情要清楚得多,并且没有无用的代码(尽管我相信JIT没有办法优化它,但不会产生显着的性能成本)。


答案 2

如果方法不使用实例数据,则它应该是静态的。如果函数是公共的,这将带来重要的效率提升,您不需要创建对象的多余实例来调用函数。可能更重要的是自我文档的优势:通过声明函数是静态的,您可以向读者传达此函数不使用实例数据。

我不明白这里的许多海报的观点,即在Java程序中使用静态函数有问题。如果函数在逻辑上是静态的,请将其设置为静态。Java 库有许多静态函数。数学课上几乎充满了静态函数。

如果我需要一个函数来计算平方根,那么理性的方法是:

public class MathUtils
{
  public static float squareRoot(float x)
  {
    ... calculate square root of parameter x ...
    return root;
  }
}

当然,你可以制作一个看起来像这样的“更OOPy”版本:

public class MathUtils
{
  private float x;
  public MathUtils(float x)
  {
    this.x=x;
  }
  public float squareRoot()
  {
    ... calculate square root of this.x ...
    return root;
  }
}

但是,除了尽可能地实现使用OOP的一些抽象目标之外,这又会更好吗?它需要更多的代码行,并且灵活性较低。

(是的,我现在在标准数学课上有一个平方根函数。我只是用这个作为一个方便的例子。

如果使用静态函数并且很可能使用的唯一位置来自某个类,那么是的,使其成为该类的成员。如果从类外调用它毫无意义,请将其设为私有。

如果静态函数在逻辑上与类相关联,但可以从外部合理地调用,则使其成为公共静态函数。就像Java的parseInt函数在Integer类中,因为它与整数有关,所以这是一个合理的地方。

另一方面,经常发生的情况是,你正在编写一个类,你意识到你需要一些静态函数,但这个函数并没有真正与这个类绑定。这只是你第一次碰巧意识到你需要它,但它可能被其他与你现在正在做的事情无关的类合理地使用。例如,回到平方根的例子,如果你有一个包含纬度和经度的“Place”类,并且你想要一个函数来计算两个地方之间的距离,并且你需要一个平方根作为计算的一部分,(并假设标准库中没有可用的平方根函数), 创建一个单独的平方根函数,而不是将其嵌入到更大的逻辑中,这是非常有意义的。但它并不真正属于你的Place类。这将是为“数学实用程序”或类似工具创建一个单独的类的时候了。

你问,“foo应该总是静态的,不管任何其他考虑因素吗?我会说“差不多,但不完全是。

我能想到的使它不静态的唯一原因是如果一个子类想要覆盖它。

我想不出任何其他原因,但我不排除这种可能性。我不愿意说“在任何情况下都永远不会”,因为有人通常会想出一些特殊情况。