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


635

我是一位Java程序员,对公司领域来说是新手。最近,我已经使用Groovy和Java 开发了一个应用程序。我编写的所有代码都使用了大量的静态变量。高级技术人员要求我减少使用的静电数量。我已经在谷歌上搜索了相同的内容,并且发现许多程序员都反对使用静态变量。

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

而且,静态函数减少了对代码其他部分的相互依赖。他们可以充当完美的国家拥有者。除此之外,我发现在某些语言(例如Smalltalk Scala的。那么,为什么在程序员中(尤其是在Java世界中)普遍存在对静态的这种压迫?

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


43
只是为了说一下,Smalltalk或Scala上没有静态变量或方法,恰恰是因为静态方法和变量违反OOP原理。
莫里西奥·林哈雷斯

87
至少您做一个这样的陈述很奇怪:“静态函数减少了代码其他部分的相互依赖性”。通常,它们会加强依赖性。进行调用的代码与被调用的代码紧密相关。之间没有抽象,直接依赖。
Arne Deutsch

11
好问题...更多是Programmers.SE的问题吗?
WernerCD

26
您的第二段涉及一个完全不同的主题,即静态方法
保罗

8
函数式编程也对全局状态不满意。如果你曾经(而且你应该)进入FP有一天,准备沟全局状态的概念。
new12

Answers:


689

静态变量表示全局状态。这很难推理,也很难测试:如果创建对象的新实例,则可以在测试中推断其新状态。如果我使用的代码使用的是静态变量,则它可能处于任何状态-任何事情都可能对其进行修改。

我可以继续进行一段时间,但是要考虑的更大概念是,事物的范围越紧密,就越容易进行推理。我们善于思考小事情,但是如果没有模块化,就很难对一百万行系统的状态进行推理。顺便说一下,这适用于各种各样的事物-不仅是静态变量。


57
不管是否可测试代码,最近似乎都是一个论点。这是一个相当有缺陷的推理。论点应该是“好的设计”,通常好的设计是可以测试的。但是相反:“我无法对其进行测试,因为它一定是错误的设计。” 不过请不要误会我的意思,我总体上同意您的帖子。
M Platvoet

144
@M Platvoet:我要说的是,如果在两个其他同样有效的设计之间进行选择,那么可测试的设计就更好了。可测试当然并不意味着设计良好,但是我很少遇到无法测试的良好设计,而且我认为它们非常稀有,因此我可以毫无疑问地将可测试性作为衡量良好设计的通用指标。
乔恩·斯基特

9
@M Platvoet-可测试性会影响可维护性和可靠性,我会考虑那些影响设计质量的主要因素。当然,它们不是唯一的因素,但是恕我直言,任何给定代码的成本都是机器周期,开发人员周期和用户周期的组合。可测性达到了这三个中的两个。
贾斯汀·摩根

5
@M Platvoet-可测试性也往往会影响可重用性,因为解耦的类通常更易于重用。
TrueWill 2011年

13
M Platvoet-我不同意您在这里的第一个评论。我认为,如果无法测试某些东西,那就是糟糕的设计;因为如果我不能测试它,我就不知道它可以工作。如果销售人员告诉您“此模型的设计阻止了它的测试,所以我不知道它是否实际运行”,您是否会购买汽车?可测试性对于软件(以及汽车)至关重要,以至于要有胜任的设计需求。
达伍德·伊本·卡里姆

277

它不是非常面向对象的: 某些人可能将静力学视为“邪恶”的原因之一是它们与面向对象的范式相反。特别是,它违反了将数据封装在对象中(可以扩展,隐藏信息等)的原理。静态,按照您描述使用它们的方式,本质上是将它们用作全局变量,以避免处理诸如范围之类的问题。但是,全局变量是过程或命令式编程范例的定义特征之一,而不是“好的”面向对象代码的特征。这并不是说程序范式很糟糕,但是我给您的印象是,您的主管希望您正在编写“良好的面向对象的代码”,而您确实想编写“

当您开始使用静态方法时,Java中有很多问题并不总是显而易见的。例如,如果您有两个在同一VM中运行的程序副本,它们是否会切分静态变量的值并彼此混淆?或在扩展类时会发生什么,可以覆盖静态成员吗​​?您的VM是否由于没有大量静态变量而用完内存,并且无法为其他所需的实例对象回收该内存?

对象生存期: 此外,静态变量的生存期与程序的整个运行期相匹配。这意味着,即使您使用完了类,也无法对所有这些静态变量的内存进行垃圾收集。举例来说,如果您将变量设为非静态,并且在main()函数中创建了类的单个实例,然后在完成这10,000次调用后要求您的类执行特定函数10,000次。 ,然后删除对单个实例的引用,则所有静态变量都可能被垃圾回收并重新使用。

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

其他选项: 如果效率是您最关心的问题,那么解决速度问题可能还有其他更好的方法,而不是仅考虑调用通常比创建更快的优点。考虑是否在任何地方都需要瞬态或挥发性调节剂。为了保留内联的能力,可以将方法标记为final而不是static。可以将方法参数和其他变量标记为final,以允许基于有关可更改这些变量的假设的某些编译器优化。一个实例对象可以多次重用,而不是每次都创建一个新实例。通常,应该为应用程序打开兼容的优化开关。也许应该对设计进行设置,以使10,000个运行可以是多线程的,并利用多处理器内核。如果可移植性是'

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


11
我喜欢您的回答,我认为它着重在权衡取舍时要考虑静态因素,而不是并发性和范围之类的一些红色鲱鱼。对于单身人士+1,一个更好的问题实际上可能是何时使用静态变量/方法与单身人士...
studgeek 2011年

2
即使单例本身可能是线程安全的(例如,通过使用synchronized方法),也并不意味着调用代码相对于单例状态没有竞争条件。
安德烈·卡隆

8
同样,静态也不违反OOP范式。许多OOP狂热者会告诉您,类是对象,而static方法是类对象的方法,而不是实例。Java中很少出现这种现象。其他语言(例如Python)允许您将类用作变量,并且可以将静态方法用作该对象的方法。
安德烈·卡隆

4
如果我没有记错的话,第三段的最后一行应显示所有非静态变量
史蒂夫(Steve)

1
Object Lifetime,是@jessica提到的非常重要的一点。
Abhidemon '17

93

邪恶是一个主观术语。

您不能根据创建和销毁来控制静态。他们按照程序加载和卸载的要求生活。

由于静态变量生活在一个空间中,因此所有希望使用它们的线程都必须通过您必须管理的访问控制。这意味着程序之间的耦合度更高,并且这种更改更难以设想和管理(如J Skeet所说)。这导致隔离变更影响的问题,从而影响测试的管理方式。

这是我遇到的两个主要问题。


59

不。全球性国家本身并不是邪恶的。但是我们必须查看您的代码以查看您是否正确使用了它。新手很可能滥用全球性国家。就像他会滥用所有语言功能一样。

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

为了摆脱它而试图摆脱全球国家的人们,不可避免地要拥有一个更加复杂的系统-并且全球国家仍然存在,在许多层间接的指导下被巧妙地/愚蠢地掩盖了;在解开所有间接之后,我们仍然必须对全局状态进行推理。

像Spring一样,人们大胆地在xml中声明全局状态并以某种方式认为它优越。

@Jon Skeet if I create a new instance of an object现在您需要考虑两件事-对象内的状态和托管对象的环境的状态。


10
“我有两件事要推理”。如果我仅根据对象状态进行测试,则不会。这比较容易,因为我所拥有的全局状态越少。
DJClayworth

2
依赖注入与全局状态或全局可见性无关,即使容器本身不是全局的。与“普通”代码相比,容器管理对象可见的唯一额外的事情是容器本身。实际上,DI非常常用于避免单例模式。
Floegipoky

31

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

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

我认为Jon Skeet引用的是您发表的相同评论。
RG-3

13
我没有“线程安全”要点,除非您这样做,否则我认为没有什么是线程安全的。这似乎与静态事物根本无关,如果我缺少某些东西,请纠正我。
Zmaster 2011年

1
@Zmaster-诚然,线程安全不是静态变量所独有的问题,因为根据它们的定义,它们将在不同的上下文中调用,而它们对它们的修剪更为
细腻

2
@sternr我理解您的意思,如果“不同的上下文”不一定等于“不同的线程”,则表示事件。但是,确实需要经常使用静态资源来考虑线程安全性。您应该考虑澄清句子。
Zmaster 2011年

例如,存在对静态资源的有效线程安全使用。私有静态最终Logger LOG = Logger.getLogger(Foo.class); 私有静态最终AtomicInteger x = new AtomicInteger(0); 据我了解,此类资源的静态分配由类加载器保证是线程安全的。Logger实例是线程安全的还是线程安全的,与将指针分配给它的位置无关。将状态保持在静态可能不是一个好主意,但是没有理由它不应该是线程安全的。
teknopaul 2015年

29

如果您使用的是'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框架,则可能不会遇到许多这种情况,但是如果使用大量遗留代码,则它们会变得更加频繁。


15

由于没有人*提到过:并发。如果您有多个线程读写静态变量,则静态变量会使您感到惊讶。这在Web应用程序(例如ASP.NET)中很常见,并且可能引起一些令人发疯的错误。例如,如果您有一个由页面更新的静态变量,并且两个人几乎“同时”请求该页面,则一个用户可能会获得另一用户期望的结果,或更糟糕的是。

静态变量减少了对代码其他部分的相互依赖。他们可以充当完美的国家拥有者

希望您准备好使用锁并处理争用。

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


5
实例变量与静态变量相比没有线程安全优势,它们都是不受保护的变量。相反,所有这些都取决于您如何保护访问这些变量的代码。
studgeek

2
我并没有提出这个主张,但是为了讨论起见:分离是一种保护形式。线程状态是分开的;全球状态不是。除非在线程之间显式共享实例变量,否则不需要保护。静态变量始终由进程中的所有线程共享。
贾斯汀·凯斯

我希望线程静态变量更多地是一流的概念,因为它们对于将信息安全地提供给包装后的子例程调用非常有用,而不必将该信息传递给包装的每一层。例如,如果对象具有将其呈现到线程的当前图形上下文的方法,并且存在用于保存/恢复当前图形上下文的方法,则使用这些方法通常比必须通过每个方法调用传递图形上下文更为简洁。
超级猫

15

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

优点:

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

缺点:

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

缺点6、7、8和10是所使用的语言/框架的缺点,而不是通常的静态变量的缺点。其他解决方案也存在缺点1、4和5,例如某些框架提供的某些单例模式。(我没有投票给答案,因为我同意其余的内容,而且它是一个很好的收藏。)
peterh-恢复莫妮卡

13

如果我必须对一个类中的函数进行10,000次调用,我将很高兴使该方法静态化并在其上使用直接的class.methodCall()而不是用10,000个该类的实例来弄乱内存,对吗?

您必须权衡将数据封装到带有状态的对象中的需要与仅计算某些数据上的函数结果的需要之间的平衡。

而且,静态函数减少了对代码其他部分的相互依赖。

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

其他答案也为防止过度使用静电提供了充分的理由。


13

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

话虽如此,重要的是要区分常规静态变量(通常被认为是不好的)和最终静态变量(AKA常数;还不错)。


4
“静态变量代表类之间的状态”……我想您的意思是“静态变量代表实例之间的状态”?+1为“最终静态AKA常数,还不错”。由于该值无法更改,因此在某个时间点依赖该值的所有内容都无法在以后的时间隐式更改其行为-该值是相同的。
Jared Updike

“静态变量代表实例之间的状态”是一种更好的陈述方式。我已经编辑了答案。
杰克·埃德蒙兹

9

我认为这几乎与性能无关,而与设计有关。我认为使用静态方法不会像使用静态变量那样错误(但是我想您实际上是在谈论方法调用)。

它只是关于如何隔离逻辑并为其提供一个好地方。有时这证明使用静态方法java.lang.Math是正确的例子。我认为,当你的名字大部分的类XxxUtilXxxhelper你最好重新考虑你的设计。


3
纯无副作用的静态方法非常适合IMO。但是全球可变状态很少出现,我将OP解释为谈论全球状态。
CodesInChaos 2011年

1
@CodeInChaos完全同意。我发现关于静态方法和var之间的区别,OP尚不完全清楚。
M Platvoet

8

我刚刚总结了答案中的一些观点。如果发现任何错误,请随时进行更正。

扩展:每个JVM仅有一个静态变量实例。假设我们正在开发一个图书馆管理系统,并且由于每本书只有一个,所以我们决定将书名放在一个静态变量中。但是,如果系统不断壮大,并且我们正在使用多个JVM,那么我们就没有办法弄清楚我们正在处理哪本书?

线程安全:在多线程环境中使用时,实例变量和静态变量都需要控制。但是,在实例变量的情况下,它不需要保护,除非在线程之间显式共享它;而在静态变量的情况下,它始终由进程中的所有线程共享。

测试:尽管可测试的设计并不等于好的设计,但是我们很少会观察到不可测试的好的设计。由于静态变量表示全局状态,因此很难对其进行测试。

关于状态的推理:如果我创建一个类的新实例,则可以对该实例的状态进行推理,但是如果它具有静态变量,则它可以处于任何状态。为什么?因为静态变量可能在某些实例之间被修改,因为静态变量在实例之间共享。

序列化:序列化也不适用于它们。

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

但是,如果我们真的需要它们怎么办?

但是有时我们可能真正需要它们。如果我们确实感觉到需要在整个应用程序中共享许多静态变量,那么一种选择是利用具有所有这些变量的Singleton Design模式。或者,我们可以创建一些具有这些静态变量并可以传递的对象。

同样,如果将静态变量标记为final,则该变量将变为常量,并且一次分配给它的值将无法更改。这意味着它将避免我们由于其可变性而面临的所有问题。


7

在我看来,您在询问静态变量,但您也在示例中指出了静态方法。

静态变量并不是邪恶的-静态变量在大多数情况下都被用作常量之类的全局变量,并与final修饰符结合使用,但是正如它所说的那样,不要过度使用它们。

静态方法又称实用方法。使用它们通常不是一个坏习惯,但是主要的担心是它们可能会阻碍测试。

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

结合了静态导入的静态变量/方法也广泛用于促进Java中的声明式编程的库中,如:easyHamcrest。没有很多静态变量和方法,这是不可能的。

因此,静态变量(和方法)是好的,但是要明智地使用它们!


6

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

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


6

可能建议在大多数情况下使用静态变量,但您确实希望使用单例模式

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


5

另一个原因:脆弱。

如果您有一个课程,那么大多数人都希望能够创建它并随意使用它。

您可以记录情况并非如此,或加以保护(单一模式/工厂模式)-但这是额外的工作,因此需要支付额外费用。即使这样,在一家大公司中,也有可能某人会在某些时候尝试使用您的课程,而没有完全注意所有好的注释或工厂。

如果您经常使用静态变量,那将会中断。虫子很贵。

在.0001%的性能提高和潜在的笨拙的开发人员进行更改的鲁棒性之间,在很多情况下,鲁棒性是不错的选择。


4

我发现静态变量更易于使用。而且我认为它们也是有效的(如果我错了,请纠正我),因为如果我必须对一个类中的函数进行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)...

}

