【问题标题】:Should Java methods be static by default?Java 方法是否应该默认是静态的?
【发布时间】:2010-07-27 18:37:28
【问题描述】:

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

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

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

【问题讨论】:

  • 如果foo 从来没有对A 做任何事情,那么为什么它在A 中?
  • 它必须存在于某个地方......而且它只被 A 使用过。
  • All:让我在这里尝试备份 Tom。他说的不是为了踢球而将垃圾扔进A。他说的是从您的方法中提取辅助代码位到辅助方法中的完全常见的情况。但是,当这些辅助方法实际上并没有访问它们所驻留的对象的 ivars 时,问题是,原则上将它们设为静态是否是一种好习惯,因为它们不访问对象状态。这是一个很好的问题。我当然不认为像这样的助手应该被驱逐到单独的实用程序类中。它们仅在 A 的上下文中有用
  • 我敢打赌,一旦您的内部辅助方法足够复杂,您会发现它们保持自己的状态(作为局部变量)。如果方法相互交互,您可能会注意到通过方法参数传输的状态更多。我发现有一个转折点,帮助者应该被提升到他们自己的班级。此外,如果您将事物设为静态,则更难测试和/或替代替代行为。
  • 如果“默认”你的意思是在技术意义上,应该有一个关键字来表示非静态,Java 将函数解释为静态,除非指定了这个关键字,我会说不,原因很简单,类中的大多数函数都不是静态的。但是,如果您的意思是“这应该是我做事的通常方式”的一般意义上的“默认”,我会说是的。有关详细信息,请参阅下面的答案。

标签: java oop


【解决方案1】:

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

但是,你所说的有点不同。您专门谈到了辅助方法。

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

不访问状态的辅助方法应该是静态的。


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

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

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

2。另一个优点:代码可以更简单地推理。

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

3。优化优势

如果该方法是静态的并且是一个纯函数,那么在某些情况下它可能是memoized 以获得一些性能提升(改变使用更多内存)。

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);
    }
}

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

没有静态方法

在第一种情况下,没有静态方法,当您调用辅助方法时,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 调用辅助方法

嗯,帮助函数的代码也有点不同:没有this作为第一个参数,所以参数实际上在位置0和1,我们可以在这里看到:

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

结论

从代码设计的角度来看,将辅助方法声明为静态更有意义:代码自己说话,它包含更多有用的信息。它声明它不需要实例状态来工作。

在字节码级别,发生了什么更加清楚,并且没有无用的代码(虽然我相信 JIT 无法优化它,但不会产生显着的性能成本)。

【讨论】:

  • 这里可能有答案,但它被所有字节码混淆了。
  • 您关于通过“this”引用浪费的 CPU 周期的观点是正确的,确实,我开始在自己的回答中谈到这一点,但后来我将其视为微不足道的。我想如果你调用这个函数一百万次,额外的 CPU 周期可能会加起来。我并不是说你错了,只是在事情的安排中这是一个小问题。
  • -1 标题太“喧闹”。还有你的字节码分析,虽然你的努力真的很棒,但我认为更强烈地证明了使用静态没有真正的技术原因。
  • @CurtainDog:就软件工程的意义而言,第 1 点和第 2 点确实很强大(主要?)技术原因。如果您不能理解它们的重要性,我很抱歉,但我想我不能说得更清楚。
  • 直接从 google 的关于如何为 android 编程的性能指南中复制:Prefer Static Over Virtual “如果您不需要访问对象的字段,请将您的方法设为静态。调用将约为 15%-快 20%。这也是一个很好的做法,因为您可以从方法签名中看出调用该方法不能改变对象的状态。来源:developer.android.com/guide/practices/design/…
【解决方案2】:

如果一个方法不使用实例数据,那么它应该是静态的。如果函数是公共的,这将大大提高效率,您不需要创建一个多余的对象实例来调用函数。可能更重要的是自文档的优势:通过将函数声明为静态,您可以向读者表明该函数不使用实例数据。

