使用静态函数/变量与类/对象有什么好处吗?

时间:2017-09-11 03:07:15

标签: java code-formatting

在书"清洁代码"通过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)线程不安全,调用&#39; generate(int)&#39;虽然素数已经生成(从多个线程调用)会使此失败。

b)在完成运行后保留一个带有垃圾填充的全局变量,它在下次运行时才会被覆盖。

我能想到的唯一优势是它可能更快?即便如此,这也可以忽略不计。

需要创建对象的代码是线程安全的,不会保留垃圾数据并且没有静态状态。

1 个答案:

答案 0 :(得分:1)

我认为重点放在一本书的叙述上可能是一个问题。的确,这是要获得整个社区的共识,以找出最佳编码实践。找出在生产环境中哪种效果更好。

执行静态和非静态函数和变量是有原因的。但是,我会犹豫是否使用“优势”一词。

静态

如果您具有不依赖于成员字段的纯逻辑(即,从不引用成员字段),那么将其静态化可能会更好。基本上,在这种情况下您可能会执行静态操作的原因是某种程度上的“关注点分离”-如果您正在调试代码或尝试读取代码,并且知道它是静态函数,那么您就没有了担心成员。

有时,我将变量本身设为静态,并使它们成为类的成员而不是对象的成员。实际上,像这样的情况可能就是您的质数示例。质数不会从PrimeGenerator的一种实现更改为另一种实现,那么为什么要计算两次呢?你不会的在计算完值之后,可以以对象的所有实例均可访问或外部调用者可以静态访问的方式存储它们。

我认为反对线程安全的呼声很高。为什么它不是线程安全的?您可以使用synchronized(PrimeGenerator.class)java.util.concurrent.*Collections.unmodifiable*(*)的任意组合来使其具有线程安全性。

牢记这一点,在全局/常量对象和范围有限的实例化对象之间有一条细线。实际上,回顾质数示例,很容易看出如何将其视为常数(存在无限质数,但在计算上存在有限性,这意味着我们可以在定义完整集时获得完整的Set) 。常量始终是静态的,仅因为没有理由为此浪费对象上的空间。因此,真正的核心是,质数示例实际上是该书作者的一个非常差的示例。那么,什么不是常数而是静态的好例子呢?

立即想到的示例是java.util.logging.Logger,但是我跳过了这一点,因为根据我对质数的论点,试图证明它不是常数可能会更难。相反,如果您可以忍受我一会儿,我会选择java.util.concurrent.atomic.AtomicBoolean。考虑一个场景,其中类Foo的多个实例正在执行某种工作,而这些工作都严格基于称为bar的某个AtomicBoolean,因此它们需要检查{{ 1}},然后再尝试做一些工作。您可能会做类似观察者模式的操作,并通知bar的所有实例化,但是,然后您在通知上浪费了CPU周期(无论最终结果有多大),同时,每个实例化都需要存储该值与其他所有实例相同,重复。或者,您可能只有一个Foo,例如,每个实例只需要做public static final AtomicBoolean bar = new AtomicBoolean();

非静态

归根结底,这是一种偏爱,但是那里散布着一些社区以及一些效率问题。如果您真的不想使用静态方法,那绝对可以。您可能最终仅实例化一个类以从函数中获取值,然后转储刚在堆上创建的对象,但是您可以做到这一点。

我最近听到的一些反对static的论点,我认为它是论点的更严格的竞争者(但实际上仍然缺乏任何权重)是代码不可测试。我会说,在生产环境中,JUnits很重要。可测试的代码可以尽早发现bug,而yada yada yada则可以。那么,这与静态有什么关系呢?好的,Mockito之类的框架(这是一种非常流行的框架)不支持静态模拟。因此,很多非常薄的论据都声称这意味着静态代码不可测试。

但这根本不是真的,因为所有模拟框架都在某种程度上支持静态模拟。 Foo.bar.compareAndSet(expected, newValue);本身甚至具有一个旨在与其他框架集成的API,例如PowerMock。因此,尽管说Mockito不能模拟静态技术在技术上是准确的,但由于它专门设置了一种可以集成的方式,因此有些误导。

关于模拟框架如何使用类以及如何与类加载器进行交互的一些小警告,但我觉得那有点超出范围。 (而且我会毫不犹豫地说,因为它的确赋予了非静态更多的权重,并使其看起来好像我在争辩静态,我不是,但这里有很多要讨论的内容-涉及类加载器,模拟框架,Java字节码等-这远远超出了这个问题的范围)

结论(?)

长期存在的方法是,静力学没有错。当您有状态对象时不要使用它们,这样就可以了。在静态上下文中要小心线程安全(您也应该在非静态上下文中这样做,所以这不应该是一个新概念),并且不要破坏编码约定。我知道这听起来有点通用,但实际上仅此而已。我发现吹捧静电不好的人往往对此没有真正的反对意见,通常是关于他们想无缘无故地强加于人的个人意识形态。因此,真正的原因在于倾听反对它的论据,看看它们是否具有分量。如果他们不这样做,那是您个人的判断。就您引用的书而言,我认为该论点不重要。