经典的单例(即由可以访问的单例Class.Instance)仅比静态变量好。它的可测试性稍强一些,但比仅碰巧创建一个实例而不是假设只有一个实例来构建代码的设计要差得多。
CodesInChaos 2011年

不确定我是否理解您的评论!我向OP回应了他以斜体表示的关于实例化1万个对象的内容。我不明白为什么要比较单例和静态变量?从您写的内容中我了解到,Singleton是糟糕的设计……!我想我误会了您,因为默认情况下Spring框架使所有bean成为Singleton ;-)
Cygnusx1 2011年

Class.Instance带有可变状态的经典单例(具有)是不良的设计IMO。在那种情况下,我非常喜欢这样一种设计:我需要将需要使用的单例作为参数传递给使用它们的类(通常在DI的帮助下)。逻辑上不变的经典单例是很好的IMO。
CodesInChaos 2011年

@ Cygnusx1如果不清楚为什么一个类单例(该类确保单个副本的单例)为什么不容易测试,它会将类的存在与程序的生命周期紧密联系在一起。要测试它,您必须坚持程序的启动和关闭,这通常具有副作用,对于测试类而言并不重要。如果它实际上是单例的(程序中有一个副本,但不是强制执行),则可以在没有程序的情况下在测试时创建多个副本,从而验证每个测试场景在类中的行为都应如此。
埃德温·巴克

