如果私有帮助器方法可以是静态的,则它们应该是静态的


205

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

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) {... } 
} 

有没有指定任何特别的原因computeOne,并computeMore为静态方法-或任何特别的理由不?

将它们设置为非静态无疑是最容易的,即使它们可以肯定是静态的而不会引起任何问题。


3
又见当不使用Java中的static关键字:stackoverflow.com/questions/1766715/...
马克·巴特勒

Answers:


174

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


2
我最喜欢的线程之一。我正在使用NetBeans,它显示带有斜体字体的静态方法调用。
skiabox 2012年

2
我通常声明这些帮助程序方法,因为这些方法protected static可以使它们很容易在同一包的测试类中进行测试。
詹姆斯

2
@James或简单地static(如果我们只想要测试)。
mrod

3
“将不会修改对象的状态”-关于如何将其与私有方法区分开的完美说明。
HopeKingKing 2015年

110

由于静态方法无法访问,因此可能会导致字节码略小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

}

调用static方法需要两个字节码(byteops?):(iconst_0用于参数)和invokestatic
调用非静态方法需要三个:(aload_1对于TestBytecodeSize对象,我想),iconst_0(对于参数)和invokespecial。(请注意,如果这些不是私有方法,则应invokevirtual改为私有方法invokespecial;请参阅JLS§7.7调用方法。)

现在,正如我说的那样,除了invokestatic需要少一个字节码的事实外,我不希望这两者之间在性能上有任何大的不同。invokestatic并且invokespecial应该都比稍快invokevirtual,因为它们都使用静态绑定而不是动态绑定,但是我不知道它们中的任何一个都比另一个快。我也找不到任何好的参考。我能找到的最接近的是1997年JavaWorld的这篇文章,基本上重述了我刚才所说的内容:

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

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

因此,总而言之……我想我仍然坚持我之前说的话。速度不应该是一个选择另一个的原因,因为速度充其量是微优化。


所有这些否定的投票似乎都是一个非常简单的答案。为了真理,正义……而+
史蒂夫·B

谢谢。(尽管我可以删除它并在-3时获得徽章。:D)
迈克尔·迈尔斯

2
无论如何,JIT编译器都会内联并对其进行优化,因此字节码指令的数量与它的速度无关。
Esko Luontola 09年

3
这就是为什么我说“我认为它不会对速度产生任何影响(如果确实如此,那么它可能太小而无法整体产生影响)”。
迈克尔·迈尔斯

7
在进行任何此类微优化之前,您应考虑热点编译器的作用。底线:编写代码以提高人类可读性,并将优化工作留给编译器
Ichthyo 2014年

18

我个人比较喜欢将它们声明为静态,因为很明显它们是无状态的。


18

答案是……这取决于。

如果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 */ } 
}

5
+1:尽管示例很糟糕,但太多的可能是静态的方法却是不好的代码味道。顺便说一下,静态方法实际上是函数,它们根本不是面向对象的。它们可能属于另一个类的方法,或者您可能缺少此函数可能属于方法的类。
比尔K

11

您可能要声明静态帮助器方法的一个原因是,是否需要在类构造函数中“ before” this或调用它们super。例如:

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(...)委托给另一个构造函数,则直到委托后才能调用实例方法(但静态变量是OK正如你指出)

10

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

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

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

除此之外,差异很小,我强烈怀疑传递给实例方法的此指针是否会产生重大差异。


4
不将其静态化的好处是,有人可以继承并覆盖方法的行为。
克林特·米勒

7
将其限制为私有方法,因此您无法覆盖该方法。
Bhushan Bhangale 09年

的确,对于私有方法,我认为它没有太大的区别。对于公共方法,我同意您的意思
Axelle Ziegler 2009年

8

正确答案是:

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


5

还是有任何特定原因不[将其声明为静态]?

是。

通过将它们保留为实例方法,您可以允许自己在以后提供其他实现。

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

因此,您将创建一个子类并在新版本中返回该子类,并且由于该方法被声明为实例方法,因此只需让多态性完成其工作即可。

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

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

观看此处,获得一些相关视频:如何设计好的API及其重要性

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


1
完全同意。出于这个原因,我通常会尝试完全避免使用静态变量和私有变量。我认为其他大多数答案都没有讲到重点。
克林特·米勒

