枚举类型(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)