关于java:如果私有帮助方法可以是静态的,那么它们应该是静态的

Should private helper methods be static if they can be static

假设我有一个被设计为要实例化的类。我在类中有几个私有的"helper"方法,它们不需要访问任何类成员,只对它们的参数进行操作,返回一个结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Example {
   private Something member;

   public double compute() {
       double total = 0;
       total += computeOne(member);
       total += computeMore(member);
       return total;        
   }

   private double computeOne(Something arg) { ... }
   private double computeMore(Something arg) {... }
}

有没有特别的理由将computeOnecomputeMore指定为静态方法,或者有什么特别的理由不指定?

当然,让它们保持非静态状态是最容易的,即使它们肯定是静态的,不会引起任何问题。


我更喜欢这样的辅助方法是private static;这将使读者明白,它们不会修改对象的状态。我的IDE还将以斜体显示对静态方法的调用,因此我知道该方法是静态的,而不需要查看签名。


它可能会导致字节码稍小,因为静态方法无法访问this。我认为它在速度上没有任何区别(如果有,它可能太小了,无法整体上产生区别)。

我会使它们静止,因为如果可能的话,我通常会这样做。但那只是我。

编辑:这个答案一直被否决,可能是因为关于字节码大小的断言没有根据。所以我将实际运行一个测试。

1
2
3
4
5
6
7
8
9
10
11
12
class TestBytecodeSize {
    private void doSomething(int arg) { }
    private static void doSomethingStatic(int arg) { }
    public static void main(String[] args) {
        // do it twice both ways
        doSomethingStatic(0);
        doSomethingStatic(0);
        TestBytecodeSize t = new TestBytecodeSize();
        t.doSomething(0);
        t.doSomething(0);
    }
}

字节码(用javap -c -private TestBytecodeSize检索):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Compiled from"TestBytecodeSize.java"
class TestBytecodeSize extends java.lang.Object{
TestBytecodeSize();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

private void doSomething(int);
  Code:
   0:   return

private static void doSomethingStatic(int);
  Code:
   0:   return

public static void main(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   invokestatic    #2; //Method doSomethingStatic:(I)V
   4:   iconst_0
   5:   invokestatic    #2; //Method doSomethingStatic:(I)V
   8:   new     #3; //class TestBytecodeSize
   11:  dup
   12:  invokespecial   #4; //Method"<init>":()V
   15:  astore_1
   16:  aload_1
   17:  iconst_0
   18:  invokespecial   #5; //Method doSomething:(I)V
   21:  aload_1
   22:  iconst_0
   23:  invokespecial   #5; //Method doSomething:(I)V
   26:  return

}

调用静态方法需要两个字节码(byteops?):iconst_0invokestatic。调用非静态方法需要三种方法:aload_1(我想对于TestBytecodeSize对象)、iconst_0(参数)和invokespecial。(请注意,如果这些方法不是私有方法,则应该是invokevirtual而不是invokespecial;请参见jls§7.7调用方法。)

现在,正如我所说的,除了invokestatic需要更少的字节码之外,我不希望这两个字节码在性能上有任何差别。invokestaticinvokespecial都应该比invokevirtual快一点,因为它们都使用静态绑定而不是动态绑定,但我不知道两者是否都比另一个快。我也找不到任何好的推荐人。我能找到的最接近的是1997年的JavaWorld文章,它基本上重述了我刚才所说的:

The fastest instructions will most likely be invokespecial and invokestatic, because methods invoked by these instructions are statically bound. When the JVM resolves the symbolic reference for these instructions and replaces it with a direct reference, that direct reference probably will include a pointer to the actual bytecodes.

但自1997年以来,很多事情都发生了变化。

所以最后…我想我还是坚持我以前说过的话。速度不应该是选择其中一个的原因,因为它充其量只是一个微观优化。


答案是…视情况而定。

如果成员是特定于您所处理的对象的实例变量,那么为什么要将其作为参数传递呢?

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Example {
   private Something member;

   public double compute() {
       double total = 0;
       total += computeOne();
       total += computeMore();
       return total;        
   }

   private double computeOne() { /* Process member here */ }
   private double computeMore() { /* Process member here */ }
}


我个人的偏好是声明它们是静态的,因为它是一个明确的标志,表明它们是无状态的。


如果需要在类构造函数"before"thissuper中调用静态helper方法,则可能需要声明这些方法。例如:

1
2
3
4
5
6
7
8
9
public class MyClass extends SomeOtherClass {
    public MyClass(String arg) {
       super(recoverInt(arg));
    }

    private static int recoverInt(String arg) {
       return Integer.parseInt(arg.substring(arg.length() - 1));
    }
}

这是一个有点做作的示例,但显然,在本例中,recoverInt不能是实例方法。


我真的想不出私有静态方法的明显优势。也就是说,使它们非静态化也没有具体的好处。这主要是一个表示的问题:您可能希望使它们保持静态,以清楚地强调这样一个事实:它们并没有改变对象。

对于具有不同访问权限的方法,我认为有两个主要参数:

  • 可以在不创建对象实例的情况下调用静态方法,这很有用
  • 静态方法不能被继承,如果需要多态性,这可能是一个问题(但与私有方法无关)。

除此之外,差异非常小,我强烈怀疑传递给实例方法的额外这个指针是否会产生显著的差异。


正确答案是:

任何不从字段中获取任何信息且不将任何信息放入字段的方法都不必是实例方法。任何不使用或更改类或对象中任何字段的方法都可能是静态方法。


or any particular reason not to [declare them as static]?

对。

通过将它们保留为实例方法,您可以在以后提供不同的实现。

这听起来可能很愚蠢(实际上,如果这些方法仅由您在50行程序中使用的话),但是在更大的应用程序中,或者在其他人使用的库中,您可能决定选择更好的实现,但不想破坏现有的代码。

所以您创建一个子类并在新版本中返回它,并且由于这些方法被声明为实例方法,所以您只需要让多态性完成它的工作。

此外,您可以从使构造函数私有化中获益,并出于同样的原因提供静态工厂方法。

所以,我的建议是将它们保留为实例方法,并尽可能避免静态。利用语言提供的活力。

请参阅此处了解一些相关视频:如何设计一个好的API以及它为什么重要

尽管它与"静态vs实例"方法讨论没有直接关系,但它触及了API设计中的一些有趣的点。


关于静态方法的一个问题是,它会使对象在单元测试中更难使用。mockito不能为静态方法创建mocks,也不能创建该方法的子类实现。


如果该方法基本上只是一个子例程,它将永远无法预见地使用状态信息,那么将其声明为静态的。

这允许它在其他静态方法或类初始化中使用,即:

1
2
3
4
5
6
7
8
public class Example {
   //...

   //Only possible if computeOne is static
   public final static double COMPUTED_ONE = computeOne(new Something("1"));

   //...
}

根据经验,我会说,这种私有方法趋向于非常通用和可重用。

我认为首先要做的是问一个问题,这个方法在当前类上下文之外是否有用。如果是这样的话,我会完全按照每个人的建议来做,并将这个方法作为静态的提取到一些utils类中,希望有人在实现新方法之前进行检查,执行完全相同的操作。

这种通用的私有方法是项目中代码复制的主要来源,因为每个开发人员都独立地将它们重新投入到她需要使用的地方。因此,这种方法的集中化是一种可行的方法。


在这种情况下,我倾向于使用computeOnecomputeMore静态方法。原因:封装。访问类实现的代码越少越好。

在您给出的示例中,您声明computeOnecomputeMore不需要访问类的内部,所以为什么要给类的维护人员机会来干预内部。


我想澄清一些其他海报所说的错误信息。

首先,由于这些方法是私有的,即使您声明它们是静态的,您也不能在这个类之外访问它们。其次,它们是私有的,所以您甚至不能在子类中重写它们,所以静态或非静态都没有任何区别。第三,非静态私有方法也可以从类的构造函数调用,它不需要是静态的。

现在我们来讨论一下私有helper方法是应该定义为静态的还是非静态的。我将使用Steve的答案,因为标记一个私有方法static表明这个方法是无状态的,因为我在编码时也遵循这个规则。


更具体地说,对于您给出的示例,在您阅读这些方法时,定义这些方法的目的似乎更多地是为了代码清晰,而不是为了功能(它们被定义为私有)。在这种情况下,使用static实际上对您没有任何帮助,因为static的目的是公开类功能。


静态/非静态问题归结为"我真的需要使用这个类的对象吗"?

那么,您是在不同的方法之间传递对象吗?对象是否包含在静态方法上下文之外有用的信息?如果您要同时使用两种方法,是否有任何理由不定义这两种方法?

如果您正处于这种困境中,在我看来,您已经拥有了该方法所需的所有数据,这些数据都在对象外部的代码中浮动。这就是你想要的吗?每次只将数据收集到一个对象中会更容易吗?你可能只是对承诺一个单一的模式持矛盾态度。如果你可以用一种方法来完成所有的工作,那么选择静态的或者非静态的,然后继续进行。


主题外:我将helper方法保存在一个独立的实用程序/helper类中,其中只有静态方法。

在使用点使用helper方法(读取"同一类")的问题是,一行中的某个人可能只是选择在同一位置发布自己不相关的helper方法。


1
2
3
4
5
6
7
8
9
class Whatever {

    public static varType myVar = initializeClassVariable();

    private static varType initializeClassVariable() {

        //initialization code goes here
    }
}

私有静态方法的优点是,如果需要重新初始化类变量,可以在以后重用它们。


一个原因是,所有其他方法都相同,静态方法调用应该更快。静态方法不能是虚拟的,并且不采用隐式此引用。


  • 如果没有静态修饰符,您就无法发现方法是无状态的,没有额外的分析,这在您(重新)编写方法时很容易完成。

  • 然后,"静态"修饰符可能会给您关于重构的想法,以及其他人可能认为无用的东西。例如,将方法移动到某个实用程序类或将其转换为成员方法。


  • 正如许多人所说,让它成为一个静止的!下面是我遵循的经验法则:如果你认为这个方法只是一个数学函数,即它是无状态的,不涉及任何实例变量(=>在方法中没有蓝色变量[在Eclipse中]),并且对于"n"个调用(当然参数相同),该方法的结果将是相同的,那么将该方法标记为静态的。

    如果您认为这个方法对其他类有用,那么将它移到一个util类,否则,将该方法作为私有方法放在sameclass中。(最小化可达性)


    我会声明它们是静态的,以将它们标记为无状态的。

    Java没有更好的机制用于未导出的小操作,因此我认为私有静态是可接受的。