我不明白这里许多发帖人的观点,即在 Java 程序中使用静态函数有问题。如果一个函数在逻辑上是静态的,就让它成为静态的。 Java 库有许多静态函数。 Math 类几乎充满了静态函数。

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

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 课程。现在是时候为“数学实用程序”或类似的东西创建一个单独的类了。

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

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

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

【讨论】:

  • +1 用于自我文档说明。代码应该传达程序员的意图,这就是我们为方法/变量使用描述性名称之类的原因。通过将函数声明为静态,您可以清楚地表明它不(也不应该)依赖于内部对象状态。更容易测试的事实也是一个巨大的好处,imo。
  • 是的,但这里的问题是数学处理原语。如果没有,则不需要静态(请参阅 BigNumber 类以供参考)。因此,在这种特殊情况下对静态的需求只是核心问题的一个副作用,即 Java 一开始就不应该有原语。
  • 为什么 Java 一开始就不应该有原语?使用原语比使用对象更有效。即使我正在处理对象,如果我不能扩展“主题”类,我可能想要使用静态,因为它是最终的。或者,如果我需要对我没有创建的对象进行操作,因此无法在所需的子类型中创建,比如它是我没有编写的类的返回值。此外,一个函数可能专门针对我的问题,因此将其放入,例如,String 或 Integer 的子类比澄清更令人困惑。
【解决方案3】:

有趣的问题。实际上,我认为将A 的私有辅助方法设为静态没有意义(当然,除非它们与A 中的可公开访问的静态方法相关)。你没有得到任何东西——根据定义,任何可能需要它们的方法都已经有一个A 的实例可供使用。而且由于它们是幕后的辅助方法,没有什么可以说你(或其他同事)最终不会决定其中一个无国籍的帮助者实际上可能会从了解状态中受益,这可能会导致一些重构麻烦。

我不认为以大量内部静态方法结束是错误,但我也看不出你从中获得了什么好处。我说默认为非静态,除非你有充分的理由不这样做。

【讨论】:

  • 看,我认为重构麻烦会是一件好事。这将迫使人们注意到他们正在极大地改变预期的实现。这将是我想象使用 static 关键字的好处之一。
  • @Tom:也许吧。对于完全存在于黑匣子中的代码,我质疑将未来的程序员捆绑起来的价值。 (当然,虽然你并没有用特别强的绳索将它们绑起来。)但如果该方法是静态的,只是因为这是你采用的默认设置,而不是由于深思熟虑的选择,那么他们会注意到的预期实现究竟是什么?
  • 把私有静态方法改成私有实例方法会有多麻烦?除了删除“静态”之外,您还需要做什么? OTH如果是从静态方法改成非私有实例方法的话,看得出来可能会有些头疼。
  • 我对这个回答表示不满,这表明迄今为止对这个问题的最深思熟虑的理解,因此引发了最有趣的讨论。除了性能方面的考虑(我认为不应该进入这个讨论,这确实是一个风格问题),我倾向于同意 BlairHippo——而且我自己的倾向也属于这些方面。除非我从 git 中设想它们,否则我倾向于不使方法成为静态的。但我认为这可能只是您做正确的事情并保持一致的情况之一。
  • @emory:你是对的,讨厌的因素并没有那么大。我在这里能想到的最坏情况是,如果辅助方法相互通信,这意味着您最终可能不得不对一大堆它们进行去静电处理。这会导致一些相当无用的思考“等一下——这种方法是静态的,还是只是汤姆再次默认使用它?”我的倾向是,如果你要声明一个静态方法,你应该有比“因为我可以”更好的理由,但在这种情况下,无论哪种方式都很难让自己陷入困境。
【解决方案4】:

没有。绝不。静态方法应该是一个例外。 OO 就是让对象的行为围绕对象的状态。恕我直言,理想情况下,不应该有任何(或很少)静态方法,因为与对象状态无关的所有内容都可以(并且为了避免导致对象广告荒谬的概念,应该)放置在模块的普通旧函数中等级。工厂可能例外,因为 Complex.fromCartesian(以维基百科为例)读起来非常好。

