默认情况下,Java方法应该是静态的吗?不访问状态的帮助器方法应为静态方法。
假设你正在A类中编写方法,foo永远不会访问A的任何状态。你对foo做了什么,或者它的行为一无所知。它可以做任何事情。foo()
foo应该始终是静态的,而不管任何其他考虑因素吗?为什么不呢?
似乎我的类总是积累许多私有的帮助器方法,因为我分解任务并应用唯一写入一次的原则。其中大多数不依赖于对象的状态,但在类自己的方法之外永远不会有用。默认情况下,它们应该是静态的吗?最终使用大量内部静态方法是错误的吗?
假设你正在A类中编写方法,foo永远不会访问A的任何状态。你对foo做了什么,或者它的行为一无所知。它可以做任何事情。foo()
foo应该始终是静态的,而不管任何其他考虑因素吗?为什么不呢?
似乎我的类总是积累许多私有的帮助器方法,因为我分解任务并应用唯一写入一次的原则。其中大多数不依赖于对象的状态,但在类自己的方法之外永远不会有用。默认情况下,它们应该是静态的吗?最终使用大量内部静态方法是错误的吗?
为了回答标题上的问题,通常,默认情况下,Java方法不应该是静态的。Java是一种面向对象的语言。
但是,您所说的内容有点不同。你特别谈到了帮助器方法。
对于仅将值作为参数并返回值而不访问状态的帮助器方法,它们应该是静态的。私有和静态。让我强调一下:
不访问状态的帮助器方法应为静态方法。
使这些方法成为静态方法至少有一个主要优点:您可以在代码中完全明确该方法不需要知道任何实例状态。
代码不言自明。对于其他会阅读您的代码的人来说,事情变得更加明显,甚至在将来的某个时候对您来说也是如此。
如果你确保该方法不依赖于外部或全局状态,那么它是一个纯函数,即数学意义上的函数:对于相同的输入,你可以确保获得始终相同的输出。
如果该方法是静态的并且是纯函数,那么在某些情况下,可以对其进行记忆以获得一些性能提升(在更改使用更多内存时)。
在字节码级别,如果将帮助器方法声明为实例方法或静态方法,则将获得两个完全不同的东西。
为了帮助使本节更易于理解,让我们使用一个示例:
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(...)
WithoutStaticMethods
WithStaticMethods
在第一种情况下,如果没有静态方法,当您调用帮助器方法时,JVM 需要将引用推送到实例以将其传递给 。看看该方法的代码:invokespecial
calculate()
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_0
invokespecial
helper(...)
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没有办法优化它,但不会产生显着的性能成本)。
如果方法不使用实例数据,则它应该是静态的。如果函数是公共的,这将带来重要的效率提升,您不需要创建对象的多余实例来调用函数。可能更重要的是自我文档的优势:通过声明函数是静态的,您可以向读者传达此函数不使用实例数据。
我不明白这里的许多海报的观点,即在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应该总是静态的,不管任何其他考虑因素吗?我会说“差不多,但不完全是。
我能想到的使它不静态的唯一原因是如果一个子类想要覆盖它。
我想不出任何其他原因,但我不排除这种可能性。我不愿意说“在任何情况下都永远不会”,因为有人通常会想出一些特殊情况。