EffectiveJava(30) -- 全面解析enum类型
—-在大多数项目中,我们会经常使用int类型来声明final类型的常量,它在不考虑安全的情况下确实能满足我们绝大多数的需求.但是在JDK1.5版本发布之后,声明一组固定的常量组成合法值的类型就建议使用enum(枚举)类型代替.原因有三:
– - -1.int类型对安全性和使用方便性没有任何帮助.就像你可以用==/=将不同类型进行对比甚至赋值,而这违背了它的final特性
- - - 2.如果与枚举常量关联的int发生了变化,客户端就必须重新编译.如果没有重新编译,它就会带着不确定的行为运行,你无法预测程序的运行结果
- - - 3.并没有方便的方式将int枚举常量翻译成可打印的字符串,而在实际操作中我们经常要这么做.
那么什么是枚举呢?
枚举就是通过公有的静态final域为每个枚举常量导出实例的类.它很像一个特殊的class,实际上enum声明定义的类型就是一个类。
而这些类都是类库中Enum类的子类(java.lang.Enum<E>)。它们继承了这个Enum中的许多有用的方法。
我们对代码编译之后发现,编译器将enum类型单独编译成了一个字节码文件:类名.class。
它们是单例的泛型化,本质上还是int类型
.
枚举的工作模式又是怎么样的呢?
包含多个同名常量的多个枚举可以在同一个系统中和平共处,因为每个枚举都有自己的命名空间.
你可以增加或者重新排列枚举类型的常量并无需重新编译它的客户端代码,因为常量值并没有被编译到客户端代码中,而是在int枚举模式中.
导出常量的域在枚举类型和他的客户端之间提供了一个隔离层,
接下来是我实现一个枚举类型的范例:通过行星的质量和半径计算它的表面重力
public enum Planet {
//枚举行星名称
MERCURY(3.302e+23, 2.439e6), VENUS(4.869e+24, 6.052e6), EARTH(5.975e+24, 6.378e6);
private final double mass;
private final double radius;
//表面重力
private final double surfaceGravity;
private static final double G = 6.6730E-11;
//为了将数据与枚举常量关联起来,得声明实例域,并编写一个带有数据并将数据保存在域中的构造器
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
//构造方法中计算行星的表面重力,使这个类只要被调用就可以会计算重力值
surfaceGravity = G * mass / (radius * radius);
}
public double mass() {
return mass;
}
public double radius() {
return radius;
}
public double surfaceGravity() {
return surfaceGravity;
}
public double surfaceWeight(double mass) {
return mass * surfaceGravity;
}
}
在这个示例中,我们再enum类型里添加了mass,radius,surfaceGravity域和其对应的构造方法,enum是允许我们这么做的,它还可以实现任意接口.
我们可以用任何适当的方法来增强枚举类型,它可以从一个枚举常量的简单集合演变成全功能的抽象类!
这是它的测试方法,他会以表格形式显示出2kg的物体在所有行星上的重量–
double earthWeigth = 2;
double mass = earthWeigth/Planet.EARTH.surfaceGravity();
for(Planet p:Planet.values()){
System.out.printf("Weight on %s is %f%n",p,p.surfaceWeight(mass));
}
接下来我们讲讲enum类型使用的注意事项:
1.除非非要将枚举方法导出至它的客户端,否则都应该将它声明为私有的.
2.如果一个枚举具有普遍适用性,他应该称为一个顶层类.如果它被用在一个特定的顶层类中,他就应该成为该顶层类的一个成员类.
enum还可以为我们实现更加强大的功能
当你需要将本质上不同的行为与每个常量关联起来时,上面的例子明显有些不够用了.
我们用枚举提供一个方法来执行计算器的四大基本操作:
public enum Operation{
PLUS,MINUS,TIMES,DIVIDE;
double apply(double x,double y){
switch(this){
case PLUS:return x + y;
case MINUS: return x-y;
case TIMES: return x * y;
case DIVIDE : return x/y;
}
//没有他将不能通过编译
throw new AssertionError("Unknown op:"+this);
}
}
每当你添加了新的计算操作,你都要添加switch条件,否则就会运行失败.这种代码是很脆弱的,因为你不能寄希望于程序员时刻记住去编写一个容易忽略的方法,你应该强制程序员去实现它.
强制实现一个方法我们可以通过实现一个接口或者实现一个为实现的抽象方法,这里我们选后一种.
在枚举类型中声明一个抽象的apply方法,并在特定于常量的类主体中,用具体的方法覆盖每个常量的抽象apply方法,如果你添加新的常量却没有实现抽象方法,编译器会提醒你.
public enum Operation {
PLUS("+"){
double apply(double x,double y){
return x + y;
}
},
MINUS("-"){
double apply(double x,double y){
return x - y;
}
},
TIMES("*"){
double apply(double x,double y){
return x * y;
}
},
DIVIDE("/"){
double apply(double x,double y){
return x/y;
}
};
private final String symbol;
Operation(String symbol){
this.symbol = symbol;
}
@Override public String toString(){
return symbol;
}
abstract double apply(double x,double y);
}
测试 double x = 2;
double y = 4;
for(Operation op:Operation.values()){
System.out.printf(“%f %s %f = %f%n”,x,op,y,op.apply(x, y));
}可以使用switch呢? – 枚举中的switch语句适合于给外部的枚举类型增加特定于常量的行为.
public static Operation inverse(Operation op){
switch(op){
case PLUS: return Operation.MINUS;
case MINUS:return Operation.PLUS;
case TIMES: return Operation.DIVIDE;
case DIVIDE:return Operation.TIMES;
}
}
values():枚举类都要继承的Enum的方法,同时还有以下方法可以使用:
(1) ordinal()方法: 返回枚举值在枚举类种的顺序。这个顺序根据枚举值声明的顺序而定。
- - - Color.RED.ordinal(); //返回结果:0
- - - Color.BLUE.ordinal(); //返回结果:1
(2) compareTo()方法: Enum实现了java.lang.Comparable接口,因此可以比较象与指定对象的顺序。Enum中的compareTo返回的是两个枚举值的顺序之差。当然,前提是两个枚举值必须属于同一个枚举类,否则会抛出ClassCastException()异常。(具体可见源代码)
- - - Color.RED.compareTo(Color.BLUE); //返回结果 -1
(3) values()方法: 静态方法,返回一个包含全部枚举值的数组。
- - - Color[] colors=Color.values();
- - - for(Color c:colors){
- - - System.out.print(c+”,”);
- - - }//返回结果:RED,BLUE,BLACK YELLOW,GREEN,
(4) toString()方法: 返回枚举常量的名称。
- - - Color c=Color.RED;
- - - System.out.println(c);//返回结果: RED
(5) valueOf()方法: 这个方法和toString方法是相对应的,返回带指定名称的指定枚举类型的枚举常量。
- - - - Color.valueOf(“BLUE”); //返回结果: Color.BLUE
(6) equals()方法: 比较两个枚举类对象的引用。
上面我们提到过,enum可以很轻松的实现将enum常量以字符串的方式打印出来,下面我们就利用enum继承的values()方法的来实现这个方法
private static final Map<String,Operation> stringToEnum = new HashMap<String,Operation>();
static{
for(Operation op :values()){
stringToEnum.put(op.toString(),op);
}
}
public static Operation fromString(String symbol){
return stringToEnum.get(symbol);
}
一大堆赞美过后,往往也要有个但是.虽然Enum类型好像什么时候都可以用,那么为什么使用它的程序员好像依然不多呢?
1.Enum类型相对来说比int要消耗更多的系统资源
2.从上面的举例中我们也看到,通过Enum实现某个功能是比较繁杂的,普通程序员并不能很好的理解并知道什么时候使用它.
3.它使得在枚举常量中共享代码变得更加困难
例如:给公司员工算总工资(总工资=基本工资+加班工资)
注:在有趣的JAVA中,我们说明了浮点数并不适合于计算工资等类似的计算.
具体请转传送门:
http://blog.csdn.net/jacxuan/article/details/62238406
enum PayrolDay{
MONDAY,TUERDAY,WEDENSDAY,THURSDAY,FRIDAY,STAURDAY,SUNDAY;
//基本工时
private static final int HOURS_PER_SHIFT = 8;
double base(double hoursWorked,double payRate){
double basePay = hoursWorked * payRate;
double overtimePay;
switch(this){
case STAURDAY : case SUNDAY : overtimePay = hoursWorked * payRate*1.5;
default:
overtimePay = hoursWorked<=HOURS_PER_SHIFT?0:(hoursWorked - HOURS_PER_SHIFT)*payRate*1.5;
break;
}
return basePay + overtimePay;
}
}
这段代码是十分危险的,因为如果你在国庆小长假的时候被公司强制要求加班的时候,你只能双休日才能领到加班工资.换句话说,这段代码扩展性非常差,它很难增加新的’工资方案’.那么我们如何解决这个问题呢?
必须用工作日加班的具体方法代替PayrollDay中抽象的overtimePay方法.每当你添加一个枚举常量时,就必须强制选择一种加班报仇策略.
public enum PayrollDay {
//声明enum常量并添加工作报酬策略
MONDAY(PayType.WEEKDAY),TUESDAY(PayType.WEEKDAY),WEDNESDAY(PayType.WEEKDAY),THURSDAY(PayType.WEEKDAY),FRIDAY(PayType.WEEKDAY),SATUSDAY(PayType.WEEKEND),SUNDAY(PayType.WEEKEND);
private final PayType payType;
//添加构造方法使其能够选择报酬策略
PayrollDay(PayType payType) {
this.payType = payType;
}
double pay(double hoursWorked,double payRate){
return payType.pay(hoursWorked, payRate);
}
/将加班报仇策略添加到PayType方法中
private enum PayType{
WEEKDAY{
double overtimePay(double hours,double payRate){
return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT)*payRate *1.5;
}
},
WEEKEND{
double overtimePay(double hours,double payRate){
return hours*payRate*1.5;
}
};
private static final int HOURS_PER_SHIFT = 8;
abstract double overtimePay(double hrs,double payRate);
double pay(double hoursWorked,double payRate){
double basePay = hoursWorked * payRate;
return basePay + overtimePay(hoursWorked,payRate);
}
}
}
枚举类型这么强大,我们应该在何时选择它呢?
每当需要一组固定常量的时候.这包括”天然的枚举类型”,如行星,一周的天数,棋子的数目等.他也包括你在编译时就知道其所有可能值的其他集合,如菜单选项,操作代码,命令标记等.最后,记住一点.枚举类型中的常量集并不一定要始终保持不变.
总结:与int类型相比,枚举类型的优势是不言而喻的.枚举类型要易读的多,也要更加安全,功能更加强大.许多枚举类型都不需要显式的构造器或者成员名单许多其他枚举则受益于”每个常量于属性的关联”以及”提供行为受这个属性影响的方法”.只有极少数的枚举受益于将多种行为与单个方法关联.在这种相对少见的情况下,特定于常量的方法要优于启用自有值的枚举.如果多个枚举常量同时共享相同的行为,则考虑枚举.
EffectiveJava(30) -- 全面解析enum类型的更多相关文章
- Java中Enum类型的序列化(转)
在Java中,对Enum类型的序列化与其他对象类型的序列化有所不同,今天就来看看到底有什么不同.下面先来看下在Java中,我们定义的Enum在被编译之后是长成什么样子的. Java代码: Java代码 ...
- C# 获取与解析枚举类型的 DescriptionAttribute
原文:C# 获取与解析枚举类型的 DescriptionAttribute System.ComponentModel.DescriptionAttribute 这个 Attribute,经常被用来为 ...
- jsoncpp动态解析节点类型
在互联网无处不在的今天,JSON作为轻量级数据存储格式,被广泛应用到互联网数据传输中.众所周知,JSON由键/值对.对象.数组组成,其中键/值对的值包括以下几种类型: enum ValueType { ...
- set和enum类型的用法和区别
mysql中的set和enum类型的用法和区别 mysql中的enum和set其实都是string类型的而且只能在指定的集合里取值, 不同的是set可以取多个值,enum只能取一个值. 1 2 3 ...
- MYSQL中 ENUM 类型
MYSQL中 ENUM 类型的详细解释 ENUM类型 ENUM 是一个字符串对象,其值通常选自一个允许值列表中,该列表在表创建时的列规格说明中被明确地列举. 在下列某些情况下,值也可以是空串(&quo ...
- C#中enum类型
最近碰到了枚举类型,就顺便整理下. 枚举的基类Enum,可以是除 Char 外的任何整型.不做显示声明的话,默认是整形(Int32). 声明一个Enum类型: /// <summary> ...
- C#遍历enum类型
对于enum类型: 使用foreach遍历enum类型的元素并填充combox foreach ( HatchStyle hs1 in Enum.GetValues(typeof(HatchStyle ...
- Java 语言中 Enum 类型的使用介绍
Enum 类型的介绍 枚举类型(Enumerated Type) 很早就出现在编程语言中,它被用来将一组类似的值包含到一种类型当中.而这种枚举类型的名称则会被定义成独一无二的类型描述符,在这一点上和常 ...
- java中enum类型的使用
java 枚举类型enum 的使用 最近跟同事讨论问题的时候,突然同事提到我们为什么java 中定义的常量值不采用enmu 枚举类型,而采用public final static 类型来定义呢?以前我 ...
随机推荐
- lr_Controller界面图
lr可用运行图介绍: lr集合点策略:
- OpenStack 存储服务 Cinder存储节点部署LVM (十四)
部署在block(10.0.0.103)主机 一)配置lvm 1.安装lvm2软件包 yum install lvm2 -y 2.启动LVM的metadata服务并且设置该服务随系统启动 system ...
- redux saga学习
来源地址:https://www.youtube.com/watch?v=o3A9EvMspig Saga的基本写法 takeEvery与takeLatest的区别 takeEvery是指响应每一个请 ...
- 转:Fuzzing Apache httpd server with American Fuzzy Lop + persistent mode
Fuzzing Apache httpd server with American Fuzzy Lop + persistent mode 小结:AFL主要以文件作为输入进行fuzz,本文介绍如何对网 ...
- HDU 6186 CS Course【前后缀位运算枚举/线段树】
[前后缀枚举] #include<cstdio> #include<string> #include<cstdlib> #include<cmath> ...
- scrapy抓取拉勾网职位信息(四)——对字段进行提取
上一篇中已经分析了详情页的url规则,并且对items.py文件进行了编写,定义了我们需要提取的字段,本篇将具体的items字段提取出来 这里主要是涉及到选择器的一些用法,如果不是很熟,可以参考:sc ...
- 如何使用Web字体?
如何使用Web字体 嵌入Web字体的关键是@font-face规则,通过它可以指定浏览器下载web字体的地址,以及如何在样式表中引用该字体 @font-face { font-family: Voll ...
- sqlldr load UTF8 error
The default length semantics for all datafiles (except UFT-16) is byte. So in your case you have a C ...
- 单能X射线产生方法
主要是荧光 利用布拉格准则, 关键词如下.. 国内有些专利 monochromating crystal spectrometer 物理实验设备名称翻译 ... 单色光检糖计 monochromati ...
- ANY和SOME 运算符
在SQL中ANY和SOME是同义词,所以下面介绍的时候只使用ANY,SOME的用法和功能和ANY一模一样.和IN运算符不同,ANY必须和其他的比较运算符共同使用,而且必须将比较运算符放在ANY 关键字 ...