JavaSE——Java枚举


枚举总结: 


本文根据Effective Java上关于枚举的知识点,自己翻译总结了关于枚举的知识点,作为张孝祥老师Java高新技术的复习和补充

1. C/C++/C# 中 int enum pattern and string enum pattern有什么缺点?

a. int enum pattern: 

a.1类型安全无法保证, 因为类型相同, 如果将水果类的元素传入一个需要蔬菜类的函数,系统不会报错

a.2 书写和实用很不方便,没有提供域名表示哪个类的变量,定义枚举内的变量要自己书写前缀

a.3 int enum 是 compile time constants 如果枚举数值变化了,需要再编译一边给客户端才会有预期的结果

a.4 int enum 不可以被直观的打印显示: print值只是一个int的数

a.5 很难得到一组int enum 枚举的个数,遍历一个int enum pattern就更难了

b. String enum pattern:

b.1 虽然可以打印出名称,但是其不同是基于String比较的

b.2 string enum可能误导初级程序员用hardcode的形式而非fieldname去写客户端程序,编译倒是能通过但是运行时会出错

2. 为什么说java中的enum就可以解决int/String enum的问题?

a. java中enum是一个完整的类,有一切基于类的可实现的自定义成员及方法

b. enum的不可改变的性质本质不是源于static final int之类的定义,而是源于enum类中的构造函数是private的,不被暴露在外界的

c. enum的枚举成员都是以一个对象的形式存在: 

c.1 可以给其按照固定的构造函数形式在定义时赋值(应该赋予final的变量值,因为enum就是为了描述不可改变的量的);

c.2 可以根据操作定义get性质函数得到成员自身的各种表示属性的成员变量的数值;

c.3 可以调用其抽象父类Enum中提供的方法来实现多种功能

d. 根据c.3其多种应用是:

d.1 toString返回 常量名称

d.2 valueOf根据名称返回某个枚举元素

d.3 values返回 所有枚举元素数组(增强for遍历数组,得到length长度)

d.4 ordinal 返回枚举元素在列表中的位置序号(慎用,名称--序号对应关系会随列表顺序变化而变化)

3. 基于switch的方法实现枚举元素中的特征成员方法有什么局限性,如何解决?

public enum Operation {
	PLUS,MINUS;
	double apply(double x,double y){
		switch(this){
			case PLUS: return x+y;
			case MINUS: return x-y;
		}
	throw new AssertionError("No such Operation"+this); //词句不加会有编译错误,因为程序逻辑上是可以执行到的
	}
}

a. 缺点是: 新添加一个枚举元素比如TIMES(*),如果忘记修改apply里面的switch方法,运行就抛出throw new AssertionError, 运行时才会被发现

b.解决方法: abstract 共性实施方法, 枚举元素列表上代码块根据需求实现(这种方法可以把可能的错误限制在编译时期,如果没有复写,编译器会提示)

	public enum Operation {
		PLUS{double apply(double x,double y){return x+y;}}, //示例对象复写抽象方法
		MINUS{double apply(double x,double y){return x-y;}};//示例对象复写抽象类方法
		abstract double apply(double x,double y);
	}

4. switch方法是有好处的,它可以方便代码共用,让Enum内部函数实施很简洁,而Enum实例复写abstract方法会让一样性质的枚举类型重复复写,代码臃肿, 有什么更好的解决办法吗?switch还有什么可维护性的不方便吗?

(例:周1-5 工作8小时,overtime 部分1.5工资;周末 anytime is overtime 1.5工资)

public enum PayRollDay1 {
	MON,TUE,WED,THU,FRI,SAT,SUN;
	private static final int HOUR_PER_SHIFT=8;
	public double pay(double hoursWorked,double payRate){
	double basePay=hoursWorked*payRate;
	double overtimePay;
	switch(this){
		case SAT:case SUN: //假日选项overtime支付
			overtimePay=hoursWorked*payRate/2;
			break;//选中跳出
		default://weekday overtime支付
			overtimePay=hoursWorked<=HOUR_PER_SHIFT?0:hoursWorked*payRate/2;
		}
	return basePay+overtimePay;
	}
}

d. 代码缺点: 和a中一样,如果要新加一个枚举元素,需要注意在修改switch中代码;更重要的一点是,这些枚举元素的pay方法不一定是不变的,有可能,正好赶上哪天weekday是个假期,工资就按weekends的付了,或者说国庆节还是3倍工资,就是一种新的payType了.这个时候,switch公用代码的简洁性反而成了之后更改代码极其麻烦的隐患

e. 解决办法——Strategy Enum Pattern 嵌套一个枚举类型Strategy Enum Type: 我们发现,工作日这个枚举元素是会对应有限个payType的属性,所以nested一个PayType的枚举就增加了灵活性,减少了重复代码,也有着OO的编程精神以及享元设计的思想

public enum PayrollDay {//简化代码,只保留MON,SUN元素来说明问题
	MON(PayType.WEEKDAY),SUN(PayType.WEEKEND); //嵌套一个枚举类,保证以后特殊假日可修改
	private PayType payType;
	private PayrollDay(PayType payType){ 	//枚举时确定属性
		this.payType=payType;
	}
	public double pay(double hoursWorked,double payRate){
		return payType.pay(hoursWorked, payRate);//支付部分方法定义在payType类枚举中,调用获得
	}
}