4

“静态是邪恶的”问题更多地是关于全球国家的问题。变量变为静态的适当时间是:如果变量不存在多个状态,则该时间为静态。整个框架都应该可以访问的IE工具,对于相同的方法调用总是返回相同的结果,这些工具永远不会像静态变量那样“邪恶”。关于您的评论:

我发现静态变量更易于使用。我认为它们也是有效的

静态变量是不变的变量/类的理想而有效的选择

全局状态的问题是它可能造成的内在矛盾。有关单元测试的文档通常会解决此问题,因为任何时候只要有一个全局状态都可以由多个不相关的对象访问,则您的单元测试将是不完整的,并且不会“粒度化”。如本文中关于全局状态和单例所提到的,如果对象A和B不相关(如未明确引用另一个对象),则A不应影响B的状态。

在良好的代码中,禁止全局状态有一些例外,例如时钟。时间是全球性的,从某种意义上说,时间在没有编码关系的情况下改变了对象的状态。


“时间是全球性的”-在计算系统中建模时间还有其他方法,而不是让某些隐式的全局性事物自行改变。cf. 这项调查:“计算中的建模时间:分类法和比较调查” @ arxiv.org/abs/0807.4132
Jared Updike

4

我的$ .02美元是这些答案中的几个令人困惑的问题,而不是说“静态变量不好”,我认为谈论范围界定和实例更好。

