我是一名Java程序员,刚进入企业界。最近我用Groovy和Java开发了一个应用程序。在我编写的所有代码中,都使用了相当多的静态元素。高级技术人员要求我减少使用的静态数据的数量。我在谷歌上搜索过相同的内容,我发现许多程序员都相当反对使用静态变量。

我发现静态变量使用起来更方便。而且我假定它们也是有效的(如果我错了请纠正我),因为如果我必须在一个类中对一个函数进行10,000次调用,我将很高兴使方法静态,并在其上使用直接的class . methodcall(),而不是用10,000个类实例使内存混乱,对吗?

此外,静态减少了对代码其他部分的相互依赖。他们可以作为完美的国家持有者。除此之外,我还发现一些语言(如Smalltalk和Scala)广泛地实现了静态。那么为什么程序员普遍反对静态(尤其是在Java世界中)呢?

PS:如果我对静态数据的假设是错误的,请纠正我。


当前回答

如果你使用“static”关键字而没有使用“final”关键字,这应该是一个信号,提醒你仔细考虑你的设计。即使“final”的存在也不是免费的,因为可变的静态final对象可能同样危险。

我估计在85%的情况下,我看到的是没有“final”的“static”,这是错误的。我经常会发现一些奇怪的方法来掩盖或隐藏这些问题。

请不要创建静态变量。特别是集合。一般来说,集合应该在其包含对象初始化时初始化,并且应该设计为当其包含对象被遗忘时重置或忘记集合。

使用静态方法可能会产生非常细微的错误,这会让工程师痛苦数日。我知道,因为这些虫子都是我创造和猎杀的。

如果你想了解更多细节,请阅读…

为什么不使用静态方法?

静态有很多问题,包括编写和执行测试,以及一些不太明显的细微错误。

依赖于静态对象的代码不容易进行单元测试,而静态对象也不容易被模拟(通常)。

如果使用静态,则不可能为了测试更高级别的组件而交换类的实现。例如,假设一个静态CustomerDAO返回它从数据库加载的Customer对象。现在我有一个类CustomerFilter,它需要访问一些Customer对象。如果CustomerDAO是静态的,那么如果不首先初始化数据库并填充有用的信息,就无法为CustomerFilter编写测试。

数据库填充和初始化需要很长时间。根据我的经验,您的DB初始化框架会随着时间的推移而改变,这意味着数据会发生变化,测试可能会中断。IE,假设客户1曾经是一个VIP,但是DB初始化框架改变了,现在客户1不再是VIP,但是你的测试被硬编码来加载客户1…

