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

Posted

技术标签:

【中文标题】如果私有帮助方法可以是静态的,那么它们是不是应该是静态的【英文标题】:Should private helper methods be static if they can be static如果私有帮助方法可以是静态的,那么它们是否应该是静态的 【发布时间】:2010-10-07 00:50:45 【问题描述】:

假设我有一个设计用于实例化的类。我在类中有几个私有“帮助器”方法,它们不需要访问任何类成员,并且只对它们的参数进行操作,返回结果。

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 指定为静态方法 - 或任何特殊原因不这样做?

将它们保留为非静态当然是最简单的,即使它们当然可以是静态的而不会引起任何问题。

【问题讨论】:

另请参阅在 Java 中何时不使用关键字 static:***.com/questions/1766715/… 【参考方案1】:

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

【讨论】:

我最喜欢的线程之一。我正在使用 NetBeans,它使用斜体显示静态方法调用。 我通常将这些辅助方法声明为protected static,这使得它们可以在同一个包中的测试类中非常容易地进行测试。 @James 或简单的 static(如果我们只是想要它进行测试)。 “不会修改对象的状态”——完美解释了如何将其与私有方法区分开来。【参考方案2】:

这可能会导致字节码略小,因为静态方法无法访问this。我不认为它对速度有任何影响(如果有的话,它可能太小而无法在整体上产生影响)。

我会将它们设为静态,因为如果可能的话,我通常会这样做。但这只是我。


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

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检索):

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_0(用于参数)和invokestatic。 调用非静态方法需要三个:aload_1(我想是用于TestBytecodeSize 对象)、iconst_0(用于参数)和invokespecial。 (请注意,如果这些不是私有方法,则应该是 invokevirtual 而不是 invokespecial;请参阅 JLS §7.7 Invoking Methods。)

现在,正如我所说,我预计这两者之间的性能不会有太大差异,除了 invokestatic 需要少一个字节码这一事实。 invokestaticinvokespecial 都应该比 invokevirtual 稍微快一点,因为它们都使用静态绑定而不是动态绑定,但我不知道其中一个是否比另一个快。我也找不到任何好的参考资料。我能找到的最接近的是this 1997 JavaWorld article,它基本上重申了我刚才所说的:

最快的指令很可能是invokespecialinvokestatic,因为这些指令调用的方法是静态绑定的。当 JVM 解析这些指令的符号引用并将其替换为直接引用时,该直接引用可能包含指向实际字节码的指针。

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

所以总而言之...我想我仍然坚持我之前所说的话。速度不应成为选择其中一个的理由,因为它充其量只是一个微优化。

【讨论】:

所有这些反对票似乎是一个非常直截了当的答案。 +1 为了真相、正义…… 谢谢。 (虽然我本可以删除它并在 -3 时获得徽章。:D) JIT 编译器无论如何都会内联和优化它们,因此字节码指令的数量与它的速度无关。 这就是为什么我说“我不认为它对速度有任何影响(如果有的话,它可能太小而无法在整体上产生影响)。” 在进行任何此类微优化之前,您应该考虑热点编译器的效果。底线:编写代码以供人类阅读,并将优化留给编译器【参考方案3】:

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

【讨论】:

【参考方案4】:

答案是……视情况而定。

如果 member 是特定于您正在处理的对象的实例变量,那么为什么要将它作为参数传递呢?

例如:

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 */  

【讨论】:

+1:尽管这个例子很糟糕,但太多可能是静态的方法是一种不好的代码味道。顺便说一句,静态方法实际上是函数,它们根本不是 OO。它们可能属于另一个类中的方法,或者您可能缺少该函数可能属于方法的类。【参考方案5】:

您可能想要声明静态辅助方法的一个原因是,如果您需要在类构造函数“之前”thissuper 中调用它们。例如:

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 在这种情况下不能是实例方法。

【讨论】:

你可以在构造函数中调用实例方法,但是如果你使用 super(...) 或 this(...) 委托给另一个构造函数,则不能调用实例方法直到委托之后(但正如你所指出的那样,静态是可以的)【参考方案6】:

我真的想不出私有静态方法的明显优势。话虽如此,使它们成为非静态也没有特定的优势。这主要是一个演示问题:您可能希望将它们设为静态以清楚地强调它们没有改变对象的事实。

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

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

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

【讨论】:

不使其成为静态的好处是有人可以继承并覆盖该方法的行为。 克林特它是一个私有方法,所以你不能覆盖该方法。 没错,对于私有方法,我认为它没有太大的不同。但是,对于公共方法,我同意您所说的【参考方案7】:

正确答案是:

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

【讨论】:

【参考方案8】:

或者有什么特别的理由不[将它们声明为静态的]?

是的。

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

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

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

此外,出于同样的原因,您可以受益于将构造函数设为私有并提供静态工厂方法。

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

看这里有一些相关的视频: How to design a good API and why it matters

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

【讨论】:

完全同意。出于这个原因,我通常会尝试完全避免静态和私有。我认为大多数其他答案都没有抓住重点。 我们谈论的是 private 助手,这意味着它们不是类的公共 API 的一部分。这意味着没有什么能阻止您稍后提供不同的实现,使它们成为非静态的,或进行任何其他更改。 Aaaahmm... 这是一个很好的观点,Jonik。尽管如此,养成一个好习惯就足够了(当然是主观的) 完全不同意。没有充分的理由要更改私有方法而不更改定义它的类。执行您建议的唯一方法是通过字节码操作。 如果方法不是私有的,您的建议可能有意义,但即便如此,我还是建议您根据当前需求进行设计,而不是根据想象的需求进行设计。【参考方案9】:

如果该方法基本上只是一个永远不会使用状态信息的子例程,则将其声明为静态。

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

public class Example 
   //...

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

   //...

【讨论】:

【参考方案10】:

使用静态方法的一个问题是它会使对象在单元测试中更难使用。 Mockito 无法为静态方法创建模拟,您也无法创建该方法的子类实现。

【讨论】:

你不应该为私有方法编写测试。见here。不需要测试私有方法是否是静态的。 @BW 这是一个非常主观的事情。测试私有方法有很多正当理由。【参考方案11】:

静态/非静态问题归结为“我真的需要使用此类的对象吗”?

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

如果您处于这种两难境地,在我看来,您拥有该方法所需的所有数据,这些数据在对象之外的代码中浮动。这是你想要的吗?每次都将数据收集到一个对象中会更容易吗?您可能只是对致力于单一模型感到矛盾。如果您可以使用一种方式完成所有操作,请选择静态或非静态并使用它。

【讨论】:

【参考方案12】:

在这种情况下,我的偏好是制作 computeOnecomputeMore 静态方法。原因:封装。访问类的实现的代码越少越好。

在您给出的示例中,您声明 computeOnecomputeMore 不需要访问类的内部,那么为什么要让类的维护者有机会干预内部。

【讨论】:

【参考方案13】:

我想澄清其他发帖者所说的一些事情,因为它提供了错误的信息。

首先,因为这些方法是私有的,即使你声明它们是静态的,你也不能在这个类之外访问它们。其次,它们是私有的,因此您甚至不能在子类中覆盖,因此静态或非静态没有任何区别。第三,非静态私有方法也可以从类的构造函数中调用,它不必是静态的。

现在来回答您的问题,是否应将私有辅助方法定义为静态或非静态。我会同意史蒂夫的回答,因为将私有方法标记为静态表明该方法是无状态的,因为我在编码时也遵循此规则。

【讨论】:

但有些地方只能调用静态方法,例如我的示例和 Chris Marshall 的示例 是的,你和克里斯的答案是正确的。我没有评论这些,因为它们是正确的答案。我只讲了错误的答案。【参考方案14】:

根据我的经验,我会说这种私有方法往往是相当普遍和可重用的。

我认为首先要做的是询问该方法在当前类上下文之外是否有用。如果是这样,我会完全按照每个人的建议去做,并将这个方法作为静态方法提取到某个 utils 类中,希望有人在执行完全相同的事情之前进行检查。

这种通用的私有方法是项目中大部分代码重复的来源,因为每个开发人员都会在她需要使用的地方独立地重新发明它们。所以这些方法的中心化是一条路。

【讨论】:

【参考方案15】:

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

【讨论】:

这个小答案,隐藏在大量其他答案之下,真正触及了问题的本质:类级功能与对象级功能。 谢谢,我真的很感激。我也不介意投票:) 我在写评论时确实给了它+1。 我不同意。代码清晰对于私有方法仍然很重要。它可能不那么重要,但仍然需要努力。【参考方案16】:

一个原因是,在其他条件相同的情况下,静态方法调用应该更快。静态方法不能是虚拟的,并且不采用隐式 this 引用。

【讨论】:

在下面查看我的答案(我在它已经在 -3 之后添加了分析,所以它不是很明显)。【参考方案17】:

正如许多人所说,将其设为静态! 这是我遵循的经验法则:如果您认为该方法只是一个数学函数,即它是无状态的,不涉及任何实例变量(=> 没有蓝色变量 [in eclipse] 在方法),并且该方法的结果对于“n”次调用将是相同的(当然,使用相同的参数),然后将该方法标记为 STATIC。

如果您认为此方法对其他类有用,则将其移至 Util 类,否则,将该方法作为私有方法放在同一类中。 (最小化可访问性)

【讨论】:

【参考方案18】:

题外话:我会将辅助方法保留在一个独立的实用程序/辅助类中,其中只有静态方法。

在使用点(读取“相同类”)使用辅助方法的问题在于,下线的人可能只是选择在同一个地方发布他们自己不相关的辅助方法

【讨论】:

-1:SO 不是消息论坛。实际提出一个正确的问题,你会做得更好。 我希望将辅助方法以及它们绑定/相关的类作为静态方法。更重要的是,如果它们应该在外部使用,我可以将它们公开。这样一来,如果该类的 API 应该被公开并被广泛使用,那么维护该类的 API 会更容易。【参考方案19】:
class Whatever 

    public static varType myVar = initializeClassVariable();

    private static varType initializeClassVariable() 

        //initialization code goes here
    

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

【讨论】:

【参考方案20】:
    1234563

    然后“静态”修饰符可能会为您提供有关重构的想法,以及其他人可能认为无用的其他内容。 例如。将方法移动到某个实用程序类或将其转换为成员方法..

【讨论】:

【参考方案21】:

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

Java对于不导出的小操作没有更好的机制,所以我认为私有静态是可以接受的。

【讨论】:

以上是关于如果私有帮助方法可以是静态的,那么它们是不是应该是静态的的主要内容,如果未能解决你的问题,请参考以下文章

如果您可以通过反射访问它们,那么拥有私有构造函数有啥意义?

私有静态方法是不是有必要?

java中私有的属性、静态成员可以被子类继承吗?

评估一个静态私有变量(Java),不应该是非法的吗? [复制]

是否可以访问私有静态变量和方法?

在类中使用私有静态变量是否合适?