在罗伯特·C·马丁(Robert C. Martin)的《清洁代码》一书中,他清理了一个凌乱的类,并以这种方式结束了一个包含静态变量和静态函数的文件。
public class PrimeGenerator{
private static int[] primes;
private static ArrayList<Integer> multipleOfPrimes;
public static int[] generate(int n){
primes = new int[n];
//call functions
someFunctionThatModifiesPrimes()
return primes;
}
private static void someFunctionThatModifiesPrimes(){
//modify primes
prime[x] = y;
}
}
他写
请注意,它并非要实例化为对象。班上
只是一个有用的范围,可以在其中声明和隐藏变量。
我的问题是,当我能够做到这一点时,为什么我会做他做的事情:
public class PrimeGenerator{
private int[] primes;
private ArrayList<Integer> multipleOfPrimes;
public PrimeGenerator(int n){
primes = new int[n];
}
public int[] generate(int n){
//call functions
someFunctionThatModifiesPrimes()
return primes;
}
private void someFunctionThatModifiesPrimes(){
//modify primes
prime[x] = y;
}
}
他的代码:
a)不是线程安全的,在素数已经生成时调用“generate(int)”(从多个线程调用)会使此操作失败。
b)在完成运行后保留带有垃圾素数的全局变量,下次运行时将被覆盖。
我能想到的唯一好处是它可能更快?即使那样,那也可以忽略不计。
需要创建对象的代码是线程安全的,不会保留垃圾数据并且没有静态。
最佳答案
我认为在此集中关注一本书的叙述可能是一个问题。的确,这是要获得整个社区的共识,以找出最佳编码实践。找出在生产环境中哪种效果更好。
有理由进行静态和非静态的函数和变量。但是,我会犹豫是否使用“优势”一词。
静态
如果您具有与成员字段无关的纯逻辑(即,从不引用成员字段),则将其静态化可能会更好。基本上,在这种情况下您可能会执行静态操作的原因是某种程度上的“关注点分离”-如果您正在调试代码或尝试读取代码,并且知道它是静态函数,则您无需担心会员。
有时我使变量本身成为静态变量,并使它们成为类的成员而不是对象的成员。实际上,此类情况可能就是您的质数示例。素数不会从PrimeGenerator
的一种实现更改为另一种实现,那么为什么要计算两次呢?你不会的在计算完值之后,可以以对象的所有实例都可以访问或外部调用者可以静态访问的方式存储它们。
我认为反对线程安全的呼声很高。为什么它不是线程安全的?您可以使用synchronized(PrimeGenerator.class)
, java.util.concurrent.*
或 Collections.unmodifiable*(*)
的任意组合使其具有线程安全性。
考虑到这一点,在全局/常量对象和范围有限的实例化对象之间有一条细线。实际上,回顾质数示例,很容易看出如何将其视为常数(存在无限质数,但在计算上存在有限性,这意味着我们可以在定义完整集时获得完整的Set) 。常量始终是静态的,仅因为没有理由为此浪费对象上的空间。因此,真正的核心是,质数示例实际上是该书作者的一个非常差的示例。那么,什么不是常数而是静态的好例子呢?
立即想到的示例是java.util.logging.Logger,但是我跳过了这一点,因为根据我针对质数给出的论点,试图证明它不是常数可能会更难。相反,如果您要忍受我一会儿,我将使用java.util.concurrent.atomic.AtomicBoolean。考虑一个场景,其中一个类Foo
的多个实例都在进行某种工作,而这些工作都严格基于某种称为bar
的AtomicBoolean,因此在尝试进行某些工作之前,它们需要检查bar
的状态。您可能会执行类似观察者模式的操作,并通知Foo
的所有实例化,当然,但是随后您会在通知上浪费cpu周期(无论最终结果是多少),同时,每个实例化都需要将该值存储为OFF,复制,与其他所有实例相同。或者,您可能只有一个public static final AtomicBoolean bar = new AtomicBoolean();
,并且每个实例仅需要执行Foo.bar.compareAndSet(expected, newValue);
。
非静态
归根结底,这是一种偏爱,但是那里散布着一些社区,以及一些效率问题。如果您真的不想使用静态,那绝对可以。您可能最终仅实例化一个类以从函数中获取值,然后转储刚在堆上创建的Object,但是可以这样做。
我最近听到的一些反对static的论点,我认为它是论点的更严格的竞争者(但实际上仍然缺乏任何权重)是代码不可测试。我会说,在生产环境中,JUnits很重要。可测试的代码可以尽早发现bug,而yada yada yada则可以。那么,这与静态有什么关系呢?好吧,像Mockito这样的框架(这是一种非常流行的框架)不支持对静态模型进行模拟。因此,许多非常薄的论据都声称这意味着静态代码不可测试。
但是,这并不是真的,因为所有模拟框架都在某种程度上支持静态模拟。 Mockito
本身甚至具有一个旨在与其他框架集成的API,例如PowerMock。因此,尽管说Mockito
不能模拟静态技术在技术上是准确的,但由于它专门设置了一种可以集成的方式,因此有点误导。
关于模拟框架如何使用类以及如何与类加载器交互的问题,这里有一些小的警告,但我觉得那有点超出范围。 (而且我会毫不犹豫地说,因为它的确赋予了非静态更多的权重,并使其看起来好像我在争辩静态,我不是,但这里有很多要讨论的内容-涉及类加载器,模拟框架,Java字节码等-这远远超出了此问题的范围)
结论(?)
长久以来的方法是,静力学没有错。有状态对象时不要使用它们,这样会很好的。在静态上下文中要小心线程安全(您也应该在非静态上下文中这样做,所以这不应该是一个新概念),并且不要破坏编码约定。我知道这听起来有点通用,但实际上这就是全部。我发现吹捧静电不好的人往往对此没有真正的反对意见,通常这是关于他们想无缘无故地强加于人的个人意识形态。因此,真正的原因在于倾听反对它的论据,看看它们是否具有分量。如果他们不这样做,那是您个人的判断。就您引用的书而言,我认为该论点不重要。
关于java - 使用静态函数/变量相对于类/对象有什么好处吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46147647/