我要说的是,静态变量是“类”变量-它表示该类的所有实例之间共享的值。通常,它也应该以这种方式确定范围(对类及其实例的保护或私有)。

如果计划围绕类级别的行为并将其暴露给其他代码,则单例可能是将来支持更改的更好解决方案(如@Jessica所建议)。这是因为您可以在实例/单例级别上使用接口,而在类级别上则不能使用接口,尤其是继承。

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

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

并发对于静态而言并不比实例方法危险。仍然需要保护。当然,您可能有1000个实例,每个实例都有一个实例变量,而只有一个静态变量,但是,如果不是以线程安全的方式编写访问这两个实例的代码,那么您仍然会感到困惑-您可能需要更长的时间才能实现它。

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

PS,就Smalltalk而言,它的许多方言确实具有类变量,但是在Smalltalk中,类实际上是Metaclass的实例,因此它们实际上是Metaclass实例上的变量。尽管如此,我还是会采用相同的经验法则。如果它们被用于实例之间的共享状态,那就好了。如果他们支持公共功能,则应查看Singleton。igh,我肯定会怀念Smalltalk。


4

您的帖子中有两个主要问题。

首先,关于静态变量。静态变量完全不必要,可以轻松避免使用它。通常,在OOP语言中,尤其是在Java中,函数参数是通过引用来粘贴的,也就是说,如果将对象传递给函数,则是将指针传递给对象,因此不需要定义静态变量,因为您可以将指针传递给需要此信息的任何作用域。即使这意味着您将用指针填充您的内存,但这也不一定表示性能低下,因为实际的内存分配系统已针对此问题进行了优化,并且它们将在内存中保留由您传递给新指针的指针所引用的页面范围; 使用静态变量可能会导致系统在需要访问它们时加载存储页面的内存页面(如果很长时间未访问该页面,就会发生这种情况)。一个好的做法是将所有静态stuf放在一些小的“配置类”中,这将确保系统将它们全部放在同一内存页中。