更好的方法是实例化CustomerDAO,并在构造CustomerFilter时将其传递给CustomerFilter。(更好的方法是使用Spring或另一个反转控制框架。

一旦你这样做了,你就可以在CustomerFilterTest中快速模拟或剔除一个备用DAO,允许你对测试有更多的控制,

如果没有静态DAO,测试将更快(没有db初始化)并且更可靠(因为当db初始化代码更改时它不会失败)。例如,在本例中,就测试而言,确保客户1始终是VIP。

执行测试

Statics cause a real problem when running suites of unit tests together (for example, with your Continuous Integration server). Imagine a static map of network Socket objects that remains open from one test to another. The first test might open a Socket on port 8080, but you forgot to clear out the Map when the test gets torn down. Now when a second test launches, it is likely to crash when it tries to create a new Socket for port 8080, since the port is still occupied. Imagine also that Socket references in your static Collection are not removed, and (with the exception of WeakHashMap) are never eligible to be garbage collected, causing a memory leak.

这是一个过于一般化的例子,但是在大型系统中,这个问题总是会发生。人们不会认为单元测试在同一个JVM中重复地启动和停止他们的软件,但它是对软件设计的一个很好的测试,如果您希望实现高可用性,那么您需要注意这一点。

这些问题经常出现在框架对象中,例如,您的DB访问、缓存、消息传递和日志记录层。如果您正在使用Java EE或一些最好的框架,它们可能会为您管理很多这方面的工作,但如果您像我一样正在处理遗留系统,则可能会有许多自定义框架来访问这些层。

如果应用于这些框架组件的系统配置在单元测试之间发生了变化,并且单元测试框架没有拆除并重新构建组件,那么这些变化就不能生效,并且当测试依赖于这些变化时,它们就会失败。

甚至非框架组件也会遇到这个问题。想象一个名为OpenOrders的静态映射。您编写了一个测试,该测试创建了几个开放订单,并检查以确保它们都处于正确的状态,然后测试结束。另一个开发人员编写第二个测试,将所需的订单放入OpenOrders映射中,然后断言订单数量是准确的。单独运行时,这些测试都将通过,但在一个套件中一起运行时,它们将失败。

更糟糕的是,失败可能基于运行测试的顺序。

在这种情况下,通过避免静态,您可以避免跨测试实例持久化数据的风险,从而确保更好的测试可靠性。

细微的错误

如果您工作在高可用性环境中,或者线程可能启动和停止的任何地方,那么当您的代码在生产环境中运行时,上面提到的与单元测试套件相同的问题也可以适用。

在处理线程时,与其使用静态对象来存储数据,不如使用在线程启动阶段初始化的对象。这样,每次线程启动时,都会创建对象的一个新实例(可能具有新的配置),并且可以避免线程的一个实例的数据泄漏到下一个实例。

When a thread dies, a static object doesn’t get reset or garbage collected. Imagine you have a thread called “EmailCustomers”, and when it starts it populates a static String collection with a list of email addresses, then begins emailing each of the addresses. Lets say the thread is interrupted or canceled somehow, so your high availability framework restarts the thread. Then when the thread starts up, it reloads the list of customers. But because the collection is static, it might retain the list of email addresses from the previous collection. Now some customers might get duplicate emails.

题外话:静态决赛

“static final”的使用实际上相当于Java中的c#定义,尽管在技术实现上存在差异。C/ c++ #定义在编译前被预处理程序从代码中交换出来。Java的“静态final”将最终驻留在JVM的类内存中,使其(通常)永久存在于ram中。这样,它更类似于c++中的“静态const”变量,而不是#define变量。

总结

我希望这有助于解释为什么静态统计是有问题的几个基本原因。如果您正在使用Java EE或Spring等现代Java框架,您可能不会遇到很多这样的情况,但如果您正在处理大量遗留代码,它们可能会变得更加频繁。

其他回答

以上所有答案都说明了为什么静态方法很糟糕。它们是邪恶的原因是因为它给人一种错误的印象,即你在编写面向对象的代码,而事实上你并不是。 这简直是邪恶。

Its not very object oriented: One reason statics might be considered "evil" by some people is they are contrary the object-oriented paradigm. In particular, it violates the principle that data is encapsulated in objects (that can be extended, information hiding, etc). Statics, in the way you are describing using them, are essentially to use them as a global variable to avoid dealing with issues like scope. However, global variables is one of the defining characteristics of procedural or imperative programming paradigm, not a characteristic of "good" object oriented code. This is not to say the procedural paradigm is bad, but I get the impression your supervisor expects you to be writing "good object oriented code" and you're really wanting to write "good procedural code".

在Java中,当您开始使用静态时,有许多并不总是立即明显的陷阱。例如,如果在同一个VM中运行两个程序副本,它们会共享静态变量的值并混淆彼此的状态吗?或者当你扩展类时会发生什么,你能重写静态成员吗?您的虚拟机内存不足,因为您有疯狂的静态数据,并且内存不能为其他需要的实例对象回收?

Object Lifetime: Additionally, statics have a lifetime that matches the entire runtime of the program. This means, even once you're done using your class, the memory from all those static variables cannot be garbage collected. If, for example, instead, you made your variables non-static, and in your main() function you made a single instance of your class, and then asked your class to execute a particular function 10,000 times, once those 10,000 calls were done, and you delete your references to the single instance, all your static variables could be garbage collected and reused.

防止某些重复使用: 此外,静态方法不能用于实现接口,因此静态方法会阻止某些面向对象的特性的可用性。

Other Options: If efficiency is your primary concern, there might be other better ways to solve the speed problem than considering only the advantage of invocation being usually faster than creation. Consider whether the transient or volatile modifiers are needed anywhere. To preserve the ability to be inlined, a method could be marked as final instead of static. Method parameters and other variables can be marked final to permit certain compiler optimiazations based on assumptions about what can change those variables. An instance object could be reused multiple times rather than creating a new instance each time. There may be compliler optimization switches that should be turned on for the app in general. Perhaps, the design should be set up so that the 10,000 runs can be multi-threaded and take advantage of multi-processor cores. If portablity isn't a concern, maybe a native method would get you better speed than your statics do.

If for some reason you do not want multiple copies of an object, the singleton design pattern, has advantages over static objects, such as thread-safety (presuming your singleton is coded well), permitting lazy-initialization, guaranteeing the object has been properly initialized when it is used, sub-classing, advantages in testing and refactoring your code, not to mention, if at some point you change your mind about only wanting one instance of an object it is MUCH easier to remove the code to prevent duplicate instances than it is to refactor all your static variable code to use instance variables. I've had to do that before, its not fun, and you end up having to edit a lot more classes, which increases your risk of introducing new bugs...so much better to set things up "right" the first time, even if it seems like it has its disadvantages. For me, the re-work required should you decide down the road you need multiple copies of something is probably one of most compelling reasons to use statics as infrequently as possible. And thus I would also disagree with your statement that statics reduce inter-dependencies, I think you will end up with code that is more coupled if you have lots of statics that can be directly accessed, rather than an object that "knows how to do something" on itself.

在我看来,你是在问静态变量,但你也在你的例子中指出了静态方法。

静态变量并不是邪恶的——它们被采用为全局变量,就像大多数情况下与最终修饰符结合的常量一样,但正如它所说的,不要过度使用它们。

静态方法又名实用方法。使用它们通常不是一个坏的做法,但主要的问题是它们可能阻碍测试。

作为一个伟大的java项目的例子,使用了大量的静态和做它的正确方式,请看看Play!框架。在SO中也有关于它的讨论。

与静态导入相结合的静态变量/方法也被广泛应用于方便java中声明性编程的库中,如:make it easy或Hamcrest。如果没有大量的静态变量和方法,这是不可能的。

所以静态变量(和方法)很好,但要明智地使用它们!

a)关于程序的原因。

