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

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

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

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


当前回答

总结在Java中使用静态方法的几个基本优点和缺点:

优点:

全局可访问,即不与任何特定的对象实例绑定。 每个JVM一个实例。 可以通过类名访问(不需要对象)。 包含一个适用于所有实例的值。 在JVM启动时加载,并在JVM关闭时死亡。 它们不会修改Object的状态。

缺点:

Static members are always part of memory whether they are in use or not. You can not control creation and destruction of static variable. Usefully they have been created at program loading and destroyed when program unload (or when JVM shuts down). You can make statics thread safe using synchronize but you need some extra efforts. If one thread change value of a static variable that can possibly break functionality of other threads. You must know “static“ before using it. You cannot override static methods. Serialization doesn't work well with them. They don't participate in runtime polymorphism. There is a memory issue (to some extent but not much I guess) if a large number of static variables/methods are used. Because they will not be Garbage Collected until program ends. Static methods are hard to test too.

其他回答

静态变量表示全局状态。这很难推理,也很难测试:如果我创建了一个对象的新实例,我就可以在测试中推理它的新状态。如果我使用使用静态变量的代码,它可以处于任何状态-任何东西都可以修改它。

我还可以继续讲很久,但更重要的概念是,事物的范围越窄,就越容易进行推理。我们擅长思考小事,但如果没有模块化,就很难推断出百万行系统的状态。顺便说一下,这适用于各种各样的东西——不仅仅是静态变量。

不。全球国家本身并不邪恶。但我们必须看看你的代码,看看你是否正确使用了它。一个新手很有可能滥用全局状态;就像他会滥用每一种语言特性一样。

全局状态是绝对必要的。我们不能回避全球国家。我们无法避免对全球状态进行推理。-如果我们关心我们的应用程序语义。

那些为了摆脱全局状态而试图摆脱全局状态的人,最终不可避免地会得到一个更复杂的系统——全局状态仍然存在,巧妙地/愚蠢地伪装在许多层间接之下;在解开所有的间接之后,我们仍然需要推理全局状态。

就像Spring的人在xml中大量声明全局状态,并认为它在某种程度上更优越。

@Jon Skeet如果我创建一个对象的新实例,现在你有两件事要考虑——对象内部的状态,以及承载对象的环境的状态。

我认为过度使用全局变量和静态关键字也会导致应用程序中实例的某些点的内存泄漏

如果你使用“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框架,您可能不会遇到很多这样的情况,但如果您正在处理大量遗留代码,它们可能会变得更加频繁。

一切(可以:)都有它的目的,如果你有一堆线程,需要共享/缓存数据和所有可访问的内存(所以你不分裂成上下文在一个JVM)静态是最好的选择->当然你可以强制只有一个实例,但为什么? 我发现这个帖子里的一些评论是邪恶的,不是静态的;)