第二,关于静态方法。静态方法还不错,但是它们可以迅速降低性能。例如,考虑一种方法,该方法比较一个类的两个对象并返回一个值,该值指示哪个对象更大(常规比较方法),该方法可以是静态的,也可以是静态的,但是调用非静态形式时,该方法将更有效。因为它只需要解析两个引用(每个对象一个),面对的三个引用将必须解析同一方法的静态版本(一个用于类加两个,每个对象一个)。但是正如我所说,这还不错,如果我们看一下Math类,我们会发现很多定义为静态方法的数学函数。这比将所有这些方法放在定义数字的类中更加有效,

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

PS:对不起,我的英语。


4

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


3

a)关于程序的原因。

如果您有一个小型到中型程序,在其中访问了静态变量Global.foo,则对它的调用通常来自任何地方-没有路径,因此也没有时间表,该变量如何到达该位置,在何处用来。现在,我怎么知道是谁将其设置为实际值?我现在怎么修改,怎么知道?我在整个源代码中都有grep,以收集所有访问权限,以了解发生了什么情况。

如果您知道如何使用它,因为您只是编写了代码,则问题是看不见的,但是,如果您尝试了解外部代码,那么您将会理解。

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

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

只有或多或少的无用的代码才能与静态变量一起很好地使用,而这些代码不会被很多人长时间地密集使用。


