为什么静态变量被认为是邪恶的?

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

我发现静态变量使用起来更方便,而且我认为它们也很有效(如果我错了请纠正我),因为如果我必须在一个类中对一个函数进行10,000次调用,我很乐意将该方法设为静态,并在其上使用简单的Class.methodCall(),而不是用该类的10,000个实例弄乱内存,对吧?

此外,静态减少了代码其他部分的相互依赖。它们可以充当完美的状态持有者。除此之外,我发现静态在Smalltalkscala等一些语言中被广泛实现。那么为什么这种对静态的反对在程序员中普遍存在(尤其是在Java的世界里)呢?

PS:如果我对静力学的假设是错误的,请纠正我。

287112 次浏览

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

我可以继续讲很长一段时间,但更重要的是,一个事物的作用域越窄,就越容易推理。我们擅长思考小事物,但如果没有模块化,就很难推理百万行系统的状态。顺便说一句,这适用于各种各样的事情——不仅仅是静态变量。

恶是一个主观的名词。

在创建和销毁方面,你不能控制静态数据。它们是在程序加载和卸载的要求下生活的。

由于静态数据位于一个空间中,所有希望使用它们的线程都必须经过你必须管理的权限改造。这意味着程序耦合更多,这种变化更难设想和管理(如J Skeet所说)。这导致隔离变化影响的问题,从而影响测试的管理方式。

这是我与他们的两个主要问题。

静态变量有两个主要问题:

  • 线程安全-静态资源根据定义不是线程安全的
  • 代码隐含-您不知道何时实例化静态变量以及它是否会在另一个静态变量之前实例化

如果我必须对一个类中的函数进行10,000次调用,我会 很高兴使方法静态并使用简单的 class.methodCall(),而不是用10,000弄乱内存 类的实例,对吗?

您必须平衡将数据封装到具有状态的对象中的需求,以及简单计算某些数据上的函数结果的需求。

此外,静态减少了对代码其他部分的相互依赖。

封装也是如此。在大型应用程序中,静态倾向于生成意大利面条代码,并且不容易允许重构或测试。

其他答案也提供了反对过度使用静力学的充分理由。

静态变量最重要的是会产生数据安全性问题(任何时间更改,任何人都可以更改,无对象直接访问等)

有关更多信息,请阅读 谢谢!

在我看来,这几乎与性能无关,而是与设计有关。我不认为使用静态方法与使用静态变量相反是错误的(但我猜你实际上是在谈论方法调用)。

它只是关于如何隔离逻辑并给它一个好的位置。有时这证明使用静态方法是合理的,其中java.lang.Math是一个很好的例子。我认为当你命名大多数类XxxUtilXxxhelper时,你最好重新考虑你的设计。

静态变量通常被认为是不好的,因为它们代表全局状态,因此更难推理。特别是,它们打破了面向对象编程的假设。在面向对象编程中,每个对象都有自己的状态,由实例(非静态)变量表示。静态变量代表实例之间的状态,这可能更难以进行单元测试。这主要是因为将静态变量的更改隔离到单个测试更困难。

话虽如此,区分常规静态变量(通常被认为是坏的)和最终静态变量(AKA常量;不是那么坏)是很重要的。

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

我明白你的想法,但是一个简单的Singleton模式可以做同样的事情,而不必实例化10,000个对象。

可以使用静态方法,但仅适用于与对象域相关且不需要或使用对象内部属性的函数。

例如:

public class WaterContainer {
private int size;
private int brand;
...etc


public static int convertToGallon(int liters)...


public static int convertToLiters(int gallon)...


}

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

全球国家是绝对必要的。我们不能避免全球国家。我们不能避免对全球国家的推理。-如果我们关心理解我们的应用语义学。

为了摆脱全球国家而试图摆脱全球国家的人,不可避免地最终会得到一个更复杂的系统——全球国家仍然存在,巧妙地/愚蠢地伪装在多层间接之下;在解开所有间接之后,我们仍然必须对全球国家进行推理。

就像Spring一样,他们慷慨地用xml声明全局状态,并认为它在某种程度上更优越。

@Jon Skeetif I create a new instance of an object现在你有两件事要考虑——对象内的状态,以及托管对象的环境的状态。

可能有人建议,在大多数情况下,如果您使用静态变量,您真的希望使用单例模式

全局状态的问题在于,有时在更简单的上下文中具有全局意义的东西在实际上下文中需要更加灵活,这就是单例模式变得有用的地方。

