一般常量的定义方式
来自 Effective Java Edition 3
// The int enum pattern - severely deficient!
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;
缺点
1、类型不安全:当传递给需要 ORANGE_xx 的方法时不会检查
2、缺乏表现力:int 直接输出显示为数字,没有意义。
3、编译时常量:常量被编译到客户端时,若常量发生改变,则客户端会报错
4、硬编码易出错:String 类型常量若书写错误,编译时逃脱检验,运行时报错
5、功能有限:没有有效的方法迭代常量组,也没有办法获得常量组的大小
语法定义
从 JDK1.5 开始,枚举类解决了上述的所有缺点。创建枚举类型要使用 enum 关键字,隐含了所创建的类型都是 java.lang.Enum 类的子类(java.lang.Enum 是一个抽象类)。枚举类型符合通用模式 abstract Class Enum<E extends Enum
public enum EnumTest {
MON, TUE, WED, THU, FRI, SAT, SUN;
}
这段代码实际上调用了7次 Enum(String name, int ordinal):
new Enum<EnumTest>("MON",0);
new Enum<EnumTest>("TUE",1);
new Enum<EnumTest>("WED",2);
... ...
遍历、switch 等常用操作
对enum进行遍历和switch的操作示例代码:
public class Test {
public static void main(String[] args) {
for (EnumTest e : EnumTest.values()) {
System.out.println(e.toString());
}
System.out.println("----------------我是分隔线------------------");
EnumTest test = EnumTest.TUE;
switch (test) {
case MON:
System.out.println("今天是星期一");
break;
case TUE:
System.out.println("今天是星期二");
break;
// ... ...
default:
System.out.println(test);
break;
}
}
}
- 输出结果:
MON
TUE
WED
THU
FRI
SAT
SUN
----------------我是分隔线------------------
今天是星期二
Enum 实例分析
由于枚举类都继承了 Enum 类,所以类中声明的枚举可以调用许多父类已有的方法
Enum.class
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
private final String name;
private final int ordinal;
public final String name() {
return name;
}
public final int ordinal() {
return ordinal;
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
public String toString() {
return name;
}
public final boolean equals(Object other) {
return this==other;
}
public final int hashCode() {
return super.hashCode();
}
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
protected final void finalize() { }
private 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");
}
常用方法简要说明
int compareTo(E o)
比较此枚举与指定对象o的顺序。
Class<E> getDeclaringClass()
返回与此枚举常量的枚举类型相对应的 Class 对象。
String name()
返回此枚举常量的名称,在其枚举声明中对其进行声明。
int ordinal()
返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。
String toString()
返回枚举常量的名称,它包含在声明中。
static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
返回带指定名称的指定枚举类型的枚举常量。
boolean equals(Object other)
比较两个枚举常量是否相等
测试 Demo
EnumTest
public enum EnumTest {
MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6) {
@Override
public boolean isRest() {
return true;
}
},
SUN(0) {
@Override
public boolean isRest() {
return true;
}
};
private final int value;
private EnumTest(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public boolean isRest() {
return false;
}
}
TestDemo1
public class TestDemo1 {
public static void main(String[] args) {
EnumTest test = EnumTest.TUE;
// compareTo(E o),self.ordinal - other.ordinal
switch (test.compareTo(EnumTest.MON)) {
case -1:
System.out.println("TUE 在 MON 之前");
break;
case 1:
System.out.println("TUE 在 MON 之后");
break;
default:
System.out.println("TUE 与 MON 在同一位置");
break;
}
// getDeclaringClass()
System.out.println("getDeclaringClass(): " + test.getDeclaringClass().getName());
// name() 和 toString()
System.out.println("name(): " + test.name());
System.out.println("toString(): " + test.toString());
// ordinal(), 返回值是从 0 开始
System.out.println("ordinal(): " + test.ordinal());
// 将常量名称转换为常量本身
System.out.println("valueOf:" + Enum.valueOf(EnumTest.class, "TUE").getValue());
// equals(Object o)
System.out.println(test.equals(EnumTest.TUE));
}
}
- 输出结果:
TUE 在 MON 之后
getDeclaringClass(): com.livejq.enums.EnumTest
name(): TUE
toString(): TUE
ordinal(): 1
valueOf:2
true
TestDemo2
public class TestDemo2 {
public static void main(String[] args) {
// EnumSet的使用
EnumSet<EnumTest> weekSet = EnumSet.allOf(EnumTest.class);
for (EnumTest day : weekSet) {
System.out.println(day);
}
// EnumMap的使用
EnumMap<EnumTest, String> weekMap = new EnumMap(EnumTest.class);
weekMap.put(EnumTest.MON, "星期一");
weekMap.put(EnumTest.TUE, "星期二");
// ... ...
for (Iterator<Entry<EnumTest, String>> iter = weekMap.entrySet().iterator(); iter.hasNext();) {
Entry<EnumTest, String> entry = iter.next();
System.out.println(entry.getKey().name() + ":" + entry.getValue());
}
}
}
原理分析
反汇编
根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。
EnumTest 使用反汇编命令 javap EnumTest.class 得到如下:
public class EnumTest extends java.lang.Enum<EnumTest> {
public static final EnumTest MON;
public static final EnumTest TUE;
public static final EnumTest WED;
public static final EnumTest THU;
public static final EnumTest FRI;
public static final EnumTest SAT;
public static final EnumTest SUN;
public static EnumTest[] values();
public static EnumTest valueOf(java.lang.String);
public int getValue();
public boolean isRest();
EnumTest(java.lang.String, int, int, EnumTest$1);
static {};
}
所以,实际上 enum 就是一个 class,只不过 java 编译器帮我们做了语法的解析和编译而已。
反编译
使用 jad 反编译工具可以查看详细的编译后代码,
下载后放到想反编译的类下,运行:
jad -s java EnumTest.java
:
具体命令看README.txt,-s为suffix,定义后缀名(默认生成.jad文件)
enum 的语法结构尽管和 class 的语法不一样,但是经过编译器编译之后产生的是一个 class 文件。该 class 文件经过反编译可以看到实际上是生成了一个类,该类继承了java.lang.Enum。
public class EnumTest extends Enum
{
public static EnumTest[] values()
{
return (EnumTest[])$VALUES.clone();
}
public static EnumTest valueOf(String s)
{
return (EnumTest)Enum.valueOf(EnumTest, s);
}
private EnumTest(String s, int i, int j)
{
super(s, i);
value = j;
}
public int getValue()
{
return value;
}
public boolean isRest()
{
return false;
}
public static final EnumTest MON;
public static final EnumTest TUE;
public static final EnumTest WED;
public static final EnumTest THU;
public static final EnumTest FRI;
public static final EnumTest SAT;
public static final EnumTest SUN;
private int value;
private static final EnumTest $VALUES[];
static
{
MON = new EnumTest("MON", 0, 1);
TUE = new EnumTest("TUE", 1, 2);
WED = new EnumTest("WED", 2, 3);
THU = new EnumTest("THU", 3, 4);
FRI = new EnumTest("FRI", 4, 5);
SAT = new EnumTest("SAT", 5, 6) {
public boolean isRest()
{
return true;
}
}
;
SUN = new EnumTest("SUN", 6, 0) {
public boolean isRest()
{
return true;
}
}
;
$VALUES = (new EnumTest[] {
MON, TUE, WED, THU, FRI, SAT, SUN
});
}
}
- 由于其中的枚举常量声明为 final 类,并且构造方法为 private ,所以客户端不能够实例化或继承。
- 由于父类实现的序列化接口中实现了 readObject 方法为不可用,所以枚举常量不能够被反序列化。
- 枚举类提供了编译时的安全。若声明一个枚举类,则其中枚举类型必定相同,尝试传递错误类型的值将导致编译时错误。
- 可以在枚举类型中添加或重新排序常量,而无需重新编译其客户端,因为导出常量的属性在枚举类型与其客户端之间提供了一层隔离。
- 常量值不会编译到客户端,因为它们位于 int 枚举模式中,可以通过调用其 toString 方法将枚举转换为可打
印的字符串。
评论区