Static Classes Versus the Singleton Pattern in Java – 静态类与Java中的单子模式的对比

最后修改: 2021年 7月 13日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this quick tutorial, we’ll discuss some eminent differences between programming to the Singleton design pattern and using static classes in Java. We’ll review both the coding methodologies and compare them with respect to different aspects of programming.

在这个快速教程中,我们将讨论按照Singleton设计模式进行编程与在Java中使用静态类之间的一些突出区别。我们将回顾这两种编码方法,并在编程的不同方面对它们进行比较。

By the end of this article, we’ll be able to make the right decision when picking between the two options.

在本文结束时,我们将能够在这两种选择中做出正确的决定。

2. The Basics

2.基础知识

Let’s hit ground zero. Singleton is a design pattern that assures a single instance of a Class for the lifetime of an application.
It also provides a global point of access to that instance.

让我们从头开始吧。Singleton是一种设计模式,它确保在应用程序的生命周期中只有一个的实例。
它还提供了一个访问该实例的全局点。

static – a reserved keyword – is a modifier that makes instance variables as class variables. Hence, these variables get associated with the class (with any object). When used with methods, it makes them accessible just with the class name. Lastly, we can also create static nested inner classes.

static–一个保留关键字–是一个修改器,它使实例变量成为类变量。因此,这些变量会与类(与任何对象)相关联。当与方法一起使用时,它使它们只需用类的名字就可以访问。最后,我们还可以创建静态的嵌套的内部类

In this context, a static class contains static methods and static variables.

在这种情况下,一个静态类包含静态方法和静态变量

3. Singleton Versus Static Utility Classes

3.单子类与静态实用类

Now, let’s go down the rabbit hole and understand some prominent differences between the two giants. We begin our quest with some Object-Oriented concepts.

现在,让我们走进兔子洞,了解这两个巨头之间的一些突出区别。我们的探索从一些面向对象的概念开始。

3.1. Runtime Polymorphism

3.1.运行时多态性

Static methods in Java are resolved at compile-time and can’t be overridden at runtime. Hence, a static class can’t truly benefit from runtime polymorphism:

Java中的静态方法是在编译时解决的,在运行时不能被重写。因此,静态类不能真正从运行时多态性中受益。

public class SuperUtility {

    public static String echoIt(String data) {
        return "SUPER";
    }
}

public class SubUtility extends SuperUtility {

    public static String echoIt(String data) {
        return data;
    }
}

@Test
public void whenStaticUtilClassInheritance_thenOverridingFails() {
    SuperUtility superUtility = new SubUtility();
    Assert.assertNotEquals("ECHO", superUtility.echoIt("ECHO"));
    Assert.assertEquals("SUPER", superUtility.echoIt("ECHO"));
}

Contrastingly, singletons can leverage the runtime polymorphism just like any other class by deriving from a base class:

与此相反,singletons可以像其他类一样,通过派生自基类而利用运行时多态性

public class MyLock {

    protected String takeLock(int locks) {
        return "Taken Specific Lock";
    }
}

public class SingletonLock extends MyLock {

    // private constructor and getInstance method 

    @Override
    public String takeLock(int locks) {
        return "Taken Singleton Lock";
    }
}

@Test
public void whenSingletonDerivesBaseClass_thenRuntimePolymorphism() {
    MyLock myLock = new MyLock();
    Assert.assertEquals("Taken Specific Lock", myLock.takeLock(10));
    myLock = SingletonLock.getInstance();
    Assert.assertEquals("Taken Singleton Lock", myLock.takeLock(10));
}

Moreover, singletons can also implement interfaces, giving them an edge over static classes:

此外,singletons也可以实现接口,使它们比静态类更有优势。

public class FileSystemSingleton implements SingletonInterface {

    // private constructor and getInstance method

    @Override
    public String describeMe() {
        return "File System Responsibilities";
    }
}

public class CachingSingleton implements SingletonInterface {

    // private constructor and getInstance method

    @Override
    public String describeMe() {
        return "Caching Responsibilities";
    }
}

@Test
public void whenSingletonImplementsInterface_thenRuntimePolymorphism() {
    SingletonInterface singleton = FileSystemSingleton.getInstance();
    Assert.assertEquals("File System Responsibilities", singleton.describeMe());
    singleton = CachingSingleton.getInstance();
    Assert.assertEquals("Caching Responsibilities", singleton.describeMe());
}

Singleton-scoped Spring Beans implementing an interface are perfect examples of this paradigm.

Singleton-scoped Spring Bean实现一个接口就是这种模式的完美例子。

3.2. Method Parameters

3.2 方法参数

As it’s essentially an object, we can easily pass around a singleton to other methods as an argument:

由于它本质上是一个对象,我们可以很容易地将一个单子作为参数传递给其他方法

@Test
public void whenSingleton_thenPassAsArguments() {
    SingletonInterface singleton = FileSystemSingleton.getInstance();
    Assert.assertEquals("Taken Singleton Lock", singleton.passOnLocks(SingletonLock.getInstance()));
}

However, creating a static utility class object and passing it around in methods is worthless and a bad idea.