上面的所有答案都表明了为什么静态是不好的。它们之所以是邪恶是因为它给人一种错误的印象,即您正在编写面向对象的代码,而实际上您不是。 这简直就是邪恶!

(一)项目原因。

如果你有一个中小型程序,在那里访问静态变量Global.foo,对它的调用通常来自任何地方——没有路径,因此没有时间线,变量是如何到达的地方,它是在哪里使用的。现在我怎么知道谁将它设置为它的实际值?如果我现在修改它,我怎么知道会发生什么?我对整个源代码有grep,收集所有访问,知道发生了什么。

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

你真的只需要一个吗?

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

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

并发。静态变量可能会让你感到惊讶,如果你有多个线程对静态变量进行读写。这在Web应用程序(例如ASP.NET)中很常见,它会导致一些相当令人抓狂的错误。例如,如果你有一个页面更新了一个静态变量,并且两个人在“几乎同时”请求该页面,一个用户可能会得到另一个用户预期的结果,甚至更糟。

静态处理减少了代码其他部分的相互依赖。它们可以充当完美的状态持有者

我希望你准备好使用锁和处理争用。

*实际上,Preet Sangha提到了它。

“静态是邪恶的”问题更多的是一个关于全局状态的问题。变量成为静态的适当时间是,如果它从来没有超过一个状态;应该被整个框架访问并始终为相同的方法调用返回相同结果的IE工具永远不会像静态那样“邪恶”。至于你的评论:

我发现静态变量使用起来更方便。我认为它们也很有效

静态是永远不会改变的变量/类的理想和有效选择

全局状态的问题在于它可能产生的内在不一致性。关于单元测试的文档经常解决这个问题,因为任何时候都有一个全局状态可以被多个不相关的对象访问,你的单元测试将是不完整的,并且不是“单元”粒度的。正如这篇关于全局状态和单例的文章中提到的,如果对象A和B不相关(比如在一个没有明确给出对另一个的引用中),那么A应该不能影响B的状态。

在好的代码中,禁止全局状态有一些例外,例如时钟。时间是全局的,并且——在某种意义上——它改变对象的状态而没有编码关系。

还有一个原因:脆弱性。

如果你有一个类,大多数人希望能够创建它并随意使用它。

您可以记录事实并非如此,或防止它(单例/工厂模式)-但这是额外的工作,因此需要额外的成本。 即便如此,在一家大公司,很有可能有人会在某个时候尝试使用你的类,而不完全关注所有好的评论或工厂。

如果你经常使用静态变量,那就会崩溃。错误是昂贵的。

在0.0001%的性能提升和健壮性之间,由潜在的无知开发人员进行更改,在很多情况下,健壮性是不错的选择。

一切(can:)都有它的目的,如果你有一堆线程需要共享/缓存数据和所有可访问内存(所以你不会在一个JVM中拆分为上下文),静态是最好的选择

->当然你可以只强制一个实例,但是为什么呢?
我发现这个线程中的一些评论是邪恶的,而不是静态的;)

它不是很面向对象: 静态变量可能被一些人认为是“邪恶”的一个原因是它们与面向对象范例相反。特别是,它违反了数据封装在对象中(可以扩展,信息隐藏等)的原则。静态,按照你描述的使用方式,本质上是将它们用作全局变量,以避免处理范围等问题。然而,全局变量是过程或命令式编程范式的定义特征之一,而不是面向对象的“好”代码的特征。这并不是说程序范式不好,但我得到的印象是,你的主管希望你写“好的面向对象代码”,而你真的想写“好的程序代码”。

当你开始使用静态时,Java有许多问题并不总是显而易见的。例如,如果你有两个程序副本在同一个虚拟机中运行,它们会粉碎静态变量的值并弄乱彼此的状态吗?或者当你扩展类时会发生什么,你能覆盖静态成员吗?你的虚拟机运行内存溢出是因为你有疯狂数量的静态,并且内存无法回收给其他需要的实例对象吗?

对象生命周期: 此外,静态变量的生命周期与程序的整个运行时相匹配。这意味着,即使你完成了对类的使用,来自所有这些静态变量的内存也无法被垃圾收集。例如,如果你让你的变量非静态,并且在你的main()函数中你为你的类创建了一个实例,然后让你的类执行一个特定的函数10,000次。一旦这10,000次调用完成,并且你删除了对单个实例的引用,你所有的静态变量都可以被垃圾收集并重用。

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

