在Java中,类的静态方法有什么缺点吗?

时间:2010-03-18 19:17:30

标签: java runtime static-methods

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

(编辑进一步澄清)

我知道这个问题有点开放和模糊,所以我为此道歉。我的提问意图主要是“辅助”方法。实用程序类(具有私有CTOR,因此无法实例化)作为我们已经执行的静态方法的持有者。我在这里的问题更像是帮助主类API的这些小方法。

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

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

14 个答案:

答案 0 :(得分:27)

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

答案 1 :(得分:22)

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

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

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

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

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

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

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

  5. 希望有所帮助!

答案 2 :(得分:17)

性能优势可能微不足道。对任何不依赖于状态的东西使用静态方法。这澄清了代码,因为您可以立即通过静态方法调用看到没有涉及实例状态。

答案 3 :(得分:6)

Disadvantage -> Static

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

答案 4 :(得分:5)

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

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

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

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

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

答案 5 :(得分:3)

这完全是一个背景问题。有些人已经给出了静态绝对可取的示例,例如在编写没有可想象状态的实用函数时。例如,如果您正在编写要在数组上使用的不同排序算法的集合,那么将您的方法设置为静态只会使情况混乱。任何阅读代码的程序员都必须询问,为什么不让它静止,并且必须查看你是否对该对象做了一些有状态的事情。

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很难重用。然而,下一个问题是这个通用例程应该是计算max,min,mean,variance等,并将其存储在某处。哪里?在一个对象的状态。如果它真的是一次性的,你可以让它静止,但当然,你会发现你想要计算两个不同的东西的平均值,然后如果你可以多次实例化对象,这是非常好的

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 ++中的是用虚拟手动指定的。

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

请注意,如果从另一个方法调用一个方法,JIT通常足够聪明以内联代码,在这种情况下根本就没有调用,这就是为什么做出关于确切保存多少的任何声明是非常危险的。你可以说的是,当编译器必须调用一个函数时,如果它可以调用一个像static或final这样需要较少计算的函数就不会受到伤害。

答案 6 :(得分:3)

您可能遇到的主要问题是,如果需要,您将无法提供新的实施方案。

如果您仍有疑问(您的实施可能会在未来发生变化),您可以随时使用下面的私有实例与实际实现:

 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 
  }

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

答案 7 :(得分:2)

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

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

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

还使用静态作为访问控制机制 - 例如单身。

答案 8 :(得分:1)

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

答案 9 :(得分:1)

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

并添加到您不需要创建任何实例来访问这些方法,从而节省内存和垃圾收集时间。

答案 10 :(得分:1)

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

答案 11 :(得分:0)

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

答案 12 :(得分:0)

一般情况下:

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

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

你应该被允许交换实现,特别是对于模拟和放大。测试。在这方面,Spring依赖注入非常好。刚从Spring和宾果游戏中注入实现,你几乎就是一个“静态”(井,单例)方法......

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

<强>具体地:

当您定位Websphere与Tomcat部署时,如何优雅地处理实现的“静态”?我确信当你的实现在两者之间有所不同时会有一个实例(没有双关语)...并且在这些特定实现之一中依赖静态方法可能是危险的......

答案 13 :(得分:0)

应该没有任何缺点 - 性能甚至可能略有优势(尽管它不可​​测量),因为可以避免动态查找。

很高兴将函数标记为函数,而不是让它们看起来像方法 - (和静态“方法”ARE函数,而不是方法 - 实际上是按照定义)。

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

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

我想我所说的是,如果这确实是一个问题,你可能想重新检查你的建模实践 - 静态应该非常罕见,甚至不是问题。