1.用enum代替int常量
枚举类型是指由一组固定的常量组成合法值得类型。在编程语言中还没有引入枚举类型之前,表示枚举类型的常量模式是声明一组具名的int常量,每个类型成员一个常量:
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_BLOOD = 2;
这种方法称作int枚举模式,但是它存在很多不足。在安全性和使用方便性而言也没有任何帮组。如果你将apple传到想要的orange的方法中,编译器也不会发出警告,还会用=操作符将apple和orange进行对比,甚至更糟糕:
int i = (APPLE_FUJI - ORANGE_TEMPLE)
采用int枚举模式的程序十分脆弱。因为int枚举是编译时常量,被编译到使用它们的客户端中。如果与枚举常量关联的int发生了编发,客户端就必须重新编译。如果没有重新编译,程序还是可以运行的,但是它们的行为就是不确定的。当然还有一种模式使用了String常量,这样的变体被称为String枚举模式。
java1.5以后就提出了另一种可以替代的解决方案:
public enum Apple { FUJI,PIPPIN,GRANNY_SMITH }
public enum Orange { NAVEL,TEMPLE,BLOOD }
java枚举类型背后的基本想法非常简单:它们就是通过公有的静态final域为每个枚举常量导出实例的类。因为没有可以访问的构造器,枚举类型是真正的final。它们是单例(Singleton)的泛型化,本质上是单元素的枚举。如下编写一个单元素的枚举类型:
public enum Singleton{
INSTANCE;
/**
* 调用方法
*/
public void getData(){
};
}
这种方式实现单例更加简洁,无偿地提供了序列化机制,绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。
除了完善了int枚举模式的不足之外,枚举类型还允许添加任意的方法和域,并实现任意的接口。把方法或者域添加到枚举类型中有这么几个原因:
1.首先,你可能是想将数据与它的常量关联起来。
2.你可以利用任何适当的方法来增强枚举类型。
3.枚举类型可以先作为枚举常量的一个简单集合,随着时间的推移再演变成为全功能的抽象。
下面举一个有关枚举的好例子,比如太阳系中的8颗行星。每颗行星都有质量和半径,通过这两个属性可以计算出它的表面重力。(数据都是假的):
public enum Planet {
MERCURY(3.302e+23, 2.439e6),
VENUS(4.869e+24, 2.439e7),
EARTH(3.302e+23, 2.439e8),
MARS(3.302e+23, 2.439e9),
JUPITER(3.302e+23, 2.439e10),
SATURN(3.302e+23, 2.439e11),
URANUS(3.302e+23, 2.439e12),
NEPTUNE(3.302e+23, 2.439e13);
private final double mass;
private final double radius;
private final double surfaceGravity;
private static final double G = 6.67300E-11;
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
surfaceGravity = G * mass / (radius * radius);
}
public double getMass() {
return mass;
}
public double getRadius() {
return radius;
}
public double getSurfaceGravity() {
return surfaceGravity;
}
public double surfaceWeight(double mass) {
return mass * surfaceGravity;
}
}
编写一个像Planet这样的枚举类型并不难。为了将数据与枚举常量关联起来,得声明实例域,并编写一个带有数据并将数据保存在域中的构造器。枚举天生就是不可变,因此所有的域都应该为final。
有一种更好的方法可以将不同的行为与每个枚举常量关联起来:在枚举类型中声明一个抽象的方法,并在特定于常量的类主体中,用具体的方法覆盖每个常量的抽象方法。这种方法被称为特定于常量的方法实现。 如下例子:
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);
}
public static void main(String[] args) {
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));
op.apply(x,y);
}
}
输出结果:
2.000000 + 4.000000 = 6.000000
2.000000 - 4.000000 = -2.000000
2.000000 * 4.000000 = 8.000000
2.000000 / 4.000000 = 0.500000
如上例子所示,在有些情况下,在枚举中覆盖toString非常有用,使得打印的结果变得非常容易。
2.用实例域代替序数
所以的枚举都有一个ordinal方法,他返回每个枚举常量在类型中的数字位置,如下例子:
public enum Ensemble {
SOLO,DUET,TRIO,QUARTET,QUINTET,
SEXTET,SEPTET,OCTET,NONET,DECTET;
public int numberOfMusicians(){
return ordinal();
}
public static void main(String[] args) {
for (Ensemble en : Ensemble.values()){
System.out.print(en.numberOfMusicians());
}
}
}
输出结果:0123456789。虽然这个枚举不错,但是维护起来就像一场噩梦。如果常量进行重新排序,numberOfMusicians方法就好遭到破坏。幸运的是有一种很简单的方法可以解决这个问题。如下:
public enum Ensemble {
SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),
SEXTET(6), SEPTET(7), OCTET(8), NONET(9), DECTET(10);
private final int numberOfMusicians;
Ensemble(int size) {
this.numberOfMusicians = size;
}
public int numberOfMusicians() {
return numberOfMusicians;
}
}
永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中。
Enum规范中谈到ordinal时写道:“大多数程序员都不需要这个方法。它是设计成用于像EnumSet和EnumMap这种基于枚举的通用数据结构的。”除非你在编写的是这种数据结构,否则最好完全避免使用ordinal方法。
3.用Enum代替位域
如果一个枚举类型的元素主要用在集合中,一般就使用int枚举模式,将2的不同倍数赋予每个常量:
public class Text {
public static final int STYLE_BOLD = 1 << 0;
public static final int STYLE_ITALIC = 1 << 1;
public static final int STYLE_UNDERLINE = 1 << 2;
public static final int STYLE_STRIKETHROUCH = 1 << 3;
public void applyStyles(int style) {
}
}
这种表示法让你用OR运算符将几个常量合并到一个集合中,称作位域:text.applyStyles(STYLE_BOLD | STYLE_ITALIC);位域表示法也允许利用位操作,有效地执行想union(联合)和intersection(交集)这样的集合操作。但是位域有着int枚举模式的所以缺点,甚至更多。有没有更好的替代方法呢?java.util包提供了EnumSet类来有效地表示单个枚举类型中提取的多个值的多个集合。这个类实现了Set接口,提供了丰富的功能、类型安全,以及可以从任何其他的Set实现中得到互用性。但是在内部具体的实现上,每个EnumSet内容都表示为位矢量。如果底层的枚举类型有64个或者更少的元素-----大多如此------EnumSet就是用单个long来表示,因此它的性能比得上位域的性能。批处理,如removeAll何retainAll,都是利用位算法来实现的,就像手工替位域实现的那样。但是可以避免手工位操作时容易出现错误以及不太雅观的代码,因为EnumSet替你完成了这项艰巨的工作。如下是一个枚举代替位域后的例子:
public class Text {
public enum Style {
BOLD, STYLE_ITALIC, UNDERLINE, STRIKETHROUCH;
}
public void applyStyles(int style) {
EnumSet.of(Style.UNDERLINE, Style.STYLE_ITALIC);
}
}
EnumSet提供了丰富的静态工厂来轻松创建集合。注意applyStyles方法采用Set<Style>而非EnumSet<Style>。虽然看起来好像所有的客户端都可以将EnumSet传到这个方法,但是最好还是接受接口类型而非接受现实类型。
好了,这篇文章主要介绍枚举最常用的一些功能