如果您使用的是'static'关键字,而没有'final'关键字,则这应该是仔细考虑您的设计的信号。甚至“最终”的存在也不是免费通行证,因为可变的静态最终对象可能同样危险。
我估计大约有85%的时间会看到“静态”而没有“最终”,这是错误的。通常,我会发现奇怪的解决方法来掩盖或隐藏这些问题。
请不要创建静态可变项。特别是收藏。通常,应该在初始化其包含的对象时初始化集合,并且应该对其进行设计,以使它们在忘记包含的对象时被重置或忘记。
使用静电会产生非常细微的错误,这会给工程师带来持续的痛苦。我知道,因为我已经创建并发现了这些错误。
如果您想了解更多详细信息,请继续阅读...
为什么不使用静态?
静态问题很多,包括编写和执行测试,以及一些不明显的错误。
依赖静态对象的代码无法轻松进行单元测试,并且静态对象也无法轻松模拟(通常)。
如果使用静态,则不能为了测试更高级别的组件而交换类的实现。例如,假设有一个静态的CustomerDAO,它返回从数据库加载的Customer对象。现在,我有一个CustomerFilter类,该类需要访问一些Customer对象。如果CustomerDAO是静态的,则必须先初始化数据库并填充有用的信息,然后才能为CustomerFilter编写测试。
而且数据库填充和初始化需要很长时间。以我的经验,您的数据库初始化框架会随着时间的推移而变化,这意味着数据会变形,测试可能会中断。IE,假设客户1曾经是VIP,但是数据库初始化框架发生了变化,现在客户1不再是VIP,但是您的测试已经过硬编码以加载客户1…
更好的方法是实例化一个CustomerDAO,并在构造它时将其传递给CustomerFilter。(一种更好的方法是使用Spring或其他Inversion of Control框架。
完成此操作后,您可以在CustomerFilterTest中快速模拟或存根备用DAO,从而可以更好地控制测试,
没有静态DAO,测试将更快(无数据库初始化)且更可靠(因为在数据库初始化代码更改时它不会失败)。例如,在这种情况下,就测试而言,确保客户1始终是VIP。
执行测试
当一起运行单元测试套件时(例如,与Continuous Integration服务器一起使用),静态会导致真正的问题。想象一下网络套接字对象的静态映射,该映射在一个测试到另一个测试之间保持打开状态。第一次测试可能会在端口8080上打开一个Socket,但是您忘了在测试拆除时清除Map。现在,当第二个测试启动时,由于该端口仍被占用,因此尝试为端口8080创建新的Socket时很可能会崩溃。还要想象一下,您的静态Collection中的Socket引用没有被删除,并且(WeakHashMap除外)永远都没有资格进行垃圾回收,从而导致内存泄漏。
这是一个过于概括的示例,但是在大型系统中,此问题始终存在。人们不会想到在同一JVM中重复启动和停止其软件的单元测试,但这对您的软件设计是一个很好的测试,如果您渴望实现高可用性,则需要意识到这一点。
这些问题通常是由框架对象引起的,例如,数据库访问,缓存,消息传递和日志记录层。如果您使用的是Java EE或某些最佳框架,那么它们可能会为您管理很多此类事务,但是如果像我一样,您正在使用旧式系统,则可能会有很多自定义框架来访问这些层。
如果应用于这些框架组件的系统配置在单元测试之间发生更改,并且单元测试框架没有拆除并重建组件,则这些更改将不会生效,并且当测试依赖这些更改时,它们将失败。
甚至非框架组件也会遇到此问题。想象一下一个名为OpenOrders的静态映射。您编写一个创建一些未结订单的测试,并检查以确保它们都处于正确的状态,然后测试结束。另一个开发人员编写了第二个测试,该测试将所需的订单放入OpenOrders映射中,然后断言订单数量是准确的。单独运行,这些测试都会通过,但是在套件中一起运行时,它们将失败。
更糟糕的是,失败可能基于测试的运行顺序。
在这种情况下,通过避免静电,可以避免在测试实例之间持久保存数据的风险,从而确保更好的测试可靠性。
细微的错误
如果您在高可用性环境中工作,或者在可能会启动和停止线程的任何地方工作,则当代码在生产环境中运行时,与单元测试套件相同的上述问题也可能适用。
在处理线程时,与其使用静态对象存储数据,不如使用在线程启动阶段初始化的对象。这样,每次启动线程时,都会创建该对象的新实例(具有潜在的新配置),并且可以避免数据从线程的一个实例渗透到下一个实例。
线程死亡时,不会重置静态对象或收集垃圾。假设您有一个名为“ EmailCustomers”的线程,当它启动时,它将使用电子邮件地址列表填充一个静态String集合,然后开始通过电子邮件发送每个地址。可以说该线程被中断或取消,因此您的高可用性框架将重新启动该线程。然后,当线程启动时,它将重新加载客户列表。但是,由于该集合是静态的,因此它可能会保留前一个集合的电子邮件地址列表。现在,一些客户可能会收到重复的电子邮件。
旁白:静态决赛
尽管在技术实现上存在差异,“静态最终”的使用实际上等效于C #define。在编译之前,预处理器会将AC / C ++ #define换出代码。Java“静态最终”将最终驻留在堆栈上的内存。这样,它与C ++中的“静态const”变量相比,与#define更相似。
摘要
我希望这有助于解释静力学出现问题的一些基本原因。如果您使用的是Java EE或Spring等现代Java框架,则可能不会遇到许多这种情况,但是如果使用大量遗留代码,则它们会变得更加频繁。