不用Enum的基本写法
|
|
缺点:
- 类型不安全,可以给currency赋值任何数,而不是限制在CurrentyDenom中的一种。编译器无法检查。
- 无意义的输出,输出NICKLE,只会得到5,而没有NICKLE。
- 需要以CurrencyDenom.PENNY的形式才能访问钱币值(尽管可以static import的方式解决)。
用Enum解决上面的问题
|
|
- 使用Currency作为变量的类型,严格限制变量只能取定义的几个值,type-safe。
- toString()会输出PENNY等有意义的值。
- 可以直接访问PENNY。
Enum的基本解释
- java关键字
- 类似于Class或Interface
- enum对象都继承自java.lang.Enum
- enum中定义的常量默认是static final的
- 可以用于switch语句
Enum的关键点
基本特性
type safe: 必须引用定义的常量值,不可以随意赋值,即使直观上一样,例如硬币的面值,1直观上也可以,但是没有在enum中定义常量,就是编译不能通过。编译器保证type-safe。
1234public enum Currency {PENNY, NICKLE, DIME, QUARTER };Currency coin = Currency.PENNY;coin = 1; //compilation error类似于Class或Interface,可以定义构造器(私有)、方法和域。
可以给常量以参数,等于是调用了有参构造器
12345public enum Currency {PENNY(1), NICKLE(5), DIME(10), QUARTER(25);private int value;private Currency(int value) { this.value = value; }};enum常量默认是static final,不能更改
- 因为是常量,所以可以用
==
直接比较两个enum对象1234Currency usCoin = Currency.DIME;if(usCoin == Currency.DIME){System.out.println("enum in java can be compared using ==");}
高级特性
可以重写java.lang.Enum的方法,例如
toString()
等。继承的方法有:12toString();// 貌似我查了一下源码,可重写的方法只有toString()了。注意,下面几个方法在JDK1.8的版本中,是不能继承的。
123456789101112131415161718192021222324252627282930// 单例,所以所有子类维持固定的语义public final boolean equals(Object other) {return this==other;}// 单例,与equals维持同样的语义即可public final int hashCode() {return super.hashCode();}// 不支持clone,很明显,单例!// 另外,因为Object的clone是protected,子类不能扩大父类的限制符,所以为protectedprotected final Object clone() {throw new CloneNotSupportedException();}// 定义顺序,不允许更改public final int ordinal() {return ordinal;}// 比较的是ordinal!public final int compareTo(E o) {Enum<?> other = (Enum<?>)o;Enum<E> self = this;if (self.getClass() != other.getClass() && // optimizationself.getDeclaringClass() != other.getDeclaringClass())throw new ClassCastException();return self.ordinal - other.ordinal;}// 不允许有finalized方法protected final void finalize() { }编译器生成的values()静态方法,返回enum对象的数组。 顺序与定义顺序相同。
123for(Currency coin: Currency.values()){System.out.println("coin: " + coin);}java.lang.Enum自带静态方法
valueOf()
,当然可以直接用于子类。
(为什么valueOf在超类可以定义,而values()则要由编译器生成呢??)ordinal()方法: 返回枚举值在枚举类种的顺序。这个顺序根据枚举值声明的顺序而定
1Currency.DIME // 返回3EnumSet和EnumMap是支持Enum存储的高效的集合类,应该尽可能使用它们。
EnumSet提供一些工厂方法,创建存储enum实例的集合,例如EnumSet.of()
。它的存储方式与普通的set不同。简单一点:当小于64个实例的时候,使用RegularEnumSet
,而大于64个实例的时候,使用JumboEnumSet
。(两者的不同点)enum可以实现接口。默认已经实现了Serializable和Comparable接口。
123456789public enum Currency implements Runnable{PENNY(1), NICKLE(5), DIME(10), QUARTER(25);private int value;............public void run() {System.out.println("Enum in Java implement interfaces");}}可以在enum中定义抽象方法,但是定义常量的时候必须提供实现。(在enum实现策略模式中很有用)
1234567891011121314151617181920212223242526272829303132333435public enum Currency {PENNY(1) {public String color() {return "copper";}},NICKLE(5) {public String color() {return "bronze";}},DIME(10) {public String color() {return "silver";}},QUARTER(25) {public String color() {return "silver";}};private int value;public abstract String color();private Currency(int value) {this.value = value;}}System.out.println("Color: " + Currency.DIME.color());enum的默认序列化读取方式被禁用
12345678private 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");}convert String to Enum:
valueOf()
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
- 静态方法
- 需要给定enum类名和常量的名字,返回enum的某一个常量实例。如果找不到这个常量实例,则抛出
IllegalArgumentException
。
convert Enum to String:
name()
java.lang.Enum中的final方法,返回定义时的常量名字,与toString的区别在于toString()
默认返回name,但是可以被子类重写,但是name()
不可以重写,它一定返回exact defined name。
Enum使用案例
单例模式
12345678910111213141516171819202122232425262728293031/*** Created by paranoidq on 16/1/16.*/public enum SingletonWithEnum {RED(1),BLUE(2),GREEN(3);/*** 私有变量*/private int nCode;SingletonWithEnum(int nCode) {this.nCode = nCode;}public String toString() {return String.valueOf(this.nCode);}/*** Other methods*/public void otherMethods() {System.out.println("Other methods");}}策略模式
12345678910public class Match {private static final Logger logger = LoggerFactory.getLogger(Match.class); public static void main(String args[]) {Player ctx = new Player(Strategy.T20);ctx.play();ctx.setStrategy(Strategy.ONE_DAY);ctx.play();ctx.setStrategy(Strategy.TEST);ctx.play();}}123456789101112class Player{private Strategy battingStrategy;public Player(Strategy battingStrategy){this.battingStrategy = battingStrategy;}public void setStrategy(Strategy newStrategy){this.battingStrategy = newStrategy;}public void play(){battingStrategy.play();}}12345678910111213141516171819202122232425enum Strategy {/* Make sure to score quickly on T20 games */T20 {public void play() {System.out.printf("In %s, If it's in the V, make sure it goes to tree %n", name());}},/* Make a balance between attach and defence in One day */ONE_DAY {public void play() {System.out.printf("In %s, Push it for Single %n", name());}},/* Test match is all about occupying the crease and grinding opposition */ TEST {public void play() {System.out.printf("In %s, Grind them hard %n", name());}};public void play() {System.out.printf("In Cricket, Play as per Merit of Ball %n");}}缺陷是:每次添加策略都需要更改enum类,违背了开闭原则。
参考:Strategy pattern using enum in java用Enum替代String或int常量表示的固定值,如ON/OFF等
- 实现状态机State Machine
参考:Java secret using enum as state machine
Enum bytecode解释
|
|
对应的bytecode:
|
|
注意main函数的第0行bytecode。Direction实际上就是一个类(在本例中是inner class),并且这个类有一个静态域EAST,而这个EAST还是一个Direction对象。这也解释了再定义一些Enum实例的时候需要用括号传递参数,因为实际上实在传递构造函数的参数!
Direction的bytecode如下:
实际上,我们可以自己实现,只是Java帮我们做了这些工作,类似于这样:
Direction继承了java.lang.Enum类,从而从这个类中继承了一些方法和域,例如: toString()
等。
参考资料
Enum examples in java
Java 1.5 Explained - Enum
What does EnumSet really mean