枚举
wiki
- Java的枚举类型用法介绍
- 深入理解Java枚举类型(enum)
- 为什么要用enum?
学习计划
- 枚举的基本用法;
- 枚举的实现原理;
- 枚举的用处
- 单例
- 序列化
理解枚举
为什么使用枚举
在没有枚举之前,实现枚举的方式使用public static final
定义常量
public class Season {
public static final int SPRING = 1;
public static final int SUMMER = 2;
public static final int AUTUMN = 3;
public static final int WINTER = 4;
}
这种方法在类型安全性和可读性方面比较差。
-
类型安全性
在下面的代码中,方法
getChineseSeason
中应该传入枚举中的值,但是传入其他的值也是可以的,不能进行类型检查,在后面的运行中可能会出现问题。private String getChineseSeason(int season){ StringBuffer result = new StringBuffer(); switch(season){ case Season.SPRING : result.append("春天"); break; case Season.SUMMER : result.append("夏天"); break; case Season.AUTUMN : result.append("秋天"); break; case Season.WINTER : result.append("冬天"); break; default : result.append("地球没有的季节"); break; } return result.toString(); } public void doSomething(){ System.out.println(this.getChineseSeason(Season.SPRING));//这是正常的场景 System.out.println(this.getChineseSeason(5));//这个却是不正常的场景,这就导致了类型不安全问题 }
代码可读性:在传入枚举值时,无法知道枚举值表示的意义。
枚举的基本用法
用法一:常量
public enum Color {
RED, GREEN, BLANK, YELLOW
}
用法二:Switch
public class EnumTest {
public static void main(String[] args)
{
Season curSeason = Season.SPRING;
switch(curSeason){
case SPRING:
curSeason = Season.SUMMER;
break;
case SUMMER:
curSeason = Season.AUTUMN;
break;
case AUTUMN:
curSeason = Season.WINTER;
break;
case WINTER:
curSeason = Season.SPRING;
break;
}
}
}
Switch和enum的实现原理
EnumTes
编译后生成两个文件,分别是EnumTest.class
和EnumTest$1.class
,然后使用Jad对两个文件进行反编译。
首先查看EnumTest.class
编译后的文件
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumTest.java
public class EnumTest
{
public EnumTest()
{
}
public static void main(String args[])
{
Season curSeason = Season.SPRING;
static class _cls1
{
static final int $SwitchMap$Season[];
static
{
$SwitchMap$Season = new int[Season.values().length];
try
{
$SwitchMap$Season[Season.SPRING.ordinal()] = 1;
}
catch(NoSuchFieldError nosuchfielderror) { }
try
{
$SwitchMap$Season[Season.SUMMER.ordinal()] = 2;
}
catch(NoSuchFieldError nosuchfielderror1) { }
try
{
$SwitchMap$Season[Season.AUTUMN.ordinal()] = 3;
}
catch(NoSuchFieldError nosuchfielderror2) { }
try
{
$SwitchMap$Season[Season.WINTER.ordinal()] = 4;
}
catch(NoSuchFieldError nosuchfielderror3) { }
}
}
switch(_cls1..SwitchMap.Season[curSeason.ordinal()])
{
case 1: // '\001'
curSeason = Season.SUMMER;
break;
case 2: // '\002'
curSeason = Season.AUTUMN;
break;
case 3: // '\003'
curSeason = Season.WINTER;
break;
case 4: // '\004'
curSeason = Season.SPRING;
break;
}
}
}
- Switch生成了一个内部类
_cls1
,里面有int数组$SwitchMap$Season
; -
$SwitchMap$Season[Season.SPRING.ordinal()] = 1;
,因为Season每一个枚举值都是一个Season对象,都有一个ordinal值(和枚举值的顺序相同,Season.SPRING.ordinal()=0,Season.SUMMER.ordinal()=1),将switch中出现的每一个枚举值都放入数组中; - 在switch进行判断时,取得是
$SwitchMap$Season
中的值。
用法三:向枚举中添加新方法
public enum Color {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
// 普通方法
public static String getName(int index) {
for (Color c : Color.values()) {
if (c.getIndex() == index) {
return c.name;
}
}
return null;
}
// get set 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
通过反编译查看枚举的本质
-
源代码
public enum Season { SPRING, SUMMER, AUTUMN, WINTER; }
-
使用Jad反编译
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) // Source File Name: Season.java public final class Season extends Enum { public static Season[] values() { return (Season[])$VALUES.clone(); } public static Season valueOf(String name) { return (Season)Enum.valueOf(Season, name); } private Season(String s, int i) { super(s, i); } public static final Season SPRING; public static final Season SUMMER; public static final Season AUTUMN; public static final Season WINTER; private static final Season $VALUES[]; static { SPRING = new Season("SPRING", 0); SUMMER = new Season("SUMMER", 1); AUTUMN = new Season("AUTUMN", 2); WINTER = new Season("WINTER", 3); $VALUES = (new Season[] { SPRING, SUMMER, AUTUMN, WINTER }); } }
对编译后的代码进行分析
- 枚举类都是继承了
Enum
类; - 枚举类是一个final类,说明无法被继承;
- 枚举中的每一个枚举值都是一个枚举类的对象;
- 在创建枚举类的时候,使用的是静态变量和静态块。当一个Java类第一次被真正使用到的时候静态资源被初始化,Java类的加载和初始化过程都是线程安全的。所以,创建一个enum类型是线程安全的。
- 枚举类都是继承了
枚举与序列化
序列化
在序列化中,在方法ObjectOutputStream.writeObject0
中判断对象是否为Enum类型,然后调用ObjectOutputStream.writeEnum
private void writeEnum(Enum> en,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
bout.writeByte(TC_ENUM);
ObjectStreamClass sdesc = desc.getSuperDesc();
writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false);
handles.assign(unshared ? null : en);
writeString(en.name(), false);
}
可以看出,枚举类进行序列化时调用writeString()
写入枚举值的name;
反序列化
在对Enum进行反序列化时,方法ObjectInputStream.readObject0
在判断时enum后,调用ObjectInputStream.readEnum
方法,通过调用Enum.valueOf
进行反序列化。
public static > T valueOf(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);
}
valueOf
方法调用Enum.enumConstantDirectory()
,通过反射调用enum类的方法values
,在枚举的本质一章中可以看到,values
方法可以获取所有的枚举值。
例子
枚举类Season具有一个私有变量,如果在对Season.SPRING
执行序列化后,然后将SPRING(1)
改为SPRING(5)
,则Season.SPRING.getVal
得到的是5。原因是序列化时只是写入枚举值的name,在反序列化时通过name获得对于的枚举实例,而这个实例是根据最新的类实例化得到的。
enum Season
{
SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
private int val;
private Season(int val){
this.val = val;
}
public int getVal()
{
return this.val;
}
}
枚举与单例
因为枚举可以保证线程安全和序列化安全性,并且实现比较简洁,适合用于单例模式
enum SingletonDemo{
INSTANCE;
public void otherMethods(){
System.out.println("Something");
}
}