java 枚举 单例 原理_Java枚举类型(enum)和枚举单例模式

枚举的定义//枚举类型,使用关键字enum

enum Day {

MONDAY, TUESDAY, WEDNESDAY,

THURSDAY, FRIDAY, SATURDAY, SUNDAY

}

枚举的引用public class EnumDemo {

public static void main(String[] args){

//直接引用

Day day =Day.MONDAY;

}

}

//定义枚举类型

enum Day {

MONDAY, TUESDAY, WEDNESDAY,

THURSDAY, FRIDAY, SATURDAY, SUNDAY

}

枚举实现的原理

实际上在使用关键字enum创建枚举类型并编译后,编译器会为我们生成一个相关的类,这个类继承了Java API中的java.lang.Enum类,也就是说通过关键字enum创建枚举类型在编译后事实上也是一个类类型而且该类继承自java.lang.Enum类。

我们再来看看反编译Day.class文件://反编译Day.class

final class Day extends Enum

{

//编译器为我们添加的静态的values()方法

public static Day[] values()

{

return (Day[])$VALUES.clone();

}

//编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf方法

public static Day valueOf(String s)

{

return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s);

}

//私有构造函数

private Day(String s, int i)

{

super(s, i);

}

//前面定义的7种枚举实例

public static final Day MONDAY;

public static final Day TUESDAY;

public static final Day WEDNESDAY;

public static final Day THURSDAY;

public static final Day FRIDAY;

public static final Day SATURDAY;

public static final Day SUNDAY;

private static final Day $VALUES[];

static

{

//实例化枚举实例

MONDAY = new Day("MONDAY", 0);

TUESDAY = new Day("TUESDAY", 1);

WEDNESDAY = new Day("WEDNESDAY", 2);

THURSDAY = new Day("THURSDAY", 3);

FRIDAY = new Day("FRIDAY", 4);

SATURDAY = new Day("SATURDAY", 5);

SUNDAY = new Day("SUNDAY", 6);

$VALUES = (new Day[] {

MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY

});

} }

从反编译的代码可以看出:编译器确实帮助我们生成了一个Day类(注意该类是final类型的,将无法被继承)而且该类继承自java.lang.Enum类,该类是一个抽象类(稍后我们会分析该类中的主要方法)

除此之外,编译器还帮助我们生成了7个Day类型的实例对象分别对应枚举中定义的7个日期,这也充分说明了我们前面使用关键字enum定义的Day类型中的每种日期枚举常量也是实实在在的Day实例对象,只不过代表的内容不一样而已。

注意编译器还为我们生成了两个静态方法,分别是values()和 valueOf(),valueOf(Class enumType, String name)方法是根据枚举类的Class对象和枚举名称获取枚举常量

*

枚举的进阶用法

实际上使用关键字enum定义的枚举类,除了不能使用继承(因为编译器会自动为它继承Enum抽象类,而Java只支持单继承,因此枚举类是无法手动实现继承的),可以把enum类当成常规类,也就是说我们可以向enum类中添加方法和变量,甚至是mian方法。

* 向enum类添加方法与自定义构造函数public enum Day2 {

MONDAY("星期一"),

TUESDAY("星期二"),

WEDNESDAY("星期三"),

THURSDAY("星期四"),

FRIDAY("星期五"),

SATURDAY("星期六"),

SUNDAY("星期日");//记住要用分号结束

private String desc;//中文描述

/**

* 私有构造,防止被外部调用

* @param desc

*/

private Day2(String desc){

this.desc=desc;

}

/**

* 定义方法,返回描述,跟常规类的定义没区别

* @return

*/

public String getDesc(){

return desc;

}

public static void main(String[] args){

for (Day2 day:Day2.values()) {

System.out.println("name:"+day.name()+

",desc:"+day.getDesc());

}

}

/**

输出结果:

name:MONDAY,desc:星期一

name:TUESDAY,desc:星期二

name:WEDNESDAY,desc:星期三

name:THURSDAY,desc:星期四

name:FRIDAY,desc:星期五

name:SATURDAY,desc:星期六

name:SUNDAY,desc:星期日

*/

}

但是我们必须注意到,如果打算在enum类中定义方法,务必在声明完枚举实例后使用分号分开,倘若在枚举实例前定义任何方法,编译器都将会报错,无法编译通过,同时即使自定义了构造函数且enum的定义结束,我们也永远无法手动调用构造函数创建枚举实例,毕竟这事只能由编译器执行。

枚举与单例模式/**

* 枚举单例

*/

public enum SingletonEnum {

INSTANCE;

private String name;

public String getName(){

return name;

}

public void setName(String name){

this.name = name;

}

}

Java中单例模式大概有五种:饿汉式、静态内部类、懒汉式、双重校验锁、枚举式。

静态内部类和双重校验锁已经这么优秀了为什么还要有第五种枚举式呢?

因为前面4种都存在一个序列化和反序列化时的安全问题。将单例对象序列化后,在反序列化时会重新创建一个单例对象,违背了单例模式的初衷。而枚举式单例则没有这个问题。

枚举序列化是由jvm保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化时Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,从而保证了枚举实例的唯一性.

Enum类的valueOf方法:public static > T (Class enumType,

String name) {

T result = enumType.enumConstantDirectory().get(name);

if (result != null)

return result;

if (name == null)

throw new NullPointerException("Name is null");

throw new IllegalArgumentException(

"No enum constant " + enumType.getCanonicalName() + "." + name);

}

实际上通过调用enumType(Class对象的引用)的enumConstantDirectory()方法获取到的是一个Map集合,在该集合中存放了以枚举name为key和以枚举实例变量为value的Key&Value数据,因此通过name的值就可以获取到枚举实例

enumConstantDirectory()方法:Map enumConstantDirectory() {

if (enumConstantDirectory == null) {

T[] universe = getEnumConstantsShared();

if (universe == null)

throw new IllegalArgumentException(

getName() + " is not an enum type");

Map m = new HashMap<>(2 * universe.length);

//map存放了当前enum类的所有枚举实例变量,以name为key值

for (T constant : universe)

m.put(((Enum>)constant).name(), constant);

enumConstantDirectory = m;

}

return enumConstantDirectory;

}

private volatile transient Map enumConstantDirectory = null;

到这里我们也就可以看出枚举序列化确实不会重新创建新实例,jvm保证了每个枚举实例变量的唯一性。

通过反射获取构造器并创建枚举 :public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {

Constructor constructor=SingletonEnum.class.getDeclaredConstructor(String.class,int.class);

constructor.setAccessible(true);

//创建枚举

SingletonEnum singleton=constructor.newInstance("otherInstance",9);

}

执行报错,不能使用反射创建枚举类,是为什么呢?在newInstance()方法中找找原因: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);

}

}

//这里判断Modifier.ENUM是不是枚举修饰符,如果是就抛异常

if ((clazz.getModifiers() & Modifier.ENUM) != 0)

throw new IllegalArgumentException("Cannot reflectively create enum objects");

ConstructorAccessor ca = constructorAccessor; // read volatile

if (ca == null) {

ca = acquireConstructorAccessor();

}

("unchecked")

T inst = (T) ca.newInstance(initargs);

return inst;

}

说明了创建枚举实例只有编译器能够做到而已。

结论

显然枚举单例模式确实是很不错的选择,因此我们推荐使用它。

不过由于使用枚举时占用的内存常常是静态变量的两倍还多,因此android官方在内存优化方面给出的建议是尽量避免在android中使用enum。

但是不管如何,关于单例,我们总是应该记住:线程安全,延迟加载,序列化与反序列化安全,反射安全是很重重要的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值