当然,这(编辑:模块级函数)在单范式 OO 语言(编辑:如 Java)中是不可能的——这就是为什么我如此热衷于多范式语言设计的倡导者,由方式。但是即使在专门的 OO 语言中,大多数方法也将围绕对象的状态进行,因此是非静态的。也就是说,除非您的设计与 OO 无关 - 但在这种情况下,您使用了错误的语言。

【讨论】:

  • @delnan:-1 Java 在模块级别没有普通的旧函数。
  • 一些优点,但我不同意避免使用静态方法因为您使用的是 Java。正如史蒂夫·麦康奈尔(Steve McConnell)所说,您不想一种语言编写代码,而是要用一种语言编写种代码。
  • @delnan + @Justin Ardini:不涉及对象状态的方法不是不仅有用,而且是可取的:更容易测试,更容易理解行为等等?当然 OO 项目的整体设计应该是 OO 的,这必然涉及到状态,但是一旦你开始分解你的算法,你的 OO 方法是不是最终开始调用可能不太关心状态的子过程?这是我的典型经历。我不明白我在这里看到的对静态方法的普遍敌意,这是我试图用这个问题调查的核心。
  • @Tom:我百分百支持你!就个人而言,我完全支持静态方法。我认为它们在 Java 中不应该是默认的,但它们当然很容易测试和维护。我同意 delnan 的一点是,在 OOP 中,许多方法将与对象密切相关,只要对象具有明确且内聚的目的,这完全没问题。
  • 解释一下:将所有东西都实现为有状态的对象有缺点,因此有时将算法与对象分离是值得的(静态方法是 Java 为我们提供的唯一方法)。我完全同意。但是静态方法并不是真正的“方法”,因为它们是对象的行为,它们只是函数,可能与某些类相关,也可能不相关。在纯 OO 中,它们没有位置。这并不意味着静态方法不好,只是意味着它们在纯 OO 中没有位置——这意味着纯 OO 并不完美。但在 OO 语言中,所有设计都应该大部分 OO。
【解决方案5】:

我经常

根据需要按顺序执行这些步骤:

a) 我在成员方法中编写了一些代码,发现我可能可以重复使用其中的一些代码并且

提取到非静态方法

b) 现在我将看看这个方法是否需要访问状态,或者我是否可以将它的需要放入一个或两个参数和一个返回语句中。如果是后者:

将方法(私有)设为静态

c) 如果我发现我可以在同一个包的其他类中使用此代码,我会

公开方法并将 Method 移动到具有默认可见性的包帮助程序类

例如在包com.mycompany.foo.bar.phleeem 中,我将创建一个具有默认可见性的PhleeemHelperPhleeemUtils 类。

d) 如果我意识到我在整个应用程序中都需要这个功能,我

将帮助类移动到专用实用程序包中

例如com.mycompany.foo.utils.PhleeemUtils

一般来说,我喜欢最低可见度的概念。不需要我方法的人不应该看到。这就是为什么我从私有访问开始,转向包访问,并且仅在它们位于专用包中时才公开。