3

一切都可以有其目的,如果您有一束需要共享/缓存数据的线程以及所有可访问的内存(因此您不希望在一个JVM中拆分上下文),那么静态是最佳选择

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


3

静态变量既不好也不邪恶。它们代表描述整个类而不是特定实例的属性。如果需要为某个类的所有实例提供计数器,则静态变量将是保存该值的正确位置。

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


2

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


1
但是,严格考虑您的代码遵循某种任意的标准范例实际上会使代码变得更好吗,还是我们抱怨要避免仅编写有效的代码?
Zoey

是的,它的确使它变得更好,因为它使它在将来更易于管理,更易于理解和更加明确。
笨蛋

1
为什么不编写OO代码是邪恶的?为什么Bjarne Stroustrup与您不同意?仅举一个...
洛恩侯爵,

2
我没有说不编写OO代码是很邪恶的。我说过以为您正在编写面向对象的代码是很邪恶的,而当您正在掩盖静态方法和属性的全局变量时。请重新阅读我写的内容。
笨蛋

2

这里有很多很好的答案,

内存:只要类加载器处于生存状态(通常直到VM死机),静态变量就一直存在,但这仅在将大对象/引用存储为静态的情况下。

模块化:考虑IOC,dependencyInjection,Proxy等概念。所有这些都完全反对紧密耦合/静态实现。

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


0

认为如果您的应用程序有很多用户,并且定义了静态表单,那么每个用户也会修改其他用户的所有其他表单。



0

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

例如,我们有一个项目的用户界面,并且有一个国家,语言,用户角色等的列表。我们有一个类来组织此数据。我们绝对确定,没有此列表,该应用程序将无法运行。因此,我们在应用初始化时首先要检查此列表是否有更新,并从api中获取此列表(如果需要)。因此,我们同意此数据“始终”存在于应用程序中。它实际上是只读数据,因此我们不需要关心它的状态-考虑这种情况,我们确实不想有很多这些数据的实例-这种情况看起来是静态的完美选择。


0

我玩过很多关于静力学的东西,我可以给你一个稍微不同的答案吗?或者也许是一个稍微不同的查看方式?

当我在一个类(成员和方法都使用)中使用了静态变量时,我终于开始注意到我的类实际上是两个共同承担责任的类-“静态”部分的作用很像单例,而没有-静态部分(常规类)。据我所知,您始终可以通过只为一个类选择所有静态变量而为另一个类选择非静态变量来完全分离这两个类。

当我在一个类中有一个包含类实例的静态集合以及一些用于管理集合的静态方法时,这种情况经常发生。一旦考虑了一下,很明显您的班级并没有做“仅一件事”,而是一个集合,并且所做的事情完全不同。

现在,让我们对问题进行一些重构:如果将您的班级分为一类,其中所有类都是静态的,而另一类只是“普通类”,而忘记了“普通类”,那么您的问题将变成纯静态类与Singleton,其中在这里详细讨论(可能还有其他十二个问题)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.