枚举类型(enum type)是指由一组固定的常量组成合法值的类型,例如一年中的季节、太阳系中的行星或者一副牌中的花色。
(An enumerated type is a type whose legal values consist of a fixed set of constants,such as the seasons of the year, the planets in the solar system, or the suits in a deck of playing cards. )
Java的枚举本质上是int值,它的基本想法是: 通过公有的静态的final域为每个枚举常量导出实例的类。
(they are classes that export one instance for each enumeration constant via a public static final field. )
也就是说它的每个枚举的实例类修饰符为public static final
由于枚举都继承自java.lang.Enum类,而java不支持多继承,但是可以实现一个或者多个接口。对于enum而言,实现接口是其子类化的唯一方法 ,可以看个例子:
public interface Food { enum Appetizer implements Food { SALAD, SOUP, SPRING_ROLLS; } enum MainCourse implements Food { LASAGNE, BURRITO, PAD_THAI, LENTILS, HUMMOUS, VINDALOO; } enum Dessert implements Food { TIRAMISU, GELATO, BLACK_FOREST_CAKE, FRUIT, CREME_CARAMEL; } enum Coffee implements Food { BLACK_COFFEE, DECAF_COFFEE, ESPRESSO, LATTE, CAPPUCCINO, TEA, HERB_TEA; } }
下面的程序说明所以东西都是某种类型的Food:
import static Food.*; public class TypeOfFood { public static void main(String[] args) { Food food = Appetizer.SALAD; food = MainCourse.LASAGNE; food = Dessert.GELATO; food = Coffee.CAPPUCCINO; } }
事实上看反编译的字节码也能看出一些东西,我使用的方便易工具是JDK(1.6)自带的javap工具,关于它的一些基本使用,可以参考文档:http://docs.oracle.com/javase/6/docs/technotes/tools/windows/javap.html
Planet类(下面的具体实例)字节码反编译的部分代码:
public final class test.effective.chapter6.Planet extends java.lang.Enum{ public static final test.effective.chapter6.Planet MERCURY; public static final test.effective.chapter6.Planet VENUS; public static final test.effective.chapter6.Planet EARTH; public static final test.effective.chapter6.Planet MARS; public static final test.effective.chapter6.Planet JUPITER; public static final test.effective.chapter6.Planet SATURN; public static final test.effective.chapter6.Planet URANUS; public static final test.effective.chapter6.Planet NEPTUNE;
// Enum type with data and behavior public enum Planet { MERCURY(3.302e+23, 2.439e6), VENUS(4.869e+24, 6.052e6), EARTH(5.975e+24, 6.378e6), MARS(6.419e+23, 3.393e6), JUPITER(1.899e+27, 7.149e7), SATURN( 5.685e+26, 6.027e7), URANUS(8.683e+25, 2.556e7), NEPTUNE(1.024e+26, 2.477e7); private final double mass; // In kilograms private final double radius; // In meters private final double surfaceGravity; // In m / s^2 // Universal gravitational constant in m^3 / kg s^2 private static final double G = 6.67300E-11; // Constructor,构造器只能是私有的private // 这样可以保证外部代码无法新构造枚举类的实例。 // 这也是完全符合情理的,因为我们知道枚举值是public static final的常量而已。 private 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; // F = ma } // 根据某一物体在地球上的重量(175),显示出该物体在所有八颗行星上的重量。 public static void main(String[] args) { double earthWeight = Double.parseDouble("175"); double mass = earthWeight / Planet.EARTH.surfaceGravity(); // Planet.values(),按照枚举的声明顺序返回它的值数组(returns an array of its values in // the order they were declared) for (Planet p : values()) // s表示字符串,f表示浮点数,n表示与平台无关的换行符 System.out.printf("Weight on %s is %f%n", p, p.surfaceWeight(mass)); } }
刚开始看这个例子的时候,不知道values()方法是如何来的,后来看了一下(JLS8.9 ),感觉有点明白了,它里面有这么一段解释:
In addition, if E is the name of an enum type, then that type has the following implicitly declared static methods: /** * Returns an array containing the constants of this enum * type, in the order they're declared. This method may be * used to iterate over the constants as follows: * * for(E c : E.values()) * System.out.println(c); * * @return an array containing the constants of this enum * type, in the order they're declared */ public static E[] values(); /** * Returns the enum constant of this type with the specified * name. * The string must match exactly an identifier used to declare * an enum constant in this type. (Extraneous whitespace * characters are not permitted.) * * @return the enum constant with the specified name * @throws IllegalArgumentException if this enum type has no * constant with the specified name */ public static E valueOf(String name);
也就是说,这两个静态方法[ values() & valueOf(String name) ]是“隐藏”的,事实上这两个方法是由编译器添加的static方法。
我用JDK自带的javap工具生成的部分代码是这样的(cd到字节码所在的目录,使用命令javap -c Planet ):
public static test.effective.chapter6.Planet[] values(); Code: 0: getstatic #87; //Field ENUM$VALUES:[Ltest/effective/chapter6/Plane t; 3: dup 4: astore_0 5: iconst_0 6: aload_0 7: arraylength 8: dup 9: istore_1 10: anewarray #1; //class test/effective/chapter6/Planet 13: dup 14: astore_2 15: iconst_0 16: iload_1 17: invokestatic #149; //Method java/lang/System.arraycopy:(Ljava/lang/Ob ject;ILjava/lang/Object;II)V 20: aload_2 21: areturn
关于这些指令的大致意思,可以参考下面的这篇文章:Java二进制指令代码解析 。当然,最好的方法是查看java虚拟机规范,可以去这个网址 下载java se7的规范中文版。在最后,作者加入了这些指令的含义。
如果多个枚举常量同时共享相同的行为,可以考虑策略枚举(Strategy enum)【from efective java 2】。
// The strategy enum pattern public enum PayrollDay { MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY), WEDNESDAY( PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY), FRIDAY(PayType.WEEKDAY), SATURDAY( 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); } // The strategy enum type private enum PayType { WEEKDAY { double overtimePay(double hours, double payRate) { return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT) * payRate / 2; } }, WEEKEND { double overtimePay(double hours, double payRate) { return hours * payRate / 2; } }; 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); } } public static void main(String[] args) { PayrollDay payroll = PayrollDay.MONDAY; System.out.println(payroll.pay(8, 20)); } }
参考资料:
effective java second edition item30
core java 卷一
Thinking in java(4th)