优雅的可选项定义
背景
JDK1.5前没有枚举时,只能简单常量定义
Sample1 2 3
| public static final int HIGH = 0; public static final int MEDIUN = 1; public static final int LOW = 2;
|
常量只是一个值,缺乏组织性,也无从检查合法性,比如方法传入未定义的3
枚举引入了类型系统,直接限定了范围。从基本值变为对象,提供了更优雅更强大的功能
Sample1 2 3
| public enum Level{ HIGH, MEDIUM, LOW }
|
使用事项
- 枚举非常适合作为参数,限定了哪些值合法,即使扩展变动后,新值由于还没被使用,不会造成影响
- 枚举不适合作为返回值,因为扩展变动后,下游不会知道,如果返回了新值可能出现异常
枚举的本质
枚举类型隐式继承了java.lang.Enum
,所以不能再继承其他类
内部有两个变量:声明时的名字和声明时位置,两者都是final
不可以重写的,行为永远是返回声明时的值
而toString
虽然默认也返回名字,但是可以被重写提供更加用户友好的名字
Enum.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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; }
public String toString() { return name; } }
|
泛型嵌套Enum<E extends Enum<E>>
则枚举Level隐式继承了Enum,即应当为
Sample1 2 3
| public class Level extends Enum<Level> { }
|
同时泛型参数要求必须为枚举,添加约束E extends Enum<E>
单例
枚举变量是单例,并且是线程安全
- 枚举变量在编译后可以观察到内部都是
static final
的实例,类加载时创建
- 反序列化是通过内部
valueOf
,不会新建
枚举实现了Serializable
接口,但是有特殊的一套机制
序列化时只写名字,反序列化时禁止自定义行为,而是通过valueOf
来根据名字查找
如果服务端提供的接口返回枚举类型,如果服务端增加新变量,老客户端会报错无法找到,有一定兼容性问题
Enum.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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); }
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.java1 2 3
| protected final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); }
|
- 判定相等
equals
直接比较引用,因此枚举可以直接==
比较
Enum.java1 2 3
| public final boolean equals(Object other) { return this==other; }
|
操作方法
除了父类Enum中的方法,编译器自动在枚举上插入静态方法,values()和valueOf()
Sample1 2
| System.out.println(Arrays.toString(Level.values())); System.out.println(Level.HIGH == Level.valueOf("HIGH"));
|
比较
枚举项之间比较序数
Enum.java1 2 3 4 5 6 7 8
| public final int compareTo(E o) { Enum<?> other = (Enum<?>)o; Enum<E> self = this; if (self.getClass() != other.getClass() && self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); return self.ordinal - other.ordinal; }
|
维护额外成员
最常规的枚举只有声明名字和序号,可以给枚举项绑定其他成员变量以及提供方法
声明其他成员后,枚举项末尾要带分号
注意枚举类中不能定义public
构造函数,因为不能在外部通过new
创建枚举变量,所有变量只能在内部声明,构造方法默认是private
的
Sample1 2 3 4 5 6 7 8 9 10 11 12 13
| enum Level{ HIGH(1),MEDIUM(2),LOW(3);
private int val;
Level(int val) { this.val = val; }
public int getVal() { return val; } }
|
接口/抽象方法
枚举虽然不能再继承其他类,但是可以实现接口,每个成员都是匿名实现类可以自定义,非常适合用来实现策略模式
Sample1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public interface Evaluable{ boolean judge(int val); }
public enum Level implements Evaluable{ HIGH { @Override public boolean judge(int val) { return val >= 100; } }, MEDIUM { @Override public boolean judge(int val) { return val >= 10 && val < 100; } }, LOW { @Override public boolean judge(int val) { return val < 10; } }; }
|
抽象方法也可以,原理类似
Sample1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public enum Level implements Evaluable{ HIGH { @Override public boolean judge(int val) { return val >= 100; } }, MEDIUM { @Override public boolean judge(int val) { return val >= 10 && val < 100; } }, LOW { @Override public boolean judge(int val) { return val < 10; } };
public abstract boolean judge(int val); }
|
通过枚举项获取枚举类名,正因为枚举项自身可以有匿名实现体,直接getClass可能不能正确获取,因此需要考虑父类
Enum.java1 2 3 4 5 6
| @SuppressWarnings("unchecked") public final Class<E> getDeclaringClass() { Class<?> clazz = getClass(); Class<?> zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper; }
|
总是应当使用getDeclaringClass确保正确
Sample1 2
| System.out.println(Level.HIGH.getClass()); System.out.println(Level.HIGH.getDeclaringClass());
|
枚举相关集合
EnumMap由枚举类型作为key建立的map, 枚举数量有限,底层为固定大小数组存储,相比通用map更高效
1 2 3 4
| EnumMap<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class); map.put(DayOfWeek.MONDAY, "1"); map.put(DayOfWeek.FRIDAY, "5"); System.out.println(map);
|
EnumSet有部分枚举成员构成集合,可以进行集合操作
1 2 3
| EnumSet<DayOfWeek> nonWorkDays = EnumSet.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY); EnumSet<DayOfWeek> workDays = EnumSet.complementOf(nonWorkDays); System.out.println(workDays);
|