enum PayType{
	WEEKDAY{	//枚举中复写abstract的元素属性方法
		double overtimePay(double hours,double payRate){
			return hours<=HOUR_PER_SHIFT?0:payRate/2*(hours-HOUR_PER_SHIFT);
		}
	},
	WEEKEND{	//枚举中复写abstract的元素属性方法
		double overtimePay(double hrs,double payRate){
			return hrs*payRate/2;
		}
	};
	private static final int HOUR_PER_SHIFT=8;
	abstract double overtimePay(double hrs,double payRate);
	double pay(double hoursWorked,double payRate){//支付运算主体,根据不同元素调用不同overtimePay
		double basePay=HOUR_PER_SHIFT*hoursWorked;
		return basePay+overtimePay(hoursWorked,payRate);
	}
}


5. 说了这么多switch对枚举应用的弊端, 那么switch对枚举应用有什么好处吗?

a. 在枚举类外部利用switch对constant-specific behavior的枚举元素进行操作时,还是很方便的(如上面很多应用一样,switch传入一个枚举类)


6. ordinal方法可以返回一个序号,那么用这个序号可以去产生一个对应枚举元素的属性数值不是很方便吗?

a. 不要这样做,这将会是维护时的噩梦, ordinal会随着枚举元素列表的变化而变化,而且这根本没有利用java枚举元素类的优势

b. 解决办法——创建相应的instance field存储需要给枚举元素赋予的属性值,自定义constructor指定需要赋予属性值,元素列表中赋予相应属性值


7. int enum的时候如果要产生一个set的枚举列表,习惯实用 bit field( 1<<0, 1<<1, 1<<2.... )来产生一个set, 在java中,有没有更好的方法?

a. bit field 有很多缺点, 所有int enum缺点都有,而且还有bit运算的各种缺点, 所有缺点中最无语的可能就是无法有简单的方法遍历Set !

b. java提供了EnumSet解决这些问题,以后就再也不要用bit fields这种方法了

b.1 EnumSet 继承自 AbstractSet, 所有其父类的Set方法都可以用,一个常用的就是调用iterator进行遍历

b.2 EnumSet 自身的独有方法都是静态的,常用方法是EnumSet.of(xxx), 内部多种参数列表,可以传入枚举排列成Set


8. 如果想要根据一个类T内定义的枚举元素分类存储其对象进入枚举对应类的容器(比如Set),用ordinal可以吗?(例如给类一堆动物,分成哺乳和爬行两类)

a. 一种的想法是利用ordinal用一个数组来index每个Set: 

a.1 建立一个Set集合的数组, 数组的个数是枚举元素个数对应Set<T>[ ], 

a.2 判断ObjectT是什么枚举元素类的Set<T>[ObjectT.enumProperty.ordinal].add(new ObjectT)

a.3 不要这样做,不要用ordinal,不要用数组这种原始的方法配合Enum !缺点: 

a.3.1 array没有generics,无法避免了存入错误的类型元素 (注: 这一点似乎站不住脚,可以定义T[ ])

a.3.2 ordinal序号代表枚举类型一点都不直观,可读性不强,且打印时很麻烦

a.3.3 ordinal去指定元素类型很不保险,编程者需要自己确保其值是正确的,即使不正确,程序仍会默默地执行而编程者自己却什么都不知道

b. 解决办法,用Map处理,而java提供了更适合枚举类的EnumMap

//Type是Animal中的一个枚举类
	Map<Animal.Type,Set<Animal>> animalsByType=new EnumMap<Animal.Type,Set<Animal>>;
	for(Animal.Type t:Animal.Type.values())
		animalsByType.put(t,new Set<Animal>);
//animals是一个给定的动物的集合,type是animal中的Type枚举类型的属性值
	for(Animal a:animals)
 	animalByType.get(a.type).add(a);
	System.out.println(animalByType);

c. 同理不要将枚举元素存入数组[]数组的数组[][]等这类容器并用ordinal进行操作


9. 如果不用数组的数组和ordinal去构建一个枚举元素的方阵坐标系来操作(x,y)成对的枚举元素,用什么方法去会更好呢?

a. 解决办法——nested EnumMap: Map<X,Map<Y,Relation>>(声明时用父类接口): Y=Relation*X; Relation就像是一个Matrix将X-->Y 

b. a的解决办法就是面向的就是X-Y之间的需要产生的Relation去编写,而非找到X-Y(x,y)之间的所有Relation

10. enum类是否可以继承某个enum类?

a. 不可以(已经继承了Enum<T>),而且给enum类增加可扩展性不是一种很好的设计方式,会使应用变得很复杂

b. 如果希望扩展某个枚举类,也是可以实现的

b.1 将原本内部抽象的需实现方法抽离出枚举类,形成一个外设公共接口

b.2 将首次用到的基本枚举类继承这个接口

b.3 如果需要扩展枚举类型,新建一个基本类型的扩展枚举类继承这个接口(注意不是继承基本枚举类)

11. Enum的几点总结:

a. 枚举在本质上是为了限定,让使用者只能在一个给定的集合内选择指定了名称和/或初始参数列表的元素

b. 要把java的Enum类元素当做对象来使用,可以调用方法,自己定义方法

c. 对于需要对Enum类的元素进行获取或者遍历,可以用values函数取出成一个Enum的数组

d. 尽量避免在Enum类内部用switch(this)函数将所有的枚举类型属性方法在一个方法内 按case实现(OO!),要定义abstract方法,枚举时复写

e. 可以在枚举类中传递枚举类型的属性参数的方式封装公用代码,瘦身代码,增加程序灵活性

f. 如果需要将Enum类的元素存入一个容器进行操作,不要选择数组,EnumSet,EnumMap都可以(c情况不在d中,c是一种获取方式,类型由JVM给定)

g. 不要用ordinal,尽管它看起来很容易用

h. 尽量不要选择可扩展枚举的设计方法,如果需要,抽离公共接口,让基本类继承接口,这样就有了可扩展性

参考文献: Effective Java, Bloch, Joshua Prentice Hall (2008-05)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值