枚举
枚举某种程度上也是一种语法糖。枚举主要是代替过多的public static final修饰的全局常量。
枚举快速开始
搞个demo看看:
public enum MyEnum {
enum1("123");
String name;
MyEnum(String name) {
this.name = name;
}
public static void main(String[] args) {
System.out.println(MyEnum.enum1.name);
}
}
输出“123”。要了解“语义”,不错的办法是看看字节码,javap搞一下:
透过字节码看语义
public final class entity.MyEnum extends java.lang.Enum {
public static final entity.MyEnum enum1;
java.lang.String name;
public static entity.MyEnum[] values();
public static entity.MyEnum valueOf(java.lang.String);
static {};
Code:
0: new #4 // class entity/MyEnum
3: dup
4: ldc #11 // String enum1
6: iconst_0
7: ldc #12 // String 123
9: invokespecial #13 // Method "":(Ljava/lang/String;ILjava/lang/String;)V
12: putstatic #9 // Field enum1:Lentity/MyEnum;
15: iconst_1
16: anewarray #4 // class entity/MyEnum
19: dup
20: iconst_0
21: getstatic #9 // Field enum1:Lentity/MyEnum;
24: aastore
25: putstatic #1 // Field $VALUES:[Lentity/MyEnum;
28: return
}
我们发现:我们的枚举被final修饰了,并且继承自Enum,所有的枚举类实例都被public static final
修饰,还多了俩方法:values和valueOf,猜一下:是因为继承Enum的原因,还是编译器?另外有一个static代码块。
那我们现在可以知道很多事情了:
枚举与继承
- 枚举是无法继承的。因为它们已经继承了Enum,而Java是单继承的。
- 枚举是无法被继承的。因为它们自己被final修饰,因此class类不能继承枚举
当然枚举是可以实现接口的
下面看一下父类Enum
Enum:枚举的父类
public abstract class Enum
implements Comparable, Serializable {
// name 就是你Enum实例对象的名字,在上述demo中是enum1
private final String name;
public final String name() {
return name;
}
// 索引,该对象在该枚举类是第几个对象
private final int ordinal;
public final int ordinal() {
return ordinal;
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
// 唯一可以重写的方法
public String toString() {
return name;
}
// ---------------------------------------------
// 不可clone
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
// ---------------------------------------------
// 重写compareTo方法,默认是根据ordinal比较
public final int compareTo(E o) {
Enum other = (Enum)o;
Enum self = this;
return self.ordinal - other.ordinal;
}
public static T valueOf(Class enumType,String name) {
T result = enumType.enumConstantDirectory().get(name);
return result;
}
// ---------------限制序列化------------------------------
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
}
从enum也可以看出枚举有如下特点:
- 限制了序列化
- 每个枚举实例都有自己的名字和索引,索引从0开始
静态代码块
观察static字节码,会生成一个String实例对象并初始化enum1实例。
那枚举能自定义static代码块吗?依然可以,在上述初始化流程结束之后,再执行你自定义的static代码块。
编译器提供的两个value方法
这两个方法都是编译器提供的,因此你在enum中找不到values方法。虽然Enum有valueOf方法,但编译器提供的valueOf方法内部会调用Enum的valueOf方法。
values()
方法的作用就是获取枚举类中的所有变量,并作为数组返回valueOf(String name)
方法的作用类似根据名称获取枚举变量MyEnum[] myEnums = MyEnum.values();
MyEnum myEnum = MyEnum.valueOf("enum1")
valueOf(Class enumType, String name)
方法是根据枚举类的Class对象和枚举名称获取枚举常量,这个方法由Enum类提供,本质是对map的get,是一张HashMap,key为String,存储在Class对象中。而valueOf(String name)
是编译器生成的
枚举的构造方法
默认就是private修饰的,也不能被别的关键字修饰。
因为枚举要生成的对象已经被确定了,不允许由外部再生成实例对象。
枚举与反射
能否通过反射调用枚举的私有构造方法呢?不可以的。
newInstance方法中有如下判断:
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
因此即便是反射,也无法调用枚举的构造方法。因此该构造方法只有枚举实例初始化时才能调用。
枚举与JDK序列化
枚举序列化是由jvm保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化时Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。
在writeObject中有这样一个判断
if (obj instanceof Enum) {
writeEnum((Enum) obj, desc, unshared);
枚举实现单例模式
一般的单例模式仍然会被反射/序列化破坏,而枚举同时限制了这两个破坏单例的手段。
public class Singleton {
// 构造函数私有化
private Singleton(){
}
public static enum SingletonEnum {
SINGLETON;
private Singleton instance = null;
private SingletonEnum(){
instance = new Singleton();
}
public Singleton getInstance(){
return instance;
}
}
// 对外提供获取实例实例的方法
public static User getInstance(){
return SingletonEnum.SINGLETON.getInstnce();
}
}
枚举与class的比较
枚举更像是一个抽象类,但是:
- 不允许有继承关系
- 所有枚举的实例必须定义在枚举中,无法通过别的方式生成
枚举的抽象方法需要每个实例去实现,demo如下:
public enum MyEnum {
enum1{
@Override
public String dosth() {
return null;
}
};
public abstract String dosth();
EnumMap
EnumMap是枚举的专属map,效率比通常的HashMap更高。
EnumMap只能接收同一枚举类型的实例作为键值且不能为null,通过ordinal方法(声明枚举对象的顺序的索引)获取枚举key对应的数组下标。
在使用上EnumMap和HashMap区别不大,只是key全部为枚举类型的实例,看下get方法:
public V get(Object key) {
// 保证key是枚举的实例
return (isValidKey(key) ?
// 下标为 key 的 ordinal索引值
unmaskNull(vals[((Enum)key).ordinal()]) : null);
}
更多的就不介绍了,源码并不复杂,感兴趣的可以自己去看一下。
当然还有EnumSet,这篇文章的分析很到位了
参考文档
Java枚举类型(enum)详解