【问题标题】:In Java, is there any disadvantage to static methods on a class?在Java中,类上的静态方法有什么缺点吗?
【发布时间】:2011-01-29 04:25:27
【问题描述】:

让我们假设在我的编码环境中已经强加了一条规则(或经验法则),即类上不使用、修改或以其他方式需要任何实例变量来完成其工作的任何方法,都是做成静态的。这样做是否有任何固有的编译时间、运行时或任何其他缺点?

(为进一步澄清而编辑)

我知道这个问题有点开放且含糊不清,因此我对此表示歉意。我询问的意图主要是在“帮助”方法的背景下。实用程序类(具有私有 CTOR,因此它们不能被实例化)作为我们已经使用的静态方法的持有者。我在这里的问题更多的是这些帮助主类 API 的小方法。

我可能在一个类上有 4 或 5 个主要的 API/实例方法来完成实际工作,但在这样做的过程中,它们共享一些可能只对 API 方法的输入参数起作用的通用功能,并且不是内部状态。这些是我通常提取到它们自己的辅助方法中的代码部分,如果它们不需要访问类的状态,请将它们设为静态。

因此,我的问题是,这本质上是一个坏主意吗?如果是,为什么? (或者为什么不呢?)

【问题讨论】:

  • 这几乎是Java中static方法的定义
  • @fuzzy:不,不是。这是一个方法何时可以 static的定义。
  • 看到目前为止的答案,人们在这个问题上有些分歧。看看正确的答案是否会浮出水面会很有趣,这对 SO 社区来说是一个很好的测试。那,或者乔恩·斯基特会出现,把它赶出公园。

标签: java runtime static-methods


【解决方案1】:

主要缺点是您不能在运行时交换、覆盖或选择方法实现。

【讨论】:

  • +1 即使该方法不需要任何对象状态,它仍然可以依赖于对象类型,但前提是它是 not static
  • +1;有些人说static 不是面向对象的概念(出于您所说的原因)。在Java中,静态方法与成员方法的性能提升几乎为0(查看编译后的字节码——这是将对象引用推送到堆栈的问题——必须是几皮秒左右:)此外,如果你使用使用静态方法进行反射,您仍然需要将null 作为第一个参数传递给invoke()
  • 我真的不知道我是否会将其称为劣势。你会说final 的缺点是不能继承类,还是说这是优点和关键字的全部意义?
【解决方案2】:

在我看来,在 Java 中避免使用静态方法有四个原因。这并不是说静态方法从不适用,只是说通常应该避免使用它们。

  1. 正如其他人所指出的,静态方法不能在单元测试中模拟出来。如果一个类依赖于,比如说,DatabaseUtils.createConnection(),那么如果没有数据库或DatabaseUtils 中的某种“测试”标志,那么该依赖类以及任何依赖于它的类几乎是不可能测试的。在后一种情况下,听起来您实际上有两个DatabaseConnectionProvider 接口的实现——请参阅下一点。

  2. 如果你有一个静态方法,它的行为适用于所有类,无处不在。有条件地改变其行为的唯一方法是将标志作为参数传递给方法或在某处设置静态标志。第一种方法的问题在于它会更改每个调用者的签名,并且随着添加越来越多的标志很快变得很麻烦。第二种方法的问题是你最终会得到这样的代码:

    boolean oldFlag = MyUtils.getFlag(); MyUtils.someMethod(); MyUtils.setFlag(oldFlag);

    遇到此问题的常见库的一个示例是 Apache Commons Lang:请参阅 StringUtilsBean 等。

  3. 每个 ClassLoader 加载一次对象,这意味着您实际上可能在不知不觉中拥有静态方法和静态变量的多个副本,这可能会导致问题。这通常与实例方法无关紧要,因为对象是短暂的。

  4. 如果您有引用静态变量的静态方法,那么这些方法会在类加载器的生命周期内一直存在,并且永远不会被垃圾回收。如果这些累积信息(例如缓存)并且您不小心,您可能会在应用程序中遇到“内存泄漏”。如果您改用实例方法,则对象往往寿命较短,因此会在一段时间后被垃圾收集。当然,实例方法也可能导致内存泄漏!但这不是什么大问题。