其他选项: 如果效率是你最关心的问题,也许有其他更好的方法来解决速度问题,而不是只考虑调用通常比创建更快的优势。考虑在任何地方是否需要瞬态修饰符或易失性修饰符。为了保持内联的能力,可以将方法标记为Final而不是静态。方法参数和其他变量可以标记为Final,以允许基于关于什么可以改变这些变量的假设进行某些编译器优化。一个实例对象可以被重复使用多次,而不是每次都创建一个新实例。一般来说,应用程序可能应该打开编译器优化开关。也许,应该设置10,000次运行的设计,以便可以多线程并利用多处理器内核。如果不担心可移植性,也许本机方法会比静态方法获得更好的速度。

如果由于某种原因,你不想要一个对象的多个副本,那么单例设计模式比静态对象更有优势,例如线程安全(假设你的单例编码良好),允许延迟初始化,保证对象在使用时已正确初始化,子类化,测试和重构代码的优势,更不用说,如果在某个时候你改变主意只想要一个对象的实例,那么删除代码以防止重复实例比重构所有静态变量代码以使用实例变量要容易得多。我以前不得不这样做,这并不好玩,你最终不得不编辑更多的类,这增加了引入新错误的风险……第一次就把事情设置得“正确”要好得多,即使它看起来有它的缺点。对我来说,如果你决定以后需要多个副本,需要重新工作,这可能是尽可能少使用静态的最令人信服的理由之一。因此,我也不同意你关于静态减少相互依赖的说法,我认为如果你有很多可以直接访问的静态,而不是一个“知道如何做某事”的对象,你最终会得到更耦合的代码。

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

我想说的是,静态是一个“类”变量——它代表了一个在该类的所有实例之间共享的值。通常它也应该以这种方式限定范围(对类及其实例是受保护的或私有的)。

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

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

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

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

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

PS,就Smalltalk而言,它的许多方言确实有类变量,但在Smalltalk中,类实际上是Metaclass的实例,所以它们实际上是Metaclass实例上的变量。尽管如此,我会应用同样的经验法则。如果它们被用于实例之间的共享状态,那么没问题。如果它们支持公共功能,你应该看看Singleton。唉,我真的很想念Smalltalk……

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

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

静态方法又名实用方法。使用它们通常不是一个糟糕的做法,但主要的担忧是它们可能会阻碍测试。

作为一个使用大量静态数据并以正确方式完成的伟大java项目的示例,请查看Play!框架。SO中也有关于它的讨论

与静态导入相结合的静态变量/方法也广泛用于促进java中声明式编程的库中,例如:使其容易Hamcrest。没有大量的静态变量和方法是不可能的。

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

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

我估计大约85%的时候我看到一个没有最终结果的静态文件,这是错误的。通常,我会找到奇怪的解决方法来掩盖或隐藏这些问题。

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

使用静力学可以创建非常微妙的错误,这将导致持续工程师几天的痛苦。我知道,因为我已经创建并追捕了这些错误。

如果您想了解更多详情,请继续阅读…

为什么不使用静态?

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

依赖静态对象的代码不能轻易地进行单元测试,并且静态不能轻易地模拟(通常)。

如果你使用静态,就不可能为了测试更高级别的组件而交换类的实现。例如,想象一个静态CustmerDAO,它返回它从数据库加载的客户对象。现在我有一个类CustmerFilter,它需要访问一些客户对象。如果CustmerDAO是静态的,我不能在不首先初始化我的数据库并填充有用信息的情况下为CustmerFilter编写测试。

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

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

一旦你这样做了,你可以快速地模拟或存根出一个备用DAO在你的客户FilterTest,让你有更多的控制测试,

没有静态DAO,测试将更快(没有db初始化)和更可靠(因为当db初始化代码更改时它不会失败)。例如,在这种情况下,就测试而言,确保客户1是并且永远是VIP。

执行测试

当一起运行单元测试套件时(例如,与您的持续集成服务器一起),静态会导致一个真正的问题。想象一个网络Socket对象的静态映射,从一个测试到另一个测试保持打开状态。第一个测试可能在端口8080上打开了一个Socket,但是当测试被拆除时,您忘记清除Map。现在,当第二个测试启动时,当它试图为端口8080创建一个新的Socket时,它很可能会崩溃,因为端口仍然被占用。再想象一下,您的静态集合中的Socket引用没有被删除,并且(WeakHashMap除外)永远没有资格被垃圾收集,从而导致内存泄漏。

