将方法声明为静态有什么好处


94

我最近一直在浏览Eclipse中的警告并遇到以下警告:

静态警告

如果该方法可以声明为静态方法,则会向编译器发出警告。

[edit] Eclipse帮助中的确切引号,重点是私有和最终的:

启用后,编译器将为私有最终方法(仅引用静态成员)发出错误或警告。

是的,我知道我可以将其关闭,但是我想知道打开它的原因吗?

为什么将每个方法都声明为静态是一件好事?

这会给性能带来任何好处吗?(在移动域中)

指出一种方法是静态的,我想表明您不使用任何实例变量,因此可以移至utils样式类吗?

在一天结束时,我应该只关闭“忽略”功能还是应该修复它给我的100多个警告?

您是否认为这只是多余的关键字,会使代码变得肮脏,因为编译器还是会内联这些方法?(有点像您没有声明每个变量都可以终止但可以声明)。


不能确定,但​​这可以简单地看作是编程辅助。警告只是要看的事物的指示。
James P.

1
我对这些方法执行的功能种类感到好奇。如果有太多这样的事情,也许做得不好。
ArjunShankar 2012年

Answers:


132

每当编写方法时,您都将在给定范围内履行合同。范围越窄,编写错误的机会就越小。

当方法是静态的时,您不能访问非静态成员。因此,您的范围更窄。因此,如果您不需要并且永远也不需要(甚至在子类中)非静态成员来履行您的合同,那么为什么要将这些字段的访问权限授予您的方法呢?static在这种情况下声明方法将使编译器检查您是否使用了不打算使用的成员。

而且,它将帮助阅读您的代码的人们了解合同的性质。

这就是为什么static在实际上实现静态协定时声明一个方法很好的原因。

在某些情况下,您的方法仅表示相对于类实例的某种含义,并且碰巧其实现实际上并未使用任何非静态字段或实例。在这种情况下,您不会标记方法static

不使用static关键字的示例:

  • 一个扩展钩子,什么都不做(但是可以对子类中的实例数据做一些事情)
  • 一个非常简单的默认行为意味着可以在子类中进行自定义。
  • 事件处理程序的实现:实现会随事件处理程序的类而变化,但不会使用事件处理程序实例的任何属性。

1
+1可以最大程度地减少射击脚的机会,并减少了解某种方法所需的知识。
彼得·劳瑞

7
另外,您无需为调用一个独立的自包含函数而费力地获取实例。
Marko Topolnik

1
因此,在其他世界中,任何不使用实例变量的方法都应声明为静态方法?我一直认为,只有在需要时,方法才应该是静态的(如实用性方法)。
Petr Mensik 2012年

18
@PetrMensik如果private可以将方法声明为静态,则几乎总是应该如此。对于任何其他访问级别,还需要考虑其他因素,例如动态分配。
Marko Topolnik 2012年

1
@JamesPoulson我的意思是动态方法分派,在练习object.method()选择要调用的方法时发生的事情。
Marko Topolnik 2012年

15

这里没有优化的概念。

一个static方法是static,因为你明确声明该方法不依赖于任何情况下,只是因为它并不需要在封闭类。因此,Eclipse警告,如文档中所述:

启用后,编译器将为私有或最终方法(仅引用静态成员)发出错误或警告。

如果您不需要任何实例变量,并且您的方法是私有的(不能从外部调用)或最终的(不能被覆盖),则没有理由让它成为普通方法而不是静态方法。静态方法本质上更安全,即使只是因为您可以使用它做更少的事情(它不需要任何实例,也没有任何隐式this对象)。


7

我没有性能方面的信息,我想它最多只能稍微提高一点,因为代码不需要根据类型进行动态分配。

但是,反对重构为静态方法的一个更有力的论据是,当前使用静态被认为是不好的做法。静态方法/变量不能很好地集成到面向对象的语言中,并且也很难正确测试。这就是为什么某些较新的语言完全放弃静态方法/变量的概念,或尝试以一种与OO更好地结合的方式将其内部化为语言的原因(例如Scala中的Objects)。

