97

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

静态警告

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

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

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

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

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

这会带来任何性能优势吗?(在移动域中)

指出一个静态方法,我想表明你不使用任何实例变量,因此可以移动到 utils 样式类?

归根结底,我应该将其关闭“忽略”,还是应该修复它给我的 100 多个警告?

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

4

9 回答 9

140

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

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

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

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

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

不使用static关键字的示例:

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

这里没有优化的概念。

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

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

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

于 2012-06-28T07:51:52.730 回答
7

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

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

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

于 2012-06-28T07:53:56.953 回答
3

、声明方式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 编程,我个人会确保启用它。

于 2012-07-04T05:21:42.837 回答
1

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

您还询问了性能:

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

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

于 2012-06-28T07:56:31.023 回答
1

来自 Android 性能指南:

首选静态而不是虚拟 如果您不需要访问对象的字段,请将您的方法设为静态。调用将快 15%-20% 左右。这也是一种很好的做法,因为您可以从方法签名中看出调用该方法不能改变对象的状态。

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

于 2013-07-17T15:12:47.177 回答
0

我错过了一些速度差异的数字。因此,我尝试对它们进行基准测试,结果并不那么容易:Java 循环在某些运行/JIT 错误后变慢?

我最终使用了 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

卡尺测试类是:

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

}
于 2014-07-22T16:07:05.203 回答
0

好吧,Eclipse 文档说明了有问题的警告:

方法可以是静态的

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

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

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

于 2012-07-03T19:53:52.603 回答
-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));
    }
}

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

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

于 2012-06-28T07:52:43.813 回答