希望有帮助!

【讨论】:

  • 第 4 点是我听过的不使用静态方法的最佳理由......尽管我仍然不相信 :-)
  • 带有PowerMockito 的JIC 可以模拟static method
【解决方案3】:

性能优势可能可以忽略不计。对不依赖于状态的任何事物使用静态方法。这澄清了代码,您可以通过静态方法调用立即看到不涉及实例状态。

【讨论】:

  • 通常它会使单元测试变得更加困难,尤其是没有以可以进行单元测试的方式编写的遗留代码。
  • @Yishai 你能澄清一下吗?如果方法是静态的,则其输出仅由其输入决定,而不是任何外部状态。该约束如何使单元测试变得更加困难?
  • @Callahad,不能模拟或抽象静态方法。因此,如果该静态方法调用 Web 服务、访问数据库、启动线程或执行任何不可单元测试的类似操作,那么模拟、抽象或以其他方式将方法重定向到其他被测对象变得更加困难。跨度>
  • @Callahad 曾经尝试过模拟单身人士吗?静态方法不能轻易被虚拟实现替换(尽管可以使用高级模拟框架)
  • 一晒,调用web服务或者数据库不是状态无关的。在 DI 框架中,这些是注入到单例中的依赖项,并且是单例状态的一部分。我不认为这些可以用静力学正确建模。
【解决方案4】:

Disadvantage -> Static

成员是类的一部分,因此在应用程序终止之前一直保留在内存中。并且永远不会被垃圾收集。使用过多的静态成员有时会预测您无法设计产品并试图应对静态/过程编程。它表示面向对象的设计受到损害。这可能导致内存溢出。