大多数时候,您需要静态方法来实现仅将参数用作输入并使用该参数生成输出的函数(例如,实用程序/辅助函数)。在现代语言中,有一类一流的函数概念允许这样做,因此静态不需要。Java 8将集成lambda表达式,因此我们已经朝着这个方向发展。


7
静态方法很容易测试(只要它们是自包含的,尤其是纯函数,它们是静态方法的主要目标)。在这种情况下,OO概念无济于事。同样,一流函数的概念与静态方法的概念几乎没有关系。如果您需要一流的函数来完成现有静态方法的工作,则实现它只需几个字符即可。
Marko Topolnik 2012年

5
可测试性可能不是直接针对静态方法,但是静态方法更难模拟,因此它们使对调用静态方法的方法的测试变得复杂。
2012年

2
如果使用功能强大的模拟框架(例如JMockitPowerMockGroovy),则静态方法很容易模拟。
杰夫·奥尔森

这些private static方法,这样你就不会需要嘲笑他们
布伦德尔

3

1.申报方法static带来一点性能上的好处,但是更有用的是,它允许在没有对象实例的情况下使用它(例如,关于工厂方法或获取单例)。它也用于说明方法本质的文档目的。此文档目的不应被忽略,因为它向代码的读者和API用户提供了有关方法性质的直接提示,并且还充当了原始程序员的思考工具-明确说明预期的含义有助于您也会直截了当,并产生更好的代码(我认为是基于我的个人经验,但是人们有所不同)。例如,逻辑上并因此希望区分对一种类型进行操作的方法和对这种类型的实例进行操作的方法(如乔恩·斯基特(Jon Skeet)对C#问题的评论)。

static方法的另一个用例是模仿过程编程接口。想一想java.lang.System.println()类及其中的方法和属性。该类java.lang.System的用法类似于分组名称空间,而不是可实例化的对象。

2. Eclipse(或任何其他编程的或其他类型的可生物合成或不可生物合成的实体)如何才能确定哪种方法可以声明为静态方法?即使基类没有访问实例变量或调用非静态方法,通过继承机制,事物也可能发生变化。只有通过继承子类不能覆盖该方法时,我们才能100%确定该方法确实可以声明static。在以下两种情况下,完全不可能覆盖方法

  1. private (没有子类可以直接使用它,甚至原则上也不知道它),或者
  2. final (即使子类可以访问,也无法更改引用实例数据或函数的方法)。

因此,Eclipse选项的逻辑。

3.原始发布者还问:“ 我想指出一个方法是静态的,这表明您不使用任何实例变量,因此可以将其移至utils样式类吗? ”这是一个很好的观点。有时,警告会指示这种设计更改。

这是一个非常有用的选项,如果要使用Eclipse和使用Java进行编程,我个人将确保启用该选项。


1

请参阅塞缪尔(Samuel)关于方法范围如何变化的答案。我猜这是使方法静态化的主要方面。

您还询问了性能:

可能会获得很小的性能提升,因为对静态方法的调用不需要隐式的“ this”引用作为参数。

但是,这种性能影响确实很小。因此,这全都涉及范围。


1

根据《 Android性能指南》:

优先使用静态而不是虚拟如果您不需要访问对象的字段,请将您的方法设为静态。调用速度将提高约15%-20%。这也是一个好习惯,因为您可以从方法签名中得知调用该方法不能更改对象的状态。

http://developer.android.com/training/articles/perf-tips.html#PreferStatic


“调用该方法不能改变对象的状态” –令人难以置信的误导。我不了解您,但是我当然认为类的静态属性是对象状态的一部分。
亚当·帕金

0

好吧,Eclipse文档说明了有关警告的信息:

方法可以是静态的

启用后,编译器将为私有或最终方法(仅引用静态成员)发出错误或警告。

我认为这说明了一切。如果该方法是私有且最终的,并且仅引用静态成员,则有问题的方法也可能被声明为静态,因此,这表明我们仅打算从中访问静态内容。

老实说,我认为背后没有其他神秘的原因。


0

由于速度差异,我错过了一些数字。因此,我尝试对它们进行基准测试,结果并非如此简单:在某些运行/ JIT的错误之后,Java循环变慢了?

我最终使用了Caliper,结果与手动运行测试的结果相同:

静态/动态调用没有可测量的差异。至少不适用于Linux / AMD64 / Java7。

卡尺结果在这里:https ://microbenchmarks.appspot.com/runs/1426eac9-36ca-48f0-980f-0106af064e8f#r:scenario.benchmarkSpec.methodName,scenario.vmSpec.options.CMSLargeCoalSurplusPercent,scenario.vmSpec.options 。 CMSLargeSplitSurplusPercent,scenario.vmSpec.options.CMSSmallCoalSurplusPercent,scenario.vmSpec.options.CMSSmallSplitSurplusPercent,scenario.vmSpec.options.FLSLargestBlockCoalesceProximity,scenario.vmSpec.OptionsSteps.G1Conc

我自己的结果是:

Static: 352 ms
Dynamic: 353 ms
Static: 348 ms
Dynamic: 349 ms
Static: 349 ms
Dynamic: 348 ms
Static: 349 ms
Dynamic: 344 ms

卡尺测试课程为:

public class TestPerfomanceOfStaticMethodsCaliper extends Benchmark {

    public static void main( String [] args ){

        CaliperMain.main( TestPerfomanceOfStaticMethodsCaliper.class, args );
    }

    public int timeAddDynamic( long reps ){
        int r=0;
        for( int i = 0; i < reps; i++ ) {
            r |= addDynamic( 1, i );
        }
        return r;
    }

    public int timeAddStatic( long reps ){
        int r=0;
        for( int i = 0; i < reps; i++ ) {
            r |= addStatic( 1, i );
        }
        return r;
    }

    public int addDynamic( int a, int b ){

        return a+b;
    }

    private static int addStatic( int a, int b ){

        return a+b;
    }

}

我自己的Test类是:

public class TestPerformanceOfStaticVsDynamicCalls {

    private static final int RUNS = 1_000_000_000;

    public static void main( String [] args ) throws Exception{

        new TestPerformanceOfStaticVsDynamicCalls().run();
    }

    private void run(){

        int r=0;
        long start, end;

        for( int loop = 0; loop<10; loop++ ){

            // Benchmark

            start = System.currentTimeMillis();
            for( int i = 0; i < RUNS; i++ ) {
                r += addStatic( 1, i );
            }
            end = System.currentTimeMillis();
            System.out.println( "Static: " + ( end - start ) + " ms" );

            start = System.currentTimeMillis();
            for( int i = 0; i < RUNS; i++ ) {
                r += addDynamic( 1, i );
            }
            end = System.currentTimeMillis();
            System.out.println( "Dynamic: " + ( end - start ) + " ms" );

            // Do something with r to keep compiler happy
            System.out.println( r );

        }

    }

    private int addDynamic( int a, int b ){

        return a+b;
    }

    private static int addStatic( int a, int b ){

        return a+b;
    }

}

看到各种Android设备和版本上的结果将会很有趣
Blundell 2014年

是的 我以为您会很感兴趣:)但是,由于您正在构建android软件,并且可能有一个放置在您开发站周围的android设备,我建议您选择代码,运行它并共享结果?
Scheintod 2014年

-2

您可以声明为静态的方法是不需要实例化的方法,例如

public class MyClass
{
    public static string InvertText(string text)
    {
        return text.Invert();
    }
}

然后,您可以在任何其他班级中调用该班级,而无需实例化该班级。

public class MyClassTwo
{
    public void DoSomething()
    {
        var text = "hello world";
        Console.Write(MyClass.InvertText(text));
    }
}

...但这是您可能已经知道的。除了更清楚地表明该方法不使用任何实例变量之外,它本身并没有给您带来任何真正的好处。

换句话说,您可以最安全地将其完全关闭。如果您知道您永远不会在其他类中使用方法(在这种情况下,它应该只是私有的),则根本不需要它是静态的。


1
那是什么语言?该示例可以编译吗?这里没有什么是静止的。
霹雳霹雳州

确实,似乎我从InvertText方法中忘记了“静态”。这是基于c#的示例
NeroS
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.