在任何开发语言中,单例模式应该算是大家基乎最先接触和学习的设计模式,由于,它最为简单也最为常用。
单例模式的特点:
单例模式的使用场景:
单例也适时使用,不要觉得简单就乱用!
大家能够用多少种方式来实现单例模式?
至少我能用5种方式来实现,假如还有更多其它方式,也欢迎在评论区留言。
/** * 没有推迟加载,但却最简单 */public class Singleton { private static final Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; }}
针对 2.1 在类加载时就初始化,这里采用了推迟初始化。
/** * 推迟加载 */public class Singleton { private static Singleton instance = null; private Singleton(){} public synchronized static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; }}
/** * 静态内部类,加载时没有初始化 instance,因而达到了推迟加载 */public class Singleton { private static class InternalSingleton{ private static final Singleton instance = new Singleton(); } private Singleton(){} public static Singleton getInstance(){ return InternalSingleton.instance; }}
这种方式,大家要注意,网上有些是错误的。
public class Singleton { private static volatile Singleton instance = null; // 这里需要 volatile private Singleton(){} public static Singleton getInstance(){ if(instance == null){ synchronized(Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; }}
为何要加上 volatile ,大家可以去看我的《小白系列五:关键字volatile》。
public enum Singleton{ instance; // 默认 instance = this public void function(){ // ...... }}
5种方式都能正确的实现单例,但只有『枚举方式』才是最为安全的,因而,它不支持反射,而其它方式尽管私有化了默认构造函数,但是,我们能可以通过反射的方式来产生多实例。
import java.lang.reflect.Constructor;public class Demo { public static void main(String[] args) { Singleton singleton1 = Singleton.getInstance(); Singleton singleton2 = null; try { Constructor constructor = Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); singleton2 = (Singleton) constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println("singleton1 = " + singleton1); if (singleton2 != null) { System.out.println("singleton2 = " + singleton2); } }}
打印结果如下:
singleton1 = Singleton@610455d6singleton2 = Singleton@511d50c0
我们可以看到,Singleton 变成了多实例。
而枚举则不允许反射,JDK 源码中有给出判断:
// java.lang.reflect.Constructor.javapublic final class Constructor<T> extends Executable { ..... @CallerSensitive public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } } // 这里会判断类的修饰符,假如是枚举则报错 if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; } ......}
相比较静态常量,枚举会略微多消耗点内存,由于枚举首先还是一个类,而后实例化几个由 final 修饰这个类的对象,每个实例都带有自己的少量元信息。而常量没有这一层封装,只占用基本的内存(包括引用和它的值本身),因而,要简单轻巧;假如值是基本类型而不是包装类型,那占用的内存就更少了。
枚举的使用,和单例的使用一样,我们也要适度,不能滥用;同样,大家在使用枚举时也不要担心这多出来的一点点内存开销,该用的时候还是要用。同时,我们需要注意一点:枚举的出现,能够帮助我们提升代码可读性,以及更好的可扩展性;因而,相比较多出来的一点点内存开销,不建议以牺牲代码可读性、可维护性、开发效率等而不使用枚举。
总之,Java的单例模式,我更推荐使用枚举来实现!