【讨论】:

    【解决方案6】:

    除非您传入对象引用,否则类上的static 方法会强制该方法本身不能改变对象,因为它无法访问this。在这方面,static 修饰符向程序员提供了有关方法意图的信息,即无副作用。

    反静电纯粹主义者可能希望将它们删除到反实用纯粹主义者肯定反对的实用程序类中。但实际上,除了与新的实用程序类紧密耦合之外,人为地将这些方法从它们唯一的调用站点移开还能实现什么。

    盲目地将常用实用程序方法提取到它们自己的类中的一个问题是,这些实用程序确实应该被视为新的公共 API,即使它仅由原始代码使用。很少有开发人员在执行重构时没有考虑到这一点。使用糟糕的实用程序类快进到其他开发人员。后来有人对扩展进行更改以适合自己。如果你幸运的话,可以测试一两次休息,但可能不会。

    【讨论】:

      【解决方案7】:

      我通常不会将它们设为静态,但可能应该这样做。告诉下一个编码员这个方法不能修改你的对象的状态是很有价值的,当你修改方法来访问一个你正在改变方法的性质的成员时,给你一个警告是很有价值的。

      编码就是与下一位编码员进行交流——不用担心让代码运行起来,这很简单。因此,为了最大限度地提高交流,我会说,如果你真的需要这样的帮助者,将其设为静态是一个好主意。除非您正在制作数学,否则将其设为私有也很重要。喜欢上课。

      【讨论】:

      • +1,这正是我的推理。如果辅助方法不修改任何状态,那么最好通过将其声明为静态来明确这一点。大多数 IDE 也会显示不同的静态方法调用,因此在调用处也很清楚没有修改任何状态。
      • 静态方法可以通过调用实例上的方法来改变实例的状态。那样会很混乱。
      • 我同意你的说法,但我认为函数应该受到包保护......这样你就可以从单独的测试类中测试它们。
      • @Willi Schönborn 仅当实例被传入时,这将使其显而易见。这实际上是一个非常明确、安全的假设,即如果它是一个静态方法,它不会改变任何对象的状态(即未传入)。如果您确定拥有一个包含实例的静态变量,则可能违反这一点 - 如果您这样做,请清楚地记录。
      【解决方案8】:

      Java 将模块、命名空间、adt 和类的概念混为一谈,因此声称某些面向类的 OO-purity 应该阻止您将 java 类用作模块、命名空间或 adt 是荒谬的。

      是的,方法应该是静态的。纯内部支持方法应该是私有的;受保护的辅助方法;实用功能应该是公开的。此外,静态字段、静态常量和公共静态方法之间存在天壤之别。第一个只是“全局变量”的另一个词;并且几乎总是要避免的,即使通过访问器方法进行调解也几乎不能限制损害。第二个是将 java 类视为符号常量的命名空间,这是完全可以接受的。第三个是将 java 类视为函数的模块,作为一般规则应避免副作用,或者如果有必要,仅限于传递给函数的任何参数。使用 static 将有助于确保您不会通过访问对象的成员而无意中破坏它。

      您会发现静态方法无价的另一种情况是您在 java 中编写函数式代码时。在这一点上,大多数由 OO 支持者开发的经验法则都被抛弃了。您会发现自己拥有充满静态方法的类,以及绑定到匿名内部函子的公共静态函数常量。

      最终,java 的作用域结构非常弱,在相同的“类”和“接口”语法下混淆了许多概念。您不应该过多地“默认”为静态,而是随意使用 java 提供的工具来提供命名空间、ADT、模块等,当您觉得需要它们时。

      【讨论】:

        【解决方案9】:

        我发现很难接受那些避免静态方法的理论。他们在那里推广一个完全卫生的面向对象的模型,以反腐的方式清除任何与对象关系的偏差。在面向对象的实践中,我认为没有任何必要的方式来实现反败血症。

        无论如何,所有 java.util.Arrays 类都是静态的。数值类 Integer、Boolean、String 具有静态方法。很多静态方法。这些类中的所有静态方法都可以转换为各自的类实例或从它们各自的类实例转换。

        由于良好的老 Gosling 等人被证明是拥有静态方法的有用榜样 - 避免它们是没有意义的。我意识到有些人困惑到足以拒绝我的回应。许多程序员喜欢将尽可能多的成员转换为静态成员是有原因和习惯的。

        我曾经在一个项目负责人希望我们尽可能使方法静态化并最终确定它们的机构工作。另一方面,我没有那么极端。与关系数据库架构设计一样,这完全取决于您的数据建模策略。

        将方法设为静态应该有一个一致的原因。遵循将方法设为静态的标准 Java 库模式并没有什么坏处。

        最重要的是编程效率和质量。在自适应和敏捷的开发环境中,它不仅要调整项目的粒度以有效地响应需求变化,还要适应编程环境,例如提供一致的编码模型,以充分利用您拥有的编程技能集。归根结底(一个项目几乎永远不会结束),您希望团队成员高效高效,而不是他们是否避免使用静态方法。

        因此,设计一个编程模型,无论您想要 MVP、注入、方面驱动、静态避免/亲和度等,并且知道您为什么想要它们 - 不是因为一些理论上的疯子告诉您您的编程实践会违反oo 原则。请记住,如果您在一个行业工作,那么它始终是质量和盈利能力,而不是理论上的纯度。

        最后什么是面向对象?面向对象和数据规范化是创建正交信息视角的策略。例如,在早期,IBM 手册的编写非常正交。也就是说,如果一条信息写在这数千本手册的某个页面中,他们会避免重复该信息。这很糟糕,因为您将阅读学习如何执行某项任务并经常遇到其他手册中提到的概念,并且您必须熟悉手册的“数据模型”才能在数千个手册。

        出于同样的原因,OS/2 未能与 Microsoft 竞争,因为 IBM 的正交性概念纯粹是基于机器和数据的,并且 IBM 如此自豪地宣称他们真正的面向对象与 Microsoft 迎合人类视角的虚假面向对象。他们忘记了我们人类对信息有各自不同的正交视角,这些视角不符合数据和基于机器的正交性,甚至彼此不符合。

        如果您熟悉树的拓扑结构,您会意识到您可以选择任何叶节点并将其设为根。甚至任何节点,如果您不介意拥有多主干树。每个人都认为他/她的节点是根,而实际上任何一个都可能是根。如果您认为面向对象的观点是经典,请再想一想。更重要的是尽量减少被接受为候选根的节点数。

        需要在有效性和效率之间取得折衷。拥有一个其他程序员几乎无法有效使用的高效数据或对象模型是没有意义的。

        【讨论】:

        • 你的例子的问题是它们与原语有着千丝万缕的联系,如果 Java 更面向对象,对这些静态方法的需求就会消失。
        【解决方案10】:

        如果它对这个类的对象不做任何事情,但实际上属于这个类(我会考虑将它移到其他地方),是的,它应该是静态的。

        【讨论】:

        • 虽然理论上很棒,但很多时候你只需要重构几行遵循某种模式但功能强大的冗余代码。也就是说,您并没有错——认真尝试确保您使用方法而不是函数,并且您的方法放置正确。
        【解决方案11】:

        如果可以避免,请不要使用静态。它与继承(覆盖)冲突。

        另外,没有被问到但稍微相关,不要公开实用程序方法。

        至于其余的,我同意马特 b。如果您有大量不使用状态的潜在静态方法,只需将它们放在私有类中,或者可能是受保护的或包受保护的类。

        【讨论】:

        • 这些是私有方法,不会被继承。
        • 如果 X 不能很好地与继承配合使用,通常不是 X 不好的迹象,而是继承。
        • @Willi 我不认为继承不好。它肯定被过度使用,主要是出于方便的原因,但还不错。 Java 中缺少 mixin 可能很糟糕,因为它会促使人们滥用继承。
        【解决方案12】:

        这取决于例如java.lang.Math 没有非静态的方法。 (您可以进行静态导入来编写 cos() 而不是 Math.cos()) 这不应该被过度使用,但作为一些旨在被称为实用程序的代码是可以接受的。 I.g Thread.currentThread()

        【讨论】:

          【解决方案13】:

          静态方法用于标识与从该类创建的对象无关但与类本身有关的方法(或变量)。例如,您需要一个变量来计算创建的对象数。你会这样写:'private static int instances = 0;'然后在该类的构造函数中放入一些增加“实例”的东西,这样你就可以计算它了。

          【讨论】:

          • 不,静态方法不是“类的方法”。它们只是不依赖或不修改实例状态的方法。
          • @Bruno 错了。格伦完全正确。静态方法(或“类方法”)属于它定义的类(推荐阅读download-llnw.oracle.com/javase/tutorial/java/javaOO/…)。您无法访问任何实例状态的效果是该方法不属于该实例的结果! +1
          【解决方案14】:

          在创建静态方法之前一定要三思而后行,但有时它们是一个很好的解决方案。

          Effective Java 中的“第 1 项:考虑静态工厂方法而不是构造函数”中的 Joshua Bloch 提出了一个非常有说服力的案例,即静态方法可能非常有益。他以 java.util.Collections 类的 32 个静态工厂方法为例。

          在一种情况下,我有一个 POJO 类的层次结构,其实例可以自动序列化为 XML 和 JSON,然后反序列化回对象。我有使用 Java 泛型进行反序列化的静态方法:fromXML(String xml)fromJSON(String json)。它们返回的 POJO 类型不是先验已知的,而是由 XML 或 JSON 文本确定的。 (我最初将这些方法打包到一个辅助类中,但将这些静态方法移到根 POJO 类中在语义上更清晰。)

          其他几个例子:

          • 使用类作为命名空间来对相关方法进行分组(例如,java.lang.Math)。
          • 该方法确实是一个私有类特定的辅助方法,无需访问实例变量(此处引用的案例)。只是不要将 this-equivalent 偷偷带入它的参数列表!

          但是不要不假思索地使用静态函数,否则你可能会陷入更杂乱无章、更程序化的编程风格。

          【讨论】:

            【解决方案15】:

            不,静态的使用应该是相当小众的。

            在这种情况下,OP 很可能在传递给静态方法的参数中处于“隐藏”状态。提出问题的方式并不明显(foo() 没有输入或输出),但我认为在现实世界的示例中,实际上应该成为对象状态一部分的东西会很快消失。

            在一天结束时,每次调用 obj.method(param) 都会解析为 method(obj, param),但这比我们设计时的水平要低。

            【讨论】:

              【解决方案16】:

              如果它只被 A 中的方法使用并且在它之外没有任何用处,那么它应该是静态的(并且可能被放置在 Helper 类中。它现在在 A 之外没有任何用处,但不能保证它永远不会有。否则,它不应该。

              如果它与A的状态没有任何关系,它可能在其他地方有用......

              无论如何,这并不是 Java 方法默认为静态的充分理由。

              谈到最后一个问题,默认情况下它们不应该是静态的,因为必须写“静态”让人在写静态方法之前思考。当您拥有异构团队(Java 最有用的团队)时,这是一个很好的做法。

              【讨论】:

                【解决方案17】:

                当你编写一个静态方法时,你应该记住,你将在使用站点使用静态导入(让它看起来无类),因此它的行为应该就像一个没有什么东西的函数并且可能会或可能不会返回某些东西,并且与它所属的类状态隔离。所以静态方法应该很少见。

                如果您似乎制作了很多辅助方法,那么请考虑使用包私有实例方法而不是私有方法。更少的输入,更少的样板,因为您可以将它们重新用作同一包中其他类的帮助器。

                【讨论】:

                  【解决方案18】:

                  我认为“私有静态”(编辑:用于方法)在 Java 中有点矛盾。在我看来,静态方法的要点是提供对对象实例上下文之外的函数的访问。换句话说,它们实际上只有在公开时才有用。如果您只是从单个对象实例的上下文中调用一个方法,并且该方法是私有的,那么将其设为静态是没有意义的。 (编辑:但是,它没有实际的区别)。

                  在这种情况下,我通常会尝试使方法足够抽象以使其在其他上下文中有用,并在实用程序类中将它们公开。将其视为编写支持库代码,并认真考虑您的 api。

                  【讨论】:

                    【解决方案19】:

                    大多数静态方法都是因为

                    1. 您将复杂的方法分解为子方法,或者
                    2. 您希望字符串(或日期,或...)具有一些它没有的功能

                    第一个本身并不坏,但它通常表明您缺少对象。与其使用默认类型(如 String 或 List),不如尝试创建自己的类并将静态方法移至这些类。

                    第二个原因产生了一直流行的 StringUtil、DateUtil、FooUtil 类。这些是有问题的,因为您无法发现它们的存在,因此程序员经常编写这些实用方法的副本。再次,解决方案是避免一直使用 String 和 Date。开始创建您自己的对象,也许通过包装原始对象。静态方法成为新对象的非静态方法。

                    【讨论】:

                      【解决方案20】:

                      如果foo() 与对象A 没有任何关系,那为什么里面有方法?

                      静态方法应该仍然是相关的。如果什么都没有发生,那你为什么要写一个与它没有关联的方法呢?

                      【讨论】:

                      • 它必须存在于某个地方......而且它只被 A 使用过。
                      • 公平地说,因为这是 Java,所以方法必须去某处。在我完成的许多项目中,我们保留了一个 Util 类,它只包含不与任何状态交互的静态方法。毕竟,Java 没有全局性的东西。
                      • 如果它只被 A 中的静态方法使用过,那么它应该是静态的。否则,它不应该。这并不能成为默认情况下它是静态的充分理由。
                      • @Tom Tresansky 你的单元测试代码也应该使用它。你有彻底的单元测试,对吧? (这是 21 世纪。)
                      • @Tom Tresansky:所以他们最好的选择是留在原地。如果您要分解方法,它们很可能会有一些争论。如果您发现这些参数被多个函数使用,最好将它们制成字段。这增加了对 A 状态的依赖,这被认为是一种很好的做法,因为您的方法调用更短。起初我觉得它有争议,但一些 od TDD 书籍强烈推荐它。此外,您还可以免费获得优化 - 每个方法调用都不需要新的大堆栈(复制参数)并且 JIT 可能会内联它们。
                      【解决方案21】:

                      如果 foo 是私有的,它可以是任何东西,不管是静态的还是非静态的。但是,大多数时候它会不是静态的,因为这些是要输入的少一个字。然后,如果您因为更改了代码而需要使用状态,您可以立即使用。

                      当它受到保护或公开时,这取决于它的作用。经验法则是当它不是实例行为的一部分时使其 不是静态,并在不带任何对象的情况下调用它时使其成为 static . 如果您不确定,请问问自己在子类中重写该方法是否有意义。

                      【讨论】:

                      • 哇,你对编程的看法肯定和我不同。我认为代码是供下一个人阅读的东西——所以你给他们的提示越多越好。键入一个或多或少的单词完全无关紧要,尽管我仍在考虑原始问题,但让编译器暗示此方法最初是“静态”编写的,并且通过添加成员访问权限,您正在显着修改使用模式会更多比能够“立即更改代码”更有价值。
                      【解决方案22】:

                      我认为让 Java 中的方法是静态的会导致没有正确理解 OO 的初学者实现相当混乱。我们去过那里并考虑过。如果这些方法默认是静态的,那么我们理解 OO 原理有多难?

                      所以,是的,一旦你掌握了这个概念,在所有方法中都使用静态(作为重构的结果)有点让人头疼。我认为我们对此无能为力。

                      NB:让我猜猜,你有没有读过Clean Code

                      【讨论】:

                        【解决方案23】:

                        很多有趣的答案。

                        如果你拼命寻找规则,那么使用这个:

                        如果代码仅由单个类的实例方法使用,则将其设为实例方法 - 它只是从实例上下文中提取代码 - 可以将其重构回(或从)方法中访问实例状态。

                        如果代码被多个类使用,并且不包含对该方法所在类中实例变量的访问权限,则将其设为静态。

                        故事结束。

                        【讨论】:

                          猜你喜欢
                          • 2021-04-05
                          • 2021-06-02
                          • 2011-08-18
                          • 1970-01-01
                          • 2012-06-17
                          • 1970-01-01
                          • 1970-01-01
                          • 2012-09-04
                          • 2010-10-07
                          相关资源
                          最近更新 更多