Java 的枚举机制可以通过为每个枚举实例编写不同的方法,来赋予它们不同的行为。要实现这一点,你可以在枚举类型中定义一个或多个抽象方法,然后为每个枚举实例编写不同的实现,例如:
ConstantSpecificMethod.java
import java.text.DateFormat;
import java.util.Date;
public enum ConstantSpecificMethod {
DATE_TIME {
@Override
String getInfo() {
return DateFormat.getDateInstance().format(new Date());
}
},
CLASSPATH {
@Override
String getInfo() {
return System.getenv("CLASSPATH");
}
},
JAVA_HOME {
@Override
String getInfo() {
return System.getenv("JAVA_HOME");
}
},
VERSION {
@Override
String getInfo() {
return System.getProperty("java.version");
}
};
abstract String getInfo();
public static void main(String[] args) {
for (ConstantSpecificMethod csm : values()) {
System.out.println(csm.getInfo());
}
}
}
运行结果如下:
注意:如果 CLASSPATH 和 JAVA_HOME 打印为 null,检查系统环境变量是否已设置(如果已设置但打印还是为 null ,可以尝试重启电脑,我遇到的情况就是重启生效)
你可以通过关联的枚举实例来查找和调用方法,这通常叫作表驱动模式(注意,和前面的命令模式很相似)。
在面向对象编程中,不同的行为和不同的类相关联,通过常量特定方法,枚举类型的 各种实例可以拥有各自的行为,这表明每个实例都是不同的类型。在上面的例子中,每个枚举实例都被视同于“基类" ConstantSpecificMethod 的实例,但调用 getlnfo() 方法时的行为是多态的。
然而,这两者的相似性也只能到此为止,你无法将 enum 实例等同于类类型:
NotClasses.java
enum LikeClasses {
WINKEN {
@Override
void behavior() {
System.out.println("Behavior1");
}
},
BLINKEN {
@Override
void behavior() {
System.out.println("Behavior2");
}
},
NOD {
@Override
void behavior() {
System.out.println("Behavior3");
}
};
abstract void behavior();
}
public class NotClasses {
// void f1(LikeClasses.WINKEN instance) {} // 不行
}
javac、javap
在 f1() 方法中,编译器不允许将枚举实例作为类类型来使用,只要设想一下编译器是如何生成代码的,这一点就说得通了,每个枚举元素都是 LikeClasses 的一个 static final 的实例。
同样,由于它们是静态的,内部枚举中的枚举实例表现得并不像普通的内部类,你无法从外部类访何非静态域或方法 。
再来看一个洗车的例子,每个用户都拿到了一个洗车选项的菜单,每个选项都代表不同的操作,每个选项都可以分配一个常量特定方法,然后用一个 EnumSet 来持有用户的选择。
CarWash.java
import java.util.EnumSet;
public class CarWash {
public enum Cycle {
UNDERBODY {
@Override
void action() {
System.out.println("Spraying the underbody");
}
},
WHEELWASH {
@Override
void action() {
System.out.println("Washing the wheels");
}
},
PREWASH {
@Override
void action() {
System.out.println("Loosening the dirt");
}
},
BASIC {
@Override
void action() {
System.out.println("The basic wash");
}
},
HOTWAX {
@Override
void action() {
System.out.println("Applying hot wax");
}
},
RINSE {
@Override
void action() {
System.out.println("Rinsing");
}
},
BLOWDRY {
@Override
void action() {
System.out.println("Blowing dry");
}
};
abstract void action();
}
EnumSet<Cycle> cycles = EnumSet.of(Cycle.BASIC, Cycle.RINSE);
public void add(Cycle cycle) {
cycles.add(cycle);
}
public void washCar() {
for (Cycle c : cycles) {
c.action();
}
}
@Override
public String toString() {
return cycles.toString();
}
public static void main(String[] args) {
CarWash wash = new CarWash();
System.out.println(wash);
wash.washCar();
// Order of addition is unimportant:
wash.add(Cycle.BLOWDRY);
wash.add(Cycle.BLOWDRY); // Duplicates ignored
wash.add(Cycle.RINSE);
wash.add(Cycle.HOTWAX);
System.out.println(wash);
wash.washCar();
}
}
运行结果如下:
定义一个常量特定方法的语法和定义匿名内部类差不多,但是要更简洁一些。
这个例子还展示了 EnumSet 的更多特点,它是一种 Set,所以每个可选洗车项目都只能持有一个选项,用相同的参数对 add() 方法的重复调用会被忽略(这很好理解,因为打开开关一次和多次是一样的) 。另外,添加枚举实例的顺序并不重要——输出顺序由枚举声明的顺序决定。
重写常量特定方法,而不是实现一个抽象方法,这是可能的吗?是的,如以下示例所示:
OverrideConstantSpecific.java
public enum OverrideConstantSpecific {
NUT, BOLT,
WASHER {
@Override
void f() {
System.out.println("Overridden method");
}
};
void f() {
System.out.println("default behavior");
}
public static void main(String[] args) {
for (OverrideConstantSpecific ocs : values()) {
System.out.print(ocs + ": ");
ocs.f();
}
}
}
运行结果如下:
虽然枚举确实阻止了某些类型的代码,但是通常来说,可以把它们当作类来实验。