单例 public 静态最终

singleton public static final

我一直在想 Java 中的单身人士。按照惯例,单例是这样设置的:

private static MyClass instance = null;
public static MyClass getInstance(){
    if (instance == null){
        instance = new MyClass();
    }
    return instance;
}
private MyClass(){}

最近我改用了以下内容:

public static final MyClass instance = new MyClass();
private MyClass(){}

由于没有空值检查,因此更短、更快,并且输入 MyClass.instance 比输入 MyClass.getInstance() 对我来说感觉更好。有什么理由说第二种不是主流的做法吗?

第一个版本在第一次实际需要时创建实例,而第二个(较短的)在 class is initialized

时立即运行构造函数

A class or interface type T will be initialized immediately before the first occurrence of any one of the following:

  • T is a class and an instance of T is created.
  • T is a class and a static method declared by T is invoked.
  • A static field declared by T is assigned.
  • A static field declared by T is used and the field is not a constant variable (§4.12.4).
  • T is a top level class (§7.6), and an assert statement (§14.10) lexically nested within T (§8.1.3) is executed. [...]

Invocation of certain reflective methods in class Class and in package java.lang.reflect also causes class or interface initialization.

首次使用时的初始化是一种性能改进,如果构造函数中的代码进行昂贵的操作,可以加快应用程序的启动速度。另一方面,第二个版本易于阅读并且自动是线程安全的。

无论如何,最先进的技术并不是以任何一种方式创建单例:对于一堆 KB,您可以获得使其为您工作的依赖注入库,并且还可以处理更复杂的场景(例如查看 Spring 和 AOP 支持的注入)。

注意:粘贴的代码段中的第一个版本不是线程安全的

您首先描述的方式称为惰性实例化,即对象只会在首次调用时创建。此方法不是线程安全的,因为第二个线程可以创建第二个实例。

如果您阅读了以下书籍:

Effective Java by Joshua Bloch

他解释说单例模式的最佳实现是通过使用 Enum :

public enum Singleton {

  INSTANCE;

  public void doSomething() {
     ...
  }

}

然后你将通过 Enum 调用你的单例,如下所示:

public class Test {

    public void test(){
        Singleton.INSTANCE.doSomething();
    }
}

这与您所说的非常吻合,因为它看起来更好、更短,但也保证永远不会有第二个实例。

同步线程最好的方法是使用Double Checked(确保一次只有一个线程进入synchronized块,避免每次执行代码都获取锁)。

public class DoubleCheckLocking {

    public static class SearchBox {
        private static volatile SearchBox searchBox;

        // private attribute of this class
        private String searchWord = "";
        private String[] list = new String[]{"Stack", "Overflow"};

        // private constructor
        private SearchBox() {}

        // static method to get instance
        public static SearchBox getInstance() {
            if (searchBox == null) { // first time lock
                synchronized (SearchBox.class) {
                    if (searchBox == null) {  // second time lock
                        searchBox = new SearchBox();
                    }
                }
            }
            return searchBox;
        }
}

我可以想到两个原因:

第一个是 封装:在 class 暴露给客户端代码后,您可能会重新考虑初始化单例的方式和时间。初始化方法让您以后可以更自由地更改策略。例如,根据运行时另一个静态变量的值,您可能会改变主意并决定使用两个不同的构造函数而不是一个。使用您的解决方案,在将 class 加载到内存中时,您只能使用一个构造函数,而使用 getInstance() 您可以更改初始化逻辑,而不会影响客户端代码的接口。

第二个是惰性初始化:在传统的单例实现中,MyClass对象仅在客户端代码第一次需要时才加载到内存中。如果客户端代码根本不需要它,您可以节省应用程序分配的内存。请注意,在程序运行之前,可能无法确定是否需要您的单例。例如,它可能取决于用户与程序的交互。

然而,惰性初始化并不是您所需要的。例如,如果您正在编写一个交互式系统,并且单例的初始化很耗时,那么在程序加载时而不是在用户已经与它交互时初始化它实际上可能更好,因为后者可能会导致第一次调用 getInstance() 时系统响应的延迟。但在这种情况下,您可以使用 public 方法初始化您的实例,如:

private static MyClass instance = getInstance();

Reflection:可以引起反射破坏单例 属性 of singleton class,如下例所示:

 // Java code to explain effect of Reflection 

 import java.lang.reflect.Constructor; 

 // Singleton class 
 class Singleton  
 { 
     // public instance initialized when loading the class 
     public static Singleton instance = new Singleton(); 

     private Singleton()  
     { 
         // private constructor 
     } 
 } 

 public class GFG  
 { 

     public static void main(String[] args) 
     { 
         Singleton instance1 = Singleton.instance; 
         Singleton instance2 = null; 
         try
         { 
             Constructor[] constructors =  
                     Singleton.class.getDeclaredConstructors(); 
             for (Constructor constructor : constructors)  
             { 
                 // Below code will destroy the singleton pattern 
                 constructor.setAccessible(true); 
                 instance2 = (Singleton) constructor.newInstance(); 
                 break; 
             } 
         } 

         catch (Exception e)  
         { 
             e.printStackTrace(); 
         } 

     System.out.println("instance1.hashCode():- " 
                                       + instance1.hashCode()); //366712642
     System.out.println("instance2.hashCode():- " 
                                       + instance2.hashCode()); //1829164700
     } 
 }