然而,创建一个静态的实用类对象并在方法中传递它是毫无价值的,是一个坏主意。

3.3. Object State, Serialization, and Cloneability

3.3.对象状态、序列化和可克隆性

A singleton can have instance variables, and just like any other object, it can maintain a state of those variables:

一个单子可以有实例变量,就像任何其他对象一样,它可以维护这些变量的状态。

@Test
public void whenSingleton_thenAllowState() {
    SingletonInterface singleton = FileSystemSingleton.getInstance();
    IntStream.range(0, 5)
        .forEach(i -> singleton.increment());
    Assert.assertEquals(5, ((FileSystemSingleton) singleton).getFilesWritten());
}

Furthermore, a singleton can be serialized to preserve its state or to be transferred over a medium, such as a network:

此外,singleton可以被serialized以保持其状态或通过媒介(如网络)传输

new ObjectOutputStream(baos).writeObject(singleton);
SerializableSingleton singletonNew = (SerializableSingleton) new ObjectInputStream
   (new ByteArrayInputStream(baos.toByteArray())).readObject();

Finally, the existence of an instance also sets up the potential to clone it using the Object’s clone method:

最后,一个实例的存在也为使用对象的克隆方法来克隆它提供了可能。

@Test
public void whenSingleton_thenAllowCloneable() {
    Assert.assertEquals(2, ((SerializableCloneableSingleton) singleton.cloneObject()).getState());
}

Contrarily, static classes only have class variables and static methods, and therefore, they carry no object-specific state. Since static members belong to the class, we can’t serialize them. Also, cloning is meaningless for static classes due to the lack of an object to be cloned. 

严格来说,静态类只有类的变量和静态方法,因此,它们不携带任何针对对象的状态。由于静态成员属于类,我们不能将它们序列化。此外,由于缺乏可供克隆的对象,克隆对于静态类是没有意义的。

3.4. Loading Mechanism and Memory Allocation

3.4.加载机制和内存分配

The singleton, like any other instance of a class, lives on the heap. To its advantage, a huge singleton object can be lazily loaded whenever required by the application.

单子,像任何其他类的实例一样,生活在堆上。它的优点是,只要应用程序需要,一个巨大的单子对象就可以懒洋洋地加载。

On the other hand, a static class encompasses static methods and statically bound variables at compile time and is allocated on the stack.
Therefore, static classes are always eagerly loaded at the time of class loading in the JVM.

另一方面,静态类在编译时包含了静态方法和静态绑定的变量,并被分配在堆栈中。
因此,静态类总是在JVM中类加载时被急切地加载。

3.5. Efficiency and Performance

3.5.效率和性能

As iterated earlier, static classes don’t require object initialization. This removes the overhead of the time required to create the object.

正如前面所迭述的,静态类不需要对象初始化。这就消除了创建对象所需的时间开销。

Additionally, by static binding at compile-time, they’re more efficient than singletons and tend to be faster.

此外,通过在编译时进行静态绑定,它们比单子更有效,而且往往更快。

We must choose singletons for design reasons only and not as a single instance solution for efficiency or a performance gain.

我们必须仅出于设计原因而选择单子,而不是为了效率或性能的提高而选择单子的解决方案。

3.6. Other Minor Differences

3.6.其他小差异

Programming to a singleton rather than a static class can also benefit the amount of refactoring required.

对单子而不是静态类进行编程,也可以使所需的重构量受益。

Unquestionably, a singleton is an object of a class. Therefore, we can easily move away from it to a multi-instance world of a class.

毋庸置疑,单例是一个类的对象。因此,我们可以很容易地从它转移到一个类的多实例世界中。

Since static methods are invoked without an object but with the class name, migrating to a multi-instance environment could be a relatively larger refactor.

由于静态方法的调用没有对象,但有类的名字,迁移到多实例环境可能是一个相对较大的重构。

Secondly, in static methods, as the logic is coupled to the class definition and not to the objects, a static method call from the object being unit-tested becomes harder to be mocked or even overwritten by a dummy or stub implementation.

其次,在静态方法中,由于逻辑被耦合到类的定义,而不是对象,被单元测试的对象的静态方法调用变得很难被模拟,甚至被假的或存根的实现覆盖。

4. Making the Right Choice

4.做出正确的选择

Go for a singleton if we:

如果我们去找一个单子。

  • Require a complete object-oriented solution for the application
  • Need only one instance of a class at all given times and to maintain a state
  • Want a lazily loaded solution for a class so that it’s loaded only when required

Use static classes when we:

当我们使用静态类时。

  • Just need to store many static utility methods that only operate on input parameters and do not modify any internal state
  • Don’t need runtime polymorphism or an object-oriented solution

5. Conclusion

5.总结

In this article, we reviewed some of the essential differences between static classes and the Singleton pattern in Java. We also inferred when to use either of the two approaches in developing software.

在这篇文章中,我们回顾了Java中静态类和Singleton模式的一些本质区别。我们还推断了在开发软件时何时使用这两种方法中的任何一种。

As always, we can find the complete code over on GitHub.

一如既往,我们可以在GitHub上找到完整的代码超过