假设我们正在编写一个剪刀-布-石头游戏。我们可以使用三个任意整数(例如,0,1,2或88,128,168),三个字符串(“剪刀”,“布”,“石头”)来表示三个手势。主要缺点是我们需要检查程序中其他不可行的值(例如 3、"Rock"等)以确保正确性。
枚举是一种特殊类型,它为程序中的常量提供类型安全的实现。换句话说,我们可以声明一个类型的变量,它只接受预先定义的值。
Enum JDK 源码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 package java.lang;import java.io.Serializable;import java.io.IOException;import java.io.InvalidObjectException;import java.io.ObjectInputStream;import java.io.ObjectStreamException;public abstract class Enum <E extends Enum <E>> implements Comparable <E>, Serializable { private final String name; public final String name () { return name; } private final int ordinal; 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() && 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" ); } }
抽象类不能被实例化,所以我们在java程序中不能使用new关键字来声明一个Enum,如果想要定义可以使用这样的语法:
1 2 3 4 5 enum enumName { value1,value2 method1 () {} method2(){} }
注意,此处的enum是抽象类,但是无法通过编程来继承.
1 2 public class TestEnumExtend extends Enum {}
编译文件会报错:
1 2 3 4 5 $ javac TestEnumExtend.java TestEnumExtend.java:1 : 错误: 类无法直接扩展 java.lang.Enum public class TestEnumExtend extends Enum { ^ 1 个错误
怎么理解<E extends Enum<E>>
Enum<E extends Enum<E>>
就是一个Enum只接受一个Enum或者他的子类作为参数。相当于把一个子类或者自己当成参数,传入到自身,引起一些特别的语法效果。
1 2 3 4 5 6 enum Color { RED,GREEN,YELLOW } enum Season { SPRING,SUMMER,WINTER }
因为枚举类型的默认的序号都是从零开始的,显然 Color.RED.ordinal()和Season.SPRING.ordinal()的值都是0.
注意一下 compareTo
方法,首先我们认为Enum的定义中没有使用Enum<E extends Enum<E>>
,那么compareTo方法就要这样定义(因为没有使用泛型,所以就要使用Object,这也是Java中很多方法常用的方式):
1 2 3 4 5 6 7 8 9 10 11 public final int compareTo (Object o) public final int compareTo (E o) { Enum other = (Enum)o; Enum self = this ; if (self.getClass() != other.getClass() && self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException (); return self.ordinal - other.ordinal; }
如果在compareTo方法中不做任何处理的话,那么 Color.RED和Season.SPRING相等的(因为Season.SPRING的序号和Color.RED的序号都是 0 )。但是,很明显, Color.RED和Season.SPRING并不相等。
但是Java使用Enum<E extends Enum<E>>
声明Enum,并且在compareTo的中使用E作为参数来避免了这种问题。 以上两个条件限制Color.RED只能和Color定义出来的枚举进行比较,当我们试图使用Color.RED.compareTo(Season.SPRING);
这样的代码是,会报出这样的错误:
1 The method compareTo (Color) in the type Enum<Color> is not applicable for the arguments (Season)
反编译 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public enum Fruit { APPLE, PEAR, PEACH, ORANGE; } public final class Fruit extends java .lang.Enum<Fruit> { public static final Fruit APPLE; public static final Fruit PEAR; public static final Fruit PEACH; public static final Fruit ORANGE; public static Fruit[] values(); public static Fruit valueOf (java.lang.String) ; static {}; }
可以看到,实际上在经过编译器编译后生成了一个 Fruit 类,该类继承自 Enum 类,且是 final 的。从这一点来看,Java 中的枚举类型似乎就是一个语法糖。
每一个枚举常量都对应类中的一个 public static final 的实例,这些实例的初始化应该是在 static {} 语句块中进行的。因为枚举常量都是 final 的,因而一旦创建之后就不能进行更改了。 此外,Fruit 类还实现了 values() 和 valueOf() 这两个静态方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public final class Fruit extends Enum { public static Fruit[] values(){ return (Fruit[])$VALUES.clone(); } public static Fruit valueOf (String s) { return (Fruit)Enum.valueOf(Fruit, s); } private Fruit (String s, int i) { super (s, i); } public static final Fruit APPLE; public static final Fruit PEAR; public static final Fruit PEACH; public static final Fruit ORANGE; private static final Fruit $VALUES[]; static { APPLE = new Fruit ("APPLE" , 0 ); PEAR = new Fruit ("PEAR" , 1 ); PEACH = new Fruit ("PEACH" , 2 ); ORANGE = new Fruit ("ORANGE" , 3 ); $VALUES = (new Fruit [] { APPLE, PEAR, PEACH, ORANGE}); } }
除了对应的四个枚举常量外,还有一个私有的数组,数组中的元素就是枚举常量。编译器自动生成了一个 private 的构造方法,这个构造方法中直接调用父类的构造方法,传入了一个字符串和一个整型变量。从初始化语句中可以看到,字符串的值就是声明枚举常量时使用的名称,而整型变量分别是它们的顺序(从0开始)。枚举类的实现使用了一种多例模式,只有有限的对象可以创建,无法显示调用构造方法创建对象。
values() 方法返回枚举常量数组的一个浅拷贝,可以通过这个数组访问所有的枚举常量;而 valueOf() 则直接调用父类的静态方法 Enum.valueOf(),根据传入的名称字符串获得对应的枚举对象。
枚举类的多态 枚举类无法实现继承,但是可以实现接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface WhatName { void whatName () ; } public enum Fruit implements whatName { Apple { @Override public void whatName () { System.out.println("this is an apple" ); } }, PEAR; @Override public void whatName () { System.out.println("this is a fruit" ); }
上述代码会扩充编译器生成的源码,以匿名内部类的方式对Fruit进行了Overide:
1 2 3 4 5 6 7 APPLE = new Fruit ("APPLE" , 0 ); APPLE = new Fruit ("APPLE" , 0 ) { public void whatName () { System.out.println("this is an apple" ); } };