java 枚举源码解析
来源:互联网 发布:做淘宝去哪里找货源 编辑:程序博客网 时间:2024/04/28 20:06
应用场景
枚举通常用来列举一个类型的有限实例集合,我们可以使用常量集来实现,jdk1.5添加了枚举(enum)支持,解决了常量集的一些缺陷
- 常量集中的变量不会必然在指定的范围内
- 常量能够提供的功能很少,难于使用
- 常量意义不明确,没有名字
- 修改或增加枚举值后需要修改的代码多,不便于维护
关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的组件使用。
枚举源码
首先我们定义一个枚举类 Explore.java
public enum Explore { HERE, THERE}
然后编译 javac Explore.java
,反编译javap Explore
,得到反编译的结果:
Compiled from "Explore.java"public final class Explore extends java.lang.Enum<Explore> { public static final Explore HERE; public static final Explore THERE; public static Explore[] values(); public static Explore valueOf(java.lang.String); static {};}
我们看到当我们定义一个枚举,编译器其实是为我们创建了一个继承自Emum的类
- 枚举实例对应新类中的static final 变量
- 该类为 final类型,不可被继承
- 增加了两个方法
- valueOf(String) 从String构造枚举类型
- values() 返回一个由枚举对象构成的数组
- 添加了一个静态初始化器 static{},用来初始化枚举实例,和枚举实例数组,也就是 values()返回数组
Enum类
Enum作为枚举类的公共基类有以下的特点
- 构造器私有(保护)
- 关键域为ordinal来指示枚举对象被声明的顺序,name用来给出声明时的合理描述,序列化时只输出name属性,用valueOf(String)通过名字来进行反序列化
- 该类中的valueOf()方法和编译器生成的valueOf方法签名不同
- 禁止了基础的序列化方法,调用readObject()和writeObject()时抛出异常
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { 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; } public final boolean equals(Object other) { return this == other; } public final int hashCode() { return super.hashCode(); } protected final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } public final int compareTo(E o) { Enum other = (Enum) o; Enum self = this; if (self.getClass() != other.getClass() && // optimization self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); return self.ordinal - other.ordinal; } public final Class<E> getDeclaringClass() { Class clazz = getClass(); Class zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? clazz : zuper; } public static <T extends Enum<T>> T valueOf(Class<T> 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); } protected final void finalize() { } 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"); }}
序列化
序列化和反序列化保证了每一个枚举类型极其定义的枚举变量在JVM中都是唯一的
- 在序列化的时候java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候通过 java.lang.Enum的valueOf方法来根据名字查找枚举对象
- 通过私有化并且直接抛出异常来禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,保证了序列化机制不会被定制
- 尝试从调用enumType这个Class对象的enumConstantDirectory()方法返回的map中获取名字为name的枚举对象,如果不存在就会抛出异常。再进一步跟到enumConstantDirectory()方法,就会发现到最后会以反射的方式调用enumType这个类型的values()静态方法
enumConstants = (T[])values.invoke(null);
,也就是上面我们看到的编译器为我们创建的那个方法,然后用返回结果填充enumType这个Class对象中的enumConstantDirectory属性。由于每次反序列化都是获取静态数组中的对象的引用,所以不会创建新的对象,从而保证了单例模式
NOTE
在了解了Java如何处理枚举的定义以及序列化和反序列化枚举类型之后,我们就需要在系统或者类库升级时,对其中定义的枚举类型多加注意,为了保持代码上的兼容性,如果我们定义的枚举类型有可能会被序列化保存(放到文件中、保存到数据库中,进入分布式内存缓存中),那么我们是不能够删除原来枚举类型中定义的任何枚举对象的,否则程序在运行过程中,JVM就会抱怨找不到与某个名字对应的枚举对象了。另外,在远程方法调用过程中,如果我们发布的客户端接口返回值中使用了枚举类型,那么服务端在升级过程中就需要特别注意。如果在接口的返回结果的枚举类型中添加了新的枚举值,那就会导致仍然在使用老的客户端的那些应用出现调用失败的情况。因此,针对以上两种情况,应该尽量避免使用枚举,如果实在要用,也需要仔细设计,因为一旦用了枚举,有可能会给后期维护带来隐患。
public static <T extends Enum<T>> T valueOf(Class<T> 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); }
一个测试序列化一致性的例子
本例中,我们通过socket将枚举类型写入到网络中,然后在另一端用readObject()方法读取,最后判断反序列化后的对象与本地枚举对象的相等性
- 如果 == 返回true,说明并没有创建新的对象,确实保证了单例模式
- 如果 equals()返回true,说明反序列化后只是语义相同,没有保证单例模式
// 枚举类型定义public enum WeekDayEnum { Mon(1), Tue(2), Wed(3), Thu(4), Fri(5), Sat(6), Sun(7); private int index; WeekDayEnum(int idx) { this.index = idx; } public int getIndex() { return index; }} // 客户端代码public class EnumerationClient { public static void main(String... args) throws UnknownHostException, IOException { Socket socket = new Socket(); socket.connect(new InetSocketAddress("127.0.0.1", 8999)); OutputStream os = socket.getOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(WeekDayEnum.Fri); oos.close(); os.close(); socket.close(); }} // 服务器端代码public class EnumerationServer { public static void main(String... args) throws IOException, ClassNotFoundException { ServerSocket server = new ServerSocket(8999); Socket socket = server.accept(); InputStream is = socket.getInputStream(); ObjectInputStream ois = new ObjectInputStream(is); WeekDayEnum day = (WeekDayEnum) ois.readObject(); if (day == WeekDayEnum.Fri) { System.out.println("client Friday enum value is same as server's"); } else if (day.equals(WeekDayEnum.Fri)) { System.out.println("client Friday enum value is equal to server's"); } else { System.out.println("client Friday enum value is not same as server's"); } ois.close(); is.close(); socket.close(); }}
线程安全
由于枚举类型的对象是static,并且在static块中初始化,所以由JVM的ClassLoader机制保证了线程安全性。
枚举的常见用法
主要API
public class TestEnum { public static void main(String[] args) { Fruit[] values = Fruit.values(); for (Fruit fruit : values) { System.out.println(fruit); printEnum(fruit); } } public enum Fruit { APPLE, ORANGE, WATERMELON } public static void printEnum(Fruit fruit) { System.out.println(fruit + " ordinal:" + fruit.ordinal()); System.out.println("compare to apple" + fruit.compareTo(Fruit.APPLE)); System.out.println(fruit == Fruit.ORANGE); System.out.println(fruit.getDeclaringClass()); System.out.println(fruit.name()); }}
switch
java的switch语法,是通过jvm的tableswitch和lookupswitch两个指令实现。java编译器为switch语句编译成一个局部变量数组,每个case对应一个数组的索引,指令的执行是通过不同的数组索引找到不同的入口指令。所以原则上switch…case只能处理int型的变量。
enum能用在switch语句中,也是一个语法糖,我们知道所有枚举类的父类Enum中有一个private final int ordinal;,java编译器检测到switch语句中变量是一个枚举类,则会利用之前枚举类的ordinal属性,编译一个局部变量数组,后续在进行case分支比较的时候,就是简单通过tableswitch或lookupswitch指令来进行跳转,需要注意的一点:这个局部变量数组的构建过程是在编译器在编译阶段完成的。
注意在switch中不需要用类型名来指定枚举对象(Single.RED),而是直接用类型名RED,在此时case语句不需要类限定前缀,完全是java编译器的限制(编译器是不需要枚举类的前缀,只需要枚举类编译的static int[] $SWITCH_TABLE
enum Signal { GREEN, YELLOW, RED}public class TrafficLight { Signal color = Signal.RED; public void change() { switch (color) { case RED : color = Signal.GREEN; break; case YELLOW : color = Signal.RED; break; case GREEN : color = Signal.YELLOW; break; } }}
像正常类一样扩展枚举类型
- 定义私有构造函数
- 自定义普通方法
- 覆盖Enum中的方法
- 实现接口方法
由于java不支持多继承,所有枚举类型都继承自java.lang.Enum,所以enum类型只能实现接口,而不能继承类
public enum Color implements Behaviour { 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.index == index) { return c.name; } } return null; } // 接口方法 @Override public void print() { System.out.println(this.index + ":" + this.name); } // 覆盖方法 @Override public String toString() { return this.index + "_" + this.name; }}interface Behaviour { void print();}
接口组织枚举类型
用接口来组织多层枚举
public class FoodTest { public static void main(String[] args) { Food f = Food.Coffee.BLACK_COFFEE; System.out.println(f); }}interface Food { enum Coffee implements Food { BLACK_COFFEE, DECAF_COFFEE, LATTE, CAPPUCCINO } enum Dessert implements Food { FRUIT, CAKE, GELATO } int i = 1;}
枚举专用集合类
java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的key是enum类型,而value则可以是任意类型。集合类利用枚举类型的ordinal域来进行组织,用法和普通Map,Set类似,只是类型限定为Enum类型。
public enum Weeks { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURADAY, SUNDAY} public class EnumSetTest { public static void main(String[] args) { EnumSet<Weeks> week = EnumSet.noneOf(Weeks.class); week.add(Weeks.MONDAY); System.out.println("EnumSet中的元素:" + week); week.remove(Weeks.MONDAY); System.out.println("EnumSet中的元素:" + week); week.addAll(EnumSet.complementOf(week)); System.out.println("EnumSet中的元素:" + week); week.removeAll(EnumSet.range(Weeks.MONDAY, Weeks.THURSDAY)); System.out.println("EnumSet中的元素:" + week); }} public enum Course { ONE, TWO, THREE} public class EnumMapTest { public static void main(String[] args) { EnumMap<Course, String> map = new EnumMap<Course, String>(Course.class); map.put(Course.ONE, "语文"); map.put(Course.ONE, "政治"); map.put(Course.TWO, "数学"); map.put(Course.THREE, "英语"); for (Entry<Course, String> entry : map.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); } }}
- java 枚举源码解析
- Java枚举解析
- Java 枚举变量解析
- java枚举解析
- Java枚举全解析
- java枚举初解析
- java 枚举实例解析
- UniversalImageLoader 源码解析 -1.enam(枚举)使用
- java中的枚举类型解析
- JAVA源码解析-String源码
- JAVA源码解析-ArrayList源码
- JAVA源码解析-LinkedList源码
- Java源码解析-DualPivotQuicksort
- java FutureTask 源码解析
- java Collection源码解析
- Java HashMap 源码解析
- Java TreeMap 源码解析
- Java TreeMap 源码解析
- android SQLite基本操作
- js的call() ,apply() 两种方法的区别和用法,最白话文的解释,让枯燥滚粗!
- Android studio配置干活
- sublime text3 linux下中文支持
- 安卓SQLite基础入门
- java 枚举源码解析
- Android——FragmentTabHost-快速实现底部导航模式的解决方案
- Java - 蓝桥杯 - 历届试题 核桃的数量
- WebSphere MQ Jms 连接
- 安卓四大组件之ContentProvider
- bug吐槽JSON格式不兼容
- DataTable在内存中的使用
- Loader
- DrawerLayout