将方法声明为静态有啥好处

Posted

技术标签:

【中文标题】将方法声明为静态有啥好处【英文标题】:What is the gain from declaring a method as static将方法声明为静态有什么好处 【发布时间】:2012-06-29 17:01:56 【问题描述】:

我最近一直在查看我在 Eclipse 中的警告并遇到了这个:

如果方法可以声明为静态,它会给出编译器警告。

[编辑] Eclipse 帮助中的准确引用,强调私有和最终:

启用后,编译器将发出错误或警告 privatefinal 且仅引用静态的方法 成员。

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

为什么将所有可能的方法都声明为静态方法是件好事?

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

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

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

你认为这只是代码的额外关键字,因为编译器无论如何都会内联这些方法? (有点像你没有声明每个变量都可以最终但你可以)。

【问题讨论】:

不确定,但这可以简单地看作是一种编程辅助。警告只是指示要查看的内容。 我很好奇这些方法执行的功能类型。如果有这么多,可能有些事情做得不太对。 相关:***.com/q/790281 我有一个类似的问题,没有一个 cmets 真正雄辩地说出答案。 Private 使其他类无法访问方法(即使您扩展)。静态允许您创建不会更改(已完成)的方法。因此,如果您想将方法提供给另一个类但不希望它们更改,请将其设为公共静态方法。如果您接受输入作为参数,则不要将其设为静态。 另外,如果一个方法是静态的,你可以使用 methodName();在扩展旧课程的新课程中。您不需要像使用非静态方法那样放置 className.methodName()。 【参考方案1】:

每当你编写一个方法时,你就在一个给定的范围内履行了一个契约。范围越窄,你写错误的机会就越小。

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

此外,它将帮助阅读您的代码的人了解合同的性质。

这就是为什么在实际实现静态合约时声明方法static 被认为是好的原因。

在某些情况下,您的方法仅表示与您的类的实例相关的东西,而且它的实现实际上并没有使用任何非静态字段或实例。在这种情况下,您不会标记方法static

不使用static 关键字的示例:

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

【讨论】:

+1 它是关于尽量减少射中自己的脚的机会,并减少您需要了解多少才能理解一种方法。 另外,你不需要为了调用一个独立的、自包含的函数而获取实例。 所以在其他世界中,任何不使用实例变量的方法都应该声明为静态的?我一直认为只有在需要时才应该是静态的(比如实用程序方法)。 @PetrMensik 如果private 方法可以声明为静态的,那么它几乎总是应该。对于任何其他访问级别,还有其他因素需要考虑,例如动态调度。 @JamesPoulson 我的意思是动态方法分派,在执行object.method() 以选择要调用的方法时发生的事情。【参考方案2】:

这里没有优化的概念。

static 方法是 static,因为您明确声明该方法不依赖任何封闭类的实例,因为它不需要。因此 Eclipse 警告,如文档中所述:

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

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

【讨论】:

【参考方案3】:

我没有关于性能的信息,我想它最多稍微好一点,因为代码不需要根据类型进行动态调度。

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

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

【讨论】:

静态方法测试起来很简单(只要它们是自包含的——尤其是纯函数,它们是静态方法的主要目标)。在这种情况下,OO 概念无能为力。此外,一等函数的概念与静态方法的概念几乎没有关系。如果您需要一个一流的函数来完成现有静态方法的工作,只需几个字符即可实现它。 可测试性评论可能并不直接针对静态方法,但静态方法更难模拟,因此它们使调用静态方法的方法的测试变得复杂。 如果你使用像JMockit、PowerMock或Groovy这样强大的模拟框架,静态方法很容易模拟。 这些是private static 方法,因此您无需模拟它们【参考方案4】:

1. 声明方法static 会带来轻微的性能优势,但更有用的是,它允许在手头没有对象实例的情况下使用它(例如考虑工厂方法或获取单例)。它还用于说明方法性质的文档目的。不应忽略此文档目的,因为它可以立即向代码读者和 API 用户提供有关方法性质的提示,并且还可以作为原始程序员的思考工具 - 明确预期含义有助于您还可以直接思考并生成质量更好的代码(我认为基于我的个人经验,但人们不同)。例如,区分作用于类型的方法和作用于该类型实例的方法是合乎逻辑的,因此需要区分(正如Jon Skeet in his comment to a C# question 所指出的那样)。

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

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

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

因此是 Eclipse 选项的逻辑。

3. 原发帖人还问:“指出一个静态方法,我想这表明你不使用任何实例变量,因此可以移动到 utils 样式类?”这是一个很好的观点。有时这种设计更改会通过警告来指示。

这是一个非常有用的选项,如果我使用 Eclipse 并使用 Java 编程,我会亲自确保启用它。

【讨论】:

【参考方案5】:

请参阅 Samuel 关于方法范围如何变化的回答。 我想,这是使方法静态化的主要方面。

您还询问了性能:

可能会有微小的性能提升,因为调用静态方法 不需要隐含的“this”引用作为参数。

但是,这种性能影响确实很小。因此,一切都与范围有关。

【讨论】:

【参考方案6】:

来自 android 性能指南:

如果您不需要访问对象的 字段,使您的方法静态。调用将约为 15%-20% 快点。这也是一个很好的做法,因为你可以从方法中看出 调用方法不能改变对象状态的签名。

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

【讨论】:

“调用方法不能改变对象的状态”——令人难以置信的误导。我不了解你,但我当然认为类的静态属性是对象状态的一部分。【参考方案7】:

好吧,Eclipse 文档中提到了有问题的警告:

方法可以是静态的

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

我认为它几乎说明了一切。如果方法是私有的和最终的,并且只引用静态成员,那么相关方法也可以声明为静态的,这样就表明我们只打算从中访问静态内容。

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

【讨论】:

【参考方案8】:

我错过了一些速度差异的数字。所以我尝试对它们进行基准测试,结果并不那么容易:Java loop gets slower after some runs / JIT's fault?

我终于用 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.options.G1ConcMarkStepDurationMillis

我自己的结果是:

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

Caliper Test 类是:

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;
    


我自己的测试类是:

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 设备和版本上查看结果会很有趣 是的。我以为你会感兴趣 :) 但是由于你正在构建 android 软件并且可能有一个 android 设备连接到你的开发站,我建议你选择代码,运行它并分享结果?【参考方案9】:

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

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

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

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

【讨论】:

那是什么语言?该示例是否编译?这里没有什么是一成不变的。 确实,我似乎忘记了 InvertText 方法中的“静态”。这是一个基于c#的例子

以上是关于将方法声明为静态有啥好处的主要内容,如果未能解决你的问题,请参考以下文章

java中啥时候该用static修饰方法?有啥好处或者坏处?

java中的静态方法有啥作用?

设置静态IP有啥好处?

请教大家关于JAVA中的静态变量和静态方法

Java语言中四种内部类(成员内部类,局部内部类,静态内部类,匿名内部类)在实际使用中有啥好处?

将 ObjectMapper 声明为 bean 有啥好处?