22
我们谈论的是私人助手,这意味着它们不属于该类的公共API。这意味着没有任何事情可以阻止您稍后提供其他实现,使它们成为非静态的或进行任何其他更改。
约尼克,

Aaaahmm ... Jonik很好。尽管如此,养成良好的习惯应该是足够的理由(当然主观地说)
OscarRyz

2
完全不同意。没有充分的理由就希望更改私有方法而不更改定义它的类。提出建议的唯一方法是通过字节码操作。
彼得·劳瑞

2
如果该方法不是私有的,那么您的建议可能会有意义,但是即使如此,我还是建议您根据当前需求进行设计,而不是根据想象的需求进行设计。
彼得·劳瑞

5

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


2
您不应该为私有方法编写测试。看这里。无需测试私有方法是否是静态的。
BW

@BW这是非常主观的事情。有许多有效的理由来测试私有方法。
ruohola

4

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

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

public class Example {
   //...

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

   //...
}

3

在这种情况下,我更喜欢make computeOnecomputeMorestatic方法。原因:封装。可以访问您的类的实现的代码越少越好。

在您给出的示例中,您声明了computeOne并且computeMore不需要访问类的内部,因此为什么要给类的维护者提供机会来干预内部。


3

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

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

现在来问您一个问题,一个私人助手方法应该定义为静态还是非静态。我将采用Steve的回答,因为将私有方法标记为static表明该方法是无状态的,因为我在编写代码时也遵循此规则。


但是在某些地方只能调用静态方法,例如在我的示例和Chris Marshall的示例中
Kip

是的,请Kip您和Chris回答正确。我没有评论这些,因为它们是正确的答案。我只说了错误的答案。
Bhushan Bhangale 2009年

3

根据经验,我将指出这种私有方法往往相当普遍且可重用。

我认为要做的第一件事是问这个方法在当前类上下文之外是否有用。如果是这样,我将按照所有人的建议进行操作,并将此方法提取为某些utils类的静态方法,希望有人在实施完全相同的新方法之前检查该方法。

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


2

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

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

如果您处于这种困境,在我看来,方法的所有必需数据都在对象之外的代码中浮动。这是你想要的吗?总是每次都将数据收集到一个对象中会更容易吗?您可能只是对提交一个单一模型感到昧。如果可以使用一种方法完成所有操作,则选择静态或非静态并选择它。


2

更具体地讲一个你给出的例子,似乎定义这些方法的目的是为更清晰的代码,当你读它比功能(它们定义为私有的)。在这种情况下,使用static实际上对您没有任何帮助,因为static的目的是公开类功能。


这个小小的答案埋在其他答案的负担之下,确实触及了问题的实质:类级别功能与对象级别功能。
eljenso

谢谢,埃尔,我真的很感激。我也不会介意一点:)
不是,

我写评论时确实给了它+1。
eljenso

1
我不同意。代码清晰对于私有方法仍然很重要。它可能不太重要,但仍然需要努力。
LegendLength

1

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


请参阅下面的答案(我已经在-3处添加了分析,因此它不是很明显)。
Michael Myers

1

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

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


1

主题外:我会将帮助器方法保留在一个仅包含静态方法的独立实用程序/帮助器类中。

使用时使用辅助方法的麻烦(请参阅“相同的类”)是某人可能只是选择将自己无关的辅助方法发布在同一位置


-1:SO不是消息论坛。通过实际提出一个适当的问题,您会做得更好。
斯图·汤普森

1
我更喜欢将helper方法与它们绑定/关联为静态的类一起使用。更重要的是,如果可以在外部使用它们,我可以将它们公开。这样,如果应该公开暴露并大量使用该类的API,将更容易维护。
Ivaylo Slavov

1
class Whatever {

    public static varType myVar = initializeClassVariable();

    private static varType initializeClassVariable() {

        //initialization code goes here
    }
}

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


1
  1. 如果没有static修饰符,则无法确定该方法是无状态的,而无需进行其他分析(在重新编写方法时可以轻松完成)。

  2. 然后,“静态”修饰符可能会为您提供关于重构的想法,而其他事情可能会让其他人觉得无用。例如,将方法移至某些Utility类或将其转换为成员方法。


0

我将其声明为静态,以将其标记为无状态。

Java没有更好的机制来处理未导出的次要操作,因此我认为私有静态是可以接受的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.