这是一个过于笼统的例子,但在大型系统中,这个问题一直在发生。人们不会想到在同一个JVM中重复启动和停止他们的软件的单元测试,但这是对您的软件设计的一个很好的测试,如果您对高可用性有愿望,这是您需要注意的事情。

这些问题通常出现在框架对象中,例如,您的数据库访问、缓存、消息传递和日志记录层。如果您使用的是JavaEE或一些同类最佳框架,它们可能会为您管理很多这些,但如果您像我一样处理遗留系统,您可能有很多自定义框架来访问这些层。

如果应用于这些框架组件的系统配置在单元测试之间发生变化,并且单元测试框架没有拆除和重建组件,则这些更改无法生效,并且当测试依赖于这些更改时,它们将失败。

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

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

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

微妙的错误

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

处理线程时,最好使用在线程启动阶段初始化的对象,而不是使用静态对象来存储数据。这样,每次线程启动时,都会创建一个对象的新实例(具有潜在的新配置),你可以避免数据从线程的一个实例流到下一个实例。

当一个线程死亡时,静态对象不会重置或垃圾回收。想象一下,你有一个名为“Email客户”的线程,当它启动时,它用电子邮件地址列表填充一个静态String集合,然后开始通过电子邮件发送每个地址。假设线程以某种方式被中断或取消,所以你的高可用性框架重新启动了线程。然后当线程启动时,它会重新加载客户列表。但是因为集合是静态的,它可能会保留前一个集合中的电子邮件地址列表。现在一些客户可能会收到重复的电子邮件。

别名:静态决赛

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

总结

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

你的文章中有两个主要问题。

首先,关于静态变量。 静态变量是完全不必要的,它的使用很容易避免。在OOP语言中,一般来说,特别是在Java,函数参数是通过引用传递的,也就是说,如果你将一个对象传递给一个函数,你将传递一个指向该对象的指针,所以你不需要定义静态变量,因为你可以将指向该对象的指针传递给任何需要该信息的范围。即使这意味着yo将用指针填充你的内存,这也不一定代表性能不佳,因为实际的内存分页系统已经为此进行了优化,并且它们将在内存中维护你传递给新作用域的指针引用的页面;静态变量的使用可能会导致系统在需要访问它们时加载存储它们的内存页(如果长时间没有访问该页面,这种情况会发生)。一个好的做法是将所有静态存根放在一些小的“配置类”中,这将确保系统将它们都放在同一个内存页中。

第二,关于静态方法。 静态方法并没有那么糟糕,但它们会很快降低性能。例如,考虑一个方法,它比较一个类的两个对象并返回一个值,指示哪个对象更大(典型比较方法)这个方法可以是静态的,也可以不是静态的,但调用它时,非静态形式会更有效,因为它只需要解决两个引用(每个对象一个),而三个引用必须解决同一方法的静态版本(一个用于类加上两个,每个对象一个)。但正如我所说,这并不坏,如果我们看看Math类,我们可以发现很多数学函数定义为静态方法。这真的比把所有这些方法放在定义数字的类中更有效,因为它们中的大多数很少使用,将它们全部包含在数字类中会导致类非常复杂,不必要地消耗大量资源。

结论:在处理静态或非静态方法时,避免使用静态变量并找到正确的性能平衡。

PS:对不起我的英语。

静态变量本身没有问题。只是Java语法被破坏了。每个Java类实际上定义了两个结构——一个封装静态变量的单例对象和一个实例。在同一个源块中定义两者纯粹是邪恶的,并导致代码难以阅读。Scala做得对。

假设您有一个包含许多用户的应用程序,并且您定义了一个静态函数,该函数将状态保存在静态变量中,那么每个用户都会修改其他用户的状态。

这里有很多好的答案,加上它,

内存: 只要类加载器存在,静态变量就会存在[通常直到VM死亡],但这仅适用于存储为静态的批量对象/引用。

模块化: 考虑IOC、依赖注入、代理等概念…所有这些都完全反对紧密耦合/静态实现。

其他缺点:线程安全性,可测试性

我刚才总结了答案中的一些要点。如果你发现任何错误,请随时纠正。

缩放:每个JVM只有一个静态变量的实例。假设我们正在开发一个图书馆管理系统,我们决定将book的名称作为静态变量,因为每本书只有一个。但是如果系统增长并且我们使用多个JVM,那么我们没有办法找出我们正在处理的是哪本书?

线程安全:在多线程环境中使用时,实例变量和静态变量都需要控制。但是对于实例变量,除非它在线程之间显式共享,否则它不需要保护,但是对于静态变量,它总是由进程中的所有线程共享。