【讨论】:

    【解决方案5】:

    我真的很喜欢这个问题,因为这是我在过去 4 年的职业生涯中一直在争论的一个问题。静态方法对于不携带任何状态的类很有意义。但最近我的想法有所修改。

    具有静态方法的实用程序类是个好主意。

    承载业务逻辑的服务类在很多情况下可以是无状态的。最初我总是在其中添加静态方法,但是当我更熟悉 Spring 框架(以及一些更一般的阅读)时,我意识到这些方法作为一个独立的单元变得不可测试,因为你不能轻易地将模拟服务注入到这个类中。例如。静态方法调用另一个类中的另一个静态方法,JUnit 测试无法通过在运行时注入虚拟实现来短路路径。

    所以我有点决定,拥有几乎不需要调用其他类或方法的实用静态方法可以是静态的。但是服务类通常应该是非静态的。这允许您利用 OOP 功能,例如覆盖。

    还有一个单例实例类可以帮助我们使一个类非常像一个静态类,仍然使用 OOPs 概念。

    【讨论】:

    • 由于只应对旨在被覆盖的方法进行覆盖,因此我不认为这是使所有可能的方法都成为非静态方法的驱动力。就个人而言,差异可以忽略不计,尤其是对于私有代码,因此更好的理由是减少输入。 :)
    【解决方案6】:

    这都是上下文的问题。有些人已经给出了静态绝对可取的示例,例如在编写没有可想象状态的实用函数时。例如,如果您正在编写用于数组的不同排序算法的集合,那么使您的方法不是静态的只会混淆情况。任何阅读您的代码的程序员都会问,您为什么不将其设为静态,并且必须查看您是否正在对对象执行有状态的操作。

    public class Sorting {
      public static void quiksort(int [] array) {}
      public static void heapsort(int[] array) { }
    }
    

    话虽如此,有很多人编写某种代码,并坚持他们有一些特殊的一次性代码,但后来发现事实并非如此。例如,您要计算变量的统计数据。所以你写:

    public class Stats {
      public static void printStats(float[] data) { }
    }
    

    这里糟糕设计的第一个元素是程序员打算只打印结果,而不是一般地使用它们。在计算中嵌入 I/O 不利于重用。然而,下一个问题是这个通用例程应该计算最大值、最小值、平均值、方差等并将其存储在某个地方。在哪里?处于对象状态。如果它真的是一次性的,你可以让它成为静态的,但当然,你会发现你想要计算两个不同事物的平均值,然后如果你可以多次实例化对象,那就太好了.

    public class Stats {
      private double min,max,mean,var;
      public void compute(float data[]) { ... }
      public double getMin() { return min; }
      public double
    }
    

    对静态的下意识反应通常是程序员对静态地做这类事情的愚蠢的反应,因为说永远不要这样做比实际解释哪些情况是好的,哪些是愚蠢的更容易。

    请注意,在这种情况下,我实际上将对象用作一种特殊用途的按引用传递,因为 Java 在这方面非常令人讨厌。在 C++ 中,这种东西可能是一个函数,任何状态都作为引用传递。但即使在 C++ 中,同样的规则也适用,只是 Java 迫使我们更多地使用对象,因为缺少引用传递。

    就性能而言,从常规方法切换最大的性能提升实际上是避免了动态多态检查,这在 java 中是默认的,而在 C++ 中是通过 virtual 手动指定的。

    当我最后一次尝试时,调用 final 方法比调用常规方法具有 3:1 的优势,但调用静态函数比调用 final 没有明显优势。

    请注意,如果您从另一个方法调用一个方法,JIT 通常足够聪明,可以内联代码,在这种情况下根本没有调用,这就是为什么做出任何关于您节省了多少的声明是极其危险的。您只能说,当编译器必须调用一个函数时,如果它可以调用像 static 或 final 这样需要较少计算的函数,那也没什么坏处。

    【讨论】:

    • 您暗示了人们经常(恕我直言)错过的一些事情:编码结构不允许某些事情有效地将其记录为不做的事实。在两个数组上使用静态方法MySorter.Sort() 清楚地表明每个排序操作都是独立的。如果代码是MySorter sorter = new MySorter(); sorter.sort(array1...); sorter.sort(array2...);,那么所讨论的操作是否是独立的就很不清楚了。人们可能期望它们是,但如果例如排序方法可能会在包含相似数据的列表上重复使用,它可能...
    • ...让它缓存有关它排序的数据的信息会很有帮助。大多数实现当然不会,但是检查使用sorter 实例的代码的人必须确保排序操作确实是独立的。使用静态方法会使它们的独立性更加明显。
    【解决方案7】:

    您可能面临的主要问题是,如果需要,您将无法提供新的实现。

    如果您仍然有疑问(您的实现是否会在未来发生变化),您始终可以在实际实现中使用私有实例:

     class StringUtil {
         private static StringUtil impl = new DefaultStringUtil();
    
         public static String nullOrValue( String s ) {
              return impl.doNullOrValue();
         }
         ... rest omitted 
      }
    

    如果出于“某些” 的原因,您需要更改您可能提供的实现类:

      class StringUtil {
         private static StringUtil impl = new ExoticStringUtil();
    
         public static String nullOrValue( String s ) {
              return impl.doNullOrValue(s);
         }
         ... rest omitted 
      }
    

    但在某些情况下可能会过度。

    【讨论】:

      【解决方案8】:

      不,实际上该建议的原因是它提供了性能优势。静态方法可以以更少的开销调用,因此任何不需要引用 this 的方法都应该设为静态。

      【讨论】:

      • 在 Android 中使用 static 可以提高 15~20% 的速度,而不是使用 new 关键字。
      【解决方案9】:

      为了调用静态方法,您不需要创建类对象。该方法立即可用。

      假设类已经加载。否则有点等待。 :-)

      我认为静态是一种将功能代码与过程/状态设置代码分开的好方法。功能代码通常不需要扩展,只有在出现错误时才更改。

      还有使用静态作为访问控制机制——比如单例。

      【讨论】:

        【解决方案10】:

        不,没有缺点,而是当您不访问方法中的任何实例成员时,将其作为实例方法没有任何意义。将其作为静态方法使用是一种很好的编程技巧。

        此外,您不必创建任何实例来访问这些方法,从而节省内存和垃圾收集时间。

        【讨论】:

          【解决方案11】:

          一个缺点是,如果您的静态方法是通用的并且就使用而言分布在不同的类中。您可能会考虑将所有通用的静态方法放在实用程序类中。

          【讨论】:

          • 不一定是好建议。一个方法不应该因为是静态的而被从一个类中拉出来。只有当您有不适合您的类的方法时,您才会考虑将它们拉入“实用程序”类。例如,如果您打算扩充语言的 String 类或提供额外的 Collection 方法。您想要在它们所属的类中使用静态方法。
          【解决方案12】:

          不应该有任何缺点——因为可以避免动态查找,所以在性能上甚至可能有一点点优势(尽管它无法衡量)。

          最好将函数标记为函数,而不是让它们看起来像方法——(静态“方法”是函数,而不是方法——这实际上是根据定义)。

          一般来说,静态方法是一种糟糕的 OO 代码气味——这可能意味着您的 OO 模型没有完全集成。这种情况总是发生在那些不知道将要使用它的代码的库中,但是在集成的非库代码中,应该检查静态方法以评估它与哪些参数最密切相关——有一个很好的它应该是该类的成员。

          如果静态方法只接受原生值,那么您可能会遗漏一些类;您还应该尽可能少地传递原生变量或库对象(如集合),而不是将它们包含在具有业务逻辑的类中。

          我想我的意思是,如果这确实是一个问题,您可能需要重新检查您的建模实践 - 静态应该如此罕见以至于这甚至不是问题。

          【讨论】:

            【解决方案13】:

            正如其他人所说,它提供了轻微的性能优势并且是良好的编程习惯。唯一的例外是当该方法需要成为实例方法以用于覆盖目的时,但这些通常很容易识别。例如,如果一个类提供了实例方法的默认行为,恰好不需要实例变量,那么显然不能将其设为静态。

            【讨论】:

            • 正如其他人所说,静态方法没有性能优势(至少在 Java 中)。
            【解决方案14】:

            一般:

            您应该编写软件以利用接口而不是实现。谁说“现在”你不会使用一些实例变量,但将来你会?接口编码示例...

            ArrayList badList = new ArrayList();  //bad
            List goodList = new ArrayList();  //good
            

            您应该被允许交换实现,尤其是对于模拟和测试。在这方面,Spring 依赖注入非常好。只需从 Spring 和 bingo 中注入实现,您就有了一个“静态”(好吧,单例)方法......

            现在,那些纯粹是“实用程序”的 API 类型(即 Apache Commons Lang)是例外,因为我相信大多数(如果不是全部)实现都是静态的。在这种情况下,您希望将 Apache Commons 换成另一个 API 的可能性有多大?

            具体来说:

            当您的目标是 Websphere 与 Tomcat 部署时,您将如何优雅地处理实现的“静态性”?我敢肯定会有一个实例(没有双关语)说明您的实现在两者之间会有所不同......并且在其中一个特定实现中依赖静态方法可能是危险的......

            【讨论】:

            • 这样做是有效率的。您可能只想发布通用接口,但对于内部代码,硬编码更快,并且只需编辑对类型的引用即可。您发布接口的原因是您无法修复代码外部的所有引用。
            • 我没有看到这与问题有什么关系?此外,在私有代码中使用具体类型也没有错。只有在公共 API 中才是好的编码习惯。
            • @NateS 我相信我的回答与问题完全相关。我们有一个必须从 Websphere 移植到 Tomcat 的框架 API。 “私有代码”中充斥着对 Websphere 特定“东西”的引用。如果这段代码只是使用一些作为 Websphere 实现代理的接口,生活会容易得多。
            • 接口不是代理,它们是定义实现必须遵守的契约的类型。接口不能代表任何人“做”任何事情。因此它无法定义代理。
            • @Dov 从不为效率而编码,总是为可维护性而编码。如果您发现您的代码不符合定义的规范,请开始尝试分析并在必要时进行更改。任何人发现自己在可维护性之前担心效率,都应该认识到这是一个问题的迹象。
            猜你喜欢
            • 1970-01-01
            • 2010-11-22
            • 1970-01-01
            • 2011-10-23
            • 2011-07-11
            • 1970-01-01
            • 2011-01-27
            • 2010-11-13
            • 1970-01-01
            相关资源
            最近更新 更多