概述
枚举类型是指一组固定常量组成合法值的类型,例如一年四季、太阳系行星、一副牌的四种花色等可以归纳的东西。在枚举出现之前,一般都是用常量来标识每个不同的类型
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;
public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOD = 2;
这种方法称为int枚举模式,在代码中经常会看到,但却有一些不足。这种写法,如果一不留神,写串了,把 APPLE_FUJI 传进了 需要 ORANGE_NAVEL 的方法里,因为值一样,所以编译不会报错,如果需要用 == 来比较两个值是否都是苹果,此时明显不是,但值却相等,很容易逻辑出错。并且我们为了区分 apple 和 orange,在给他们定义常量时,通常会把他们的类型作为前缀,防止命名冲突。有时候我们也会用String类型来标识类型,例如
public static final String BANANA_FUJI = "FUJI";
public static final String BANANA_PIPPIN = "PIPPIN";
public static final String BANANA_GRANNY_SMITH = "GRANNY_SMITH";
这种被称为String枚举模式,但与int枚举模式相比,这种更糟糕。我们知道,int是基本类型,String则不是,String 类型是在常量池里的,比较是用 equals(()方法,一旦写错了任何一个字母,运行时也会出问题。所以,Java 1.5 开始,出现了替代的解决方案,就是枚举。
public enum Apple {
FUJI, PIPPIN, GRANNY_SMITH
}
public enum Orange {
NAVEL, TEMPLE, BLOD
}
枚举是通过公有的静态final域为每个枚举常量导出实例的类,没有对外构造器,默认都是 final 类型,不会被继承,不能被构造创建,直接使用,同时也是单利类型,所以,也可以把枚举看成一个特殊的类,这个类不能被继承,不能创建构造方法,不能被 new 出对象,它直接提供对象,并且这个类是单利模式的。 书中这个太阳系八个星星的信息,书中是通过枚举来举例子
public enum Planet {
MERCURY(1, 1),
VENUS(2, 2),
EARTH(3, 3);
private final double mass;
private final double radius;
private final double surfaceGravity;
private static final double G = 6.67 * 1000000;
Planet(double mass, double radius){
this.mass = mass;
this.radius = radius;
surfaceGravity = G * mass / (radius * radius);
}
public double surfaceGravity(){
return surfaceGravity;
}
public double surfaceWeight(double mass){
return mass * surfaceGravity;
}
}
通过这个枚举,和上面 Apple 枚举相比,我们可以发现一些东西。 因为枚举不对外构造,它是内部自己实现的,所以 Apple 是默认无参构造,可以忽略不谢,Planet 则有两个参数需要传进来,所以声明了两个参数的构造;每个对象的值都是在内部统一写好的,后期不能改变,枚举,就是把所有的都列举出来的意思,既然要列举,那肯定要提前知道所有信息,才能罗列;枚举也对外提供方法,对外暴露自己参数的信息;枚举也可以做一些业务逻辑操作,按照业务计算一些值。 看到这,我们是不是感觉和 class 类差不多,可以看做是特殊的 class 类来用就行了,没那么多的神秘感。我们如果要获取枚举单独一个值,只需要 Planet o = Planet.MERCURY; 有点类似class 的静态对象调用;获取枚举的所有值,则 Planet[] arr = Planet.values(); 是不是像List集合中对外暴露的方法,转换为数组。枚举有一些自己独特的 api, 只需要明白与普通class 的图通,就能灵活运用。比如,要打印所有星星的surfaceWeight值,则
private void test() {
double erathWeight = 10f;
double mass = erathWeight / Planet.EARTH.surfaceGravity();
for (Planet p : Planet.values()) {
System.out.println(p + " weight is " + p.surfaceWeight(mass));
}
}
通过遍历,拿到每个Planet,然后穿进去参数,获取想要的值,用法和普通类基本一样,逻辑都是相通的。枚举的另外一种使用方式,比如要标识几种不同的状态
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("Unknow oop : " + this);
}
}
以枚举列举四种类型,一一对应。代码可行,但容易出错和不利于扩展,扩展的原则是尽量不改变原先的代码,如果我们增加一种新的类型,比如 取余,那么我们需要在switch方法里添加新类型,如果一不小心添加错了,忘了添加对应的条件,那么仍能编译,但运行会出错。之前提到了,枚举可以成是特殊的class类,那么,我们可以用抽象技术,把apply()方法抽象出来,每个 枚举类型在自己内部再去实现它,修改为
public enum Operation {
PLUS {
@Override
double apply(double x, double y) {
return x + y;
}
},
MINUS {
@Override
double apply(double x, double y) {
return x - y;
}
},
TIMES {
@Override
double apply(double x, double y) {
return x * y;
}
},
DIVIDE {
@Override
double apply(double x, double y) {
return x / y;
}
};
abstract double apply(double x, double y);
}
添加新类型,直接写新的就好了,不牵涉原先逻辑。有木有感觉和class是一样的, 可以理解为 Operation 抽象的class, PLUS 等是创建的对象,因为是抽象的,所以创建对象时要把抽象的方法给实现,跟new 一个抽象类和new一个接口是同样道理。 进一步改进方案是在增加一个符号标识,解释方法的功能,例如添加 + - * / %等等
public enum Operation {
PLUS("+") {
@Override
double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
@Override
double apply(double x, double y) {
return x - y;
}
},
TIMES("*") {
@Override
double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
@Override
double apply(double x, double y) {
return x / y;
}
},
Residual("/") {
@Override
double apply(double x, double y) {
return x % y;
}
};
private final String symbol;
Operation(String symbol) {
this.symbol = symbol;
}
abstract double apply(double x, double y);
}
这样就把问题解决了,并且更加清晰。但有时候,复杂的情况怎么办?
enum PayrollDay {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY;
private static final int HOURS_PER_SHIFT = 8;
double pay(double hoursWorked, double payRate) {
double basePay = hoursWorked * payRate;
double overtimePay; // Calculate overtime pay
switch(this) {
case SATURDAY: case SUNDAY:
overtimePay = hoursWorked * payRate / 2;
break;
default: // Weekdays
overtimePay = hoursWorked <= HOURS_PER_SHIFT ?
0 : (hoursWorked - HOURS_PER_SHIFT) * payRate / 2;
}
return basePay + overtimePay;
}
}
像这种,一周七天,周一到周五是平常工资,周六和周日有加班工资,这段代码看起来比较简洁,但犯了和上面同样的问题,不利于扩展。改法如果和上面一样修改。简单的例子
enum PayrollDay {
MONDAY {
@Override
double pay(double hoursWorked, double payRate) {
double basePay = hoursWorked * payRate;
double overtimePay = hoursWorked <= HOURS_PER_SHIFT ?
0 : (hoursWorked - HOURS_PER_SHIFT) * payRate / 2;
return basePay + overtimePay;
}
},
TUESDAY {
@Override
double pay(double hoursWorked, double payRate) {
double basePay = hoursWorked * payRate;
double overtimePay = hoursWorked <= HOURS_PER_SHIFT ?
0 : (hoursWorked - HOURS_PER_SHIFT) * payRate / 2;
return basePay + overtimePay;
}
};
private static final int HOURS_PER_SHIFT = 8;
abstract double pay(double hoursWorked, double payRate);
}
里面只包含了两个,其他五个大家可以自己完善。如果写全了,我们会发现 周一到周五的pay()方法一样,周六和周日的一样,有重复,能复用吗?能! 该抽取的就抽取,该封装的就封装,那么怎么办?我们可以再用一个枚举来分类,分为平常日和周末日,这样,就归为两类了,在这两类里面各自实现自己的逻辑,然后外面的周一到周五调用一个,周六到周日调用另外一个。这样相当于封装了一层,划分更清晰
public enum PayRoll {
MONDY(PayType.WEEKDAY),
TUESDAY(PayType.WEEKDAY),
WEDNESDAY(PayType.WEEKDAY),
THURSDAY(PayType.WEEKDAY),
FRIDAY(PayType.WEEKDAY),
SATURDAY(PayType.WEEKEND),
SUNDAY(PayType.WEEKEND);
private final PayType payType;
PayRoll(PayType payType) {
this.payType = payType;
}
double pay(double hoursWorked, double payRate) {
return payType.pay(hoursWorked, payRate);
}
private enum PayType {
WEEKDAY {
@Override
double overtimePay(double hoursWorked, double payRate) {
double overtime = hoursWorked - HOURS_PER_SHIFT;
return overtime <= 0 ? 0 : overtime * payRate / 2;
}
},
WEEKEND {
@Override
double overtimePay(double hoursWorked, double payRate) {
return hoursWorked * payRate / 2;
}
};
private static final int HOURS_PER_SHIFT = 8;
abstract double overtimePay(double hoursWorked, double payRate);
double pay(double hoursWorked, double payRate) {
double basePay = hoursWorked * payRate;
return basePay + overtimePay(hoursWorked, payRate);
}
}
}
里面定义一个 PayType ,标识两种类型,平常和周末,抽象一个额外补助工资的方法overtimePay(),两种类型 WEEKDAY WEEKEND 分别实现自己的细节,那么这个枚举就ok了。再看外面,周一到周五属于 WEEKDAY,周六周日是 WEEKEND ,那么我们在枚举 PayRoll 定义属性,之前定义的属性是对象,这次特殊点,定义的属性时枚举,同时通过构造,一周七天前五天对应WEEKDAY,后两天对应WEEKEND,这样,就完美了。
枚举分类很好用,android中也常用到,但枚举比常量好性能,android 代码中除了关键点,还是尽量少用。
最后
以上就是昏睡咖啡豆为你收集整理的第三十条 用enum代替int常量的全部内容,希望文章能够帮你解决第三十条 用enum代替int常量所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复