测试:虽然可测试的设计不等于好的设计,但我们很少会观察到不可测试的好设计。由于静态变量代表全局状态,测试它们变得非常困难。

关于状态的推理:如果我创建了一个类的新实例,那么我们可以推断这个实例的状态,但是如果它有静态变量,那么它可能处于任何状态。为什么?因为静态变量有可能被一些不同的实例修改,因为静态变量是跨实例共享的。

序列化:序列化也不能很好地与他们一起工作。

创造与毁灭:无法控制静态变量的创建和销毁。通常它们是在程序加载和卸载时创建和销毁的。这意味着它们不利于内存管理,并且还会增加启动时的初始化时间。

但如果我们真的需要它们呢?

但有时我们可能真的需要它们。如果我们真的觉得需要在应用程序中共享的许多静态变量,那么一种选择是使用具有所有这些变量的单例设计模式。或者我们可以创建一些具有这些静态变量并可以传递的对象。

此外,如果静态变量被标记为Final,它将成为一个常量,并且一旦分配给它的值就不能更改。这意味着它将使我们免于由于其可变性而面临的所有问题。

总结Java中使用静态方法的一些基本优点和缺点:

优点:

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

缺点:

  1. 静态成员始终是内存的一部分,无论它们是否在使用中。
  2. 您无法控制静态变量的创建和销毁。有用的是,它们在程序加载时创建,在程序卸载(或JVM关闭时)时销毁。
  3. 您可以使用同步使静态线程安全,但您需要一些额外的努力。
  4. 如果一个线程更改静态变量的值,则可能会破坏其他线程的功能。
  5. 在使用它之前,你必须知道“静态”。
  6. 您不能覆盖静态方法。
  7. 序列化对他们不起作用。
  8. 它们不参与运行时多态性。
  9. 如果使用大量静态变量/方法,则会出现内存问题(在某种程度上,但我猜并不多)。因为直到程序结束,它们才会被垃圾收集。
  10. 静态方法也很难测试。

静态变量没有好坏之分。它们代表描述整个类而不是特定实例的属性。如果你需要一个特定类的所有实例的计数器,静态变量将是保存值的正确位置。

当您尝试使用静态变量来保存与实例相关的值时,会出现问题。

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

从我的角度来看,static变量应该只有只读数据变量创建按公约

例如,我们有一个项目的用户界面,我们有一个国家,语言,用户角色等的列表。我们有类来组织这些数据。我们绝对确定没有这些列表,应用程序将无法工作。所以我们在app init上做的第一件事是检查此列表是否有更新,并从api获取此列表(如果需要)。所以我们同意这些数据“始终”存在于应用程序中。它实际上是只读数据,所以我们不需要照顾它的状态-考虑到这个案例,我们真的不希望有很多这些数据的实例-这个案例看起来是静态的完美候选者。

我玩过很多静力学,我可以给你一个稍微不同的答案,或者用稍微不同的方式来看待它吗?

当我在一个类(成员和方法)中使用静态时,我最终开始注意到我的类实际上是两个共享职责的类——有“静态”部分,它的行为很像单例,还有一个非静态部分(普通类)。据我所知,你总是可以通过为一个类选择所有静态而为另一个类选择非静态来完全分离这两个类。

当我在一个类中有一个静态集合来保存该类的实例和一些静态方法来管理集合时,这种情况经常发生。一旦你想到它,很明显你的类不是在做“只是一件事”,它是一个集合,做一些完全不同的事情。

现在,让我们稍微重构一下这个问题:如果你把你的类分成一个类,其中所有东西都是静态的,另一个类只是一个“普通类”,而忘记了“普通类”,那么你的问题就变成了纯粹的静态类vs单例,它在长度这里(可能还有十几个其他问题)中得到解决。

静态字段实际上是GC根(请参阅本章前面的垃圾收集如何工作部分),这意味着它们永远不会被垃圾收集!仅为方便起见,静态字段和集合通常用于保存缓存或跨线程共享状态。可变静态字段需要明确清理。如果开发人员没有考虑到每一种可能性(几乎肯定),清理将不会发生,从而导致内存泄漏。这种粗心的编程意味着静态字段和集合已经成为内存泄漏的最常见原因!

简而言之,永远不要使用可变静态字段-只使用常量。如果您认为需要可变静态字段,请再考虑一遍,然后再考虑一遍!总有更合适的技术。