如果你有一个小型到中型的程序,其中静态变量Global。Foo被访问,对它的调用通常不知道从哪里来——没有路径,因此也没有时间轴,变量是如何到达这个地方的,它在哪里被使用。现在我怎么知道是谁设置了它的实际值呢?如果我现在修改它,我怎么知道会发生什么呢?我让grep覆盖整个源代码,收集所有访问,知道发生了什么。

如果你知道如何使用它,因为你只是写了代码,问题是看不见的,但如果你试着理解外国代码,你就会明白。

b)你真的只需要一个吗?

静态变量通常会阻止相同类型的多个程序在相同JVM中运行,但它们的值不同。您通常无法预见在哪些情况下您的程序的多个实例是有用的,但是如果它发展了,或者如果它对其他人有用,他们可能会遇到这样的情况,他们想要启动您的程序的多个实例。

只有那些在较长时间内不会被很多人密集使用的或多或少无用的代码才可能适合使用静态变量。

我的美元。这些答案中有几个混淆了这个问题,而不是说“静态是坏的”,我认为更好的是谈论范围和实例。

我想说的是,静态变量是一个“类”变量——它表示一个值,该值在该类的所有实例中共享。通常情况下,它也应该以这种方式确定作用域(对类及其实例进行保护或私有)。

如果您计划在它周围放置类级行为,并将其暴露给其他代码,那么单例可能是未来支持更改的更好解决方案(正如@Jessica所建议的那样)。这是因为您可以在实例/单例级别使用无法在类级别使用的接口——特别是继承。

关于为什么我认为其他答案中的某些方面不是问题的核心……

静态数据不是“全局的”。在Java中,作用域与静态/实例是分开控制的。

并发性对于静态方法的危险并不比实例方法小。它仍然是需要保护的州。当然,你可能有1000个实例,每个实例变量只有一个静态变量,但如果访问它们的代码不是以线程安全的方式编写的,你仍然会被搞砸——只是你可能需要更长的时间才能意识到这一点。

管理生命周期是一个有趣的论点,但我认为它不那么重要。我不明白为什么管理一对类方法(如init()/clear())比创建和销毁一个单例实例更难。事实上,有些人可能会说,由于GC的存在,单例更复杂一些。

PS,就Smalltalk而言,它的许多方言确实有类变量,但在Smalltalk中,类实际上是元类的实例,所以它们实际上是元类实例上的变量。尽管如此,我还是会运用同样的经验法则。如果它们被用于跨实例的共享状态,那么ok。如果它们支持公共功能,你应该考虑单例。唉,我真的很想念Smalltalk....