简介
关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。这是一种非常有用的功能。
基本enum特性
通过values()方法可以遍历enum实例,而且该数组中的元素严格保持在enum中声明时的顺序。
创建enum时,编译器会生成一个相关的类,这个类继承自java.lang.Enum,下面例子中演示了Enum提供的一些功能:
public class EnumDemo1 {
public static void main(String[] args) {
for (Fruit fruit:Fruit.values()){
System.out.println(fruit.ordinal());//返回声明的次序
System.out.print(fruit.compareTo(Fruit.APPLE)+" ");
System.out.print(fruit.equals(Fruit.APPLE)+" ");
System.out.println(fruit == Fruit.APPLE);
System.out.println(fruit.getDeclaringClass());
System.out.println(fruit.name());
System.out.println("------------------------------");
/**
* 0
* 0 true true
* class org.example.enummmm.Fruit
* APPLE
* ------------------------------
* 1
* 1 false false
* class org.example.enummmm.Fruit
* BANANA
* ------------------------------
* 2
* 2 false false
* class org.example.enummmm.Fruit
* ORANGE
* ------------------------------
*/
}
/**
* APPLE
* BANANA
* ORANGE
*/
}
}
enum Fruit{
APPLE,BANANA,ORANGE
}
向enum中添加新方法
除了不能继承自一个enum之外,我们基本上可以将enum看作一个常规的类,也就是说,我们可以向enum中添加方法。enum甚至可以有main方法。
一般来说,我们希望每个枚举实例都能够携带一些对自身的描述信息,而不仅仅是作为一个简单的标识集合。为此,我们可以提供一个构造器,专门负责处理这个额外的信息,然后添加一个方法,返回这个描述信息。
public class EnumDemo2 {
public static void main(String[] args) {
for(Car car:Car.values()){
System.out.println(car.name()+": "+car.getBrand());
}
/**
* BMW: 宝马
* BENZ: 奔驰
*/
}
}
enum Car{
BMW("宝马"),
BENZ("奔驰");
private String brand;
Car(String brand){this.brand = brand;}
public String getBrand(){
return brand;
}
}
switch语句中的enum
在switch语句中使用枚举类是一个非常便利的方法,它可以通过枚举类来限定switch的选项
public class EnumDemo3 {
public static void main(String[] args) {
Fruit fruit = Fruit.APPLE;
switch (fruit){
case APPLE:
System.out.println("这是苹果");
break;
case BANANA:
System.out.println("这是香蕉");
break;
case ORANGE:
System.out.println("这是香蕉");
break;
default:
break;
}
/**
* 这是苹果
*/
}
}
enum Fruit{
APPLE,BANANA,ORANGE
}
values()的神秘之处
前面提到,我们的enum类全部都继承自Enum类。然而,如果看一下Enum类我们就会发现并没有values()方法。事实上,values()是由编译器添加的static方法。因此,如果将enum类向上转型为Enum将无法使用values()方法。不过,在Class中有一个getEnumConstants()方法,所以即使不使用values()我们仍然可以通过Class对象取获取所有enum实例
public static void main(String[] args) {
for(Fruit fruit:Fruit.class.getEnumConstants()){
System.out.println(fruit.name());
}
}
enum Fruit{
APPLE,BANANA,ORANGE
}
使用接口组织枚举
有些情况下我们会希望通过枚举来进行分组,但由于枚举类无法被继承,所以我们可以这么做
public static void main(String[] args) {
Food food = Food.Meat.BEEF;
}
interface Food{
enum Fruit implements Food{
APPLE,ORANGE
}
enum Meat implements Food{
CHICKEN,BEEF
}
}
对于枚举类而言,实现接口是其子类化的唯一方法。因此,现在Food接口中的所有类型它都可以认为是一个食物
但是当我们与一大堆类打交道时,接口就不如enum好用了。因此我们还有一种方式来创建一种枚举的枚举
public class EnumDemo4 {
public static void main(String[] args) {
for (FoodKind kind:FoodKind.values()){
for (Food food:kind.getValues()){
System.out.println(kind+": "+food);
}
}
}
interface Food{
enum Fruit implements Food{
APPLE,ORANGE
}
enum Meat implements Food{
CHICKEN,BEEF
}
}
enum FoodKind{
FRUIT(Food.Fruit.class),
MEAT(Food.Meat.class);
private Food[] values;
FoodKind(Class<? extends Food> kind){
values = kind.getEnumConstants();
}
public Food[] getValues(){
return values;
}
}
}
在上面例子中我们充分利用了可以通过Class来获取枚举实例的特性来完成枚举中的枚举这一结构。
首先,如果我们希望一个类中可以嵌套另一种类,同时通过这种结构实现分组。我们通常会选择使用继承来实现,比如Meat类继承Food类。但是很明显,枚举类之间不存在继承关系,所以我们只能退而求其次,通过接口来实现一个继承结构。当我们通过在Food接口实现多个枚举类时,看似已经完成了分组。事实上这种分组并没有完全使用到枚举类的便利性(values(),枚举类实例等)。因此我们选择让一个新的枚举类去持有Food对象的类引用来完成这种包含关系。因为通过类对象就可以完成values()的功能。
使用EnumSet来代替标志
Set是一种集合,只能向其中添加不重复的对象。当然,enum也要求其成员都是唯一的,所以enum看起来也具有集合的行为。不过由于不能从enum中增加或者删除元素。因此这种集合并没什么用。JavaSE5引入了EnumSet,是为了通过enum创建一种替代品,以替代传统的基于int的”位标志“。这种标志可用来标识某种开关信息。
EnumSet的设计充分考虑到了速度因素,因为它必须和非常高效的bit标志相竞争(其操作与HashSet相比,非常的快)。
EnumSet中的元素顺序是与枚举中声明的顺序是一致的
EnumSet中的元素必须来自于一个enum。下面的enum表示在一座大楼中,警报传感器的安放位置
enum AlarmPoints{
STAIR1,STAIR2,LOBBY,OFFICE1,OFFICE2,OFFICE3,
OFFICE4,BATHROOM,KITCHEN
}
然后我们使用EnumSet来跟踪警报的状态
public static void main(String[] args) {
EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class);
points.add(BATHROOM);
points.addAll(EnumSet.of(OFFICE1,OFFICE2,OFFICE3));
System.out.println(points);
points = EnumSet.allOf(AlarmPoints.class);
points.remove(BATHROOM);
System.out.println(points);
points = EnumSet.complementOf(points);
System.out.println(points);
/**
* [OFFICE1, OFFICE2, OFFICE3, BATHROOM]
* [STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, KITCHEN]
* [BATHROOM]
*/
}
使用EnumMap
EnumMap是一种特殊的Map,它要求其中的键必须来自于enum,由于enum本身的限制,所以EnumMap在内部由数组实现。因此EnumMap的速度很快。棕的来说操作起来与一般的Map差不多。
public static void main(String[] args) {
EnumMap<Fruit,String> map = new EnumMap<Fruit, String>(Fruit.class);
map.put(Fruit.APPLE,"新疆阿克苏苹果");
map.put(Fruit.BANANA,"泰国大香蕉");
System.out.println(map.get(Fruit.BANANA));
/**
* 泰国大香蕉
*/
}
enum Fruit{
APPLE,ORANGE,BANANA
}
EnumMap中的元素顺序是与枚举中声明的顺序是一致的。同时enum的每个实例作为键,总是存在的,但是如果没有为其调用put存入相应的值,其对应的值就是null
常量相关的方法
Java的enum有一个非常有意思的特性,即它允许我们为enum实例编写方法,从而为每个实例赋予各自不同的行为。要实现常量相关的方法,我们需要为enum定义一个或多个抽象方法,然后为每个实例实现该抽象方法。
public class EnumDemo7 {
public static void main(String[] args) {
for(ConstantSpecificMethod method:ConstantSpecificMethod.values()){
System.out.println(method.getInfo());
}
}
enum ConstantSpecificMethod{
DATE_TIME{
@Override
String getInfo(){
return DateFormat.getInstance().format(new Date());
}
},
VERSION{
@Override
String getInfo(){
return System.getProperty("java.version");
}
};
abstract String getInfo();
}
}
在面向对象的设计中,不同的行为与不同的类关联接。而通过常量相关的方法,每个enum实例可以具有自己独特的行为。在上面例子中,enum实例似乎被当作了其基类ConstantSpecificMethod来使用,在调用getInfo()方法时体现出多态的行为。
然而,虽然这种形式与继承中的多态相似,但事实上实例终究只是实例,无法真正地被当作一个类型进行使用。因为enum的每个实例都是enum类型的static final实例。
覆盖常量相关的方法
除了实现抽象方法以外,我们还可以覆盖常量相关的方法
public class EnumDemo8 {
public static void main(String[] args) {
for(OverrideConstantSpecific method:OverrideConstantSpecific.values()){
System.out.println(method.getInfo());
}
/**
* 这是一种好吃的水果!
* 这是一种好吃的香蕉
*/
}
enum OverrideConstantSpecific{
APPLE,
BANANA{
@Override
public String getInfo() {
return "这是一种好吃的香蕉";
}
};
public String getInfo(){
return "这是一种好吃的水果!";
}
}
}
使用enum的职责链
在职责链模式中我们可以使用不同的方式来解决一个问题,然后将它们链接在一起。当一个请求到来时,它遍历这个链,直到链中的某个解决方案能够处理该请求。
而通过常量相关方法我们就能够实现一个类多个解决方案。而对于链的遍历我们也可以通过values()方法来迅速实现。
举例,在公司里,我们经常会遇到有急事需要请假的情况,那这时候我们需要上上级提出请假申请,如果上级同意我们才能被允许请求。但是不同的请假天数需要不同级别的上级批准,你如果请一天,通常组长同意即可,如果请三天,可能就可能不仅需要组长的同意还需要部门老大的同意,如果请一个月,那还需要加上老板的同意(但是注意,请这么久,老板大概率时让你走人的)。因此我们现在根据这个例子实现
首先我们定义一个假条类,包含请假的天数和请假的理由
class RequestForLeave{
private Integer day;
private String reason;
public RequestForLeave(Integer day, String reason) {
this.day = day;
this.reason = reason;
}
//省略getset
}
然后定义处理请求的enum链。
enum RequestHandler{
GROUP_HEAD{
@Override
public boolean handle(RequestForLeave request) {
if(request.getDay()<3){
return true;
}
System.out.println("请假超过3天,组长处理不了");
return false;
}
},
DEPARTMENT_HEAD{
@Override
public boolean handle(RequestForLeave request) {
if(request.getDay()<30 && request.getDay()>=3){
return true;
}else if(request.getDay() <3){
System.out.println("请假时间太短了,不想处理");
return false;
}
System.out.println("请假超过30天,部长处理不了");
return false;
}
},
BOSS{
@Override
public boolean handle(RequestForLeave request) {
if(request.getDay()>=30){
System.out.println("请假时间太长了,还干啥。去财务领这个月薪水走人吧");
return false;
}
System.out.println("请假时间太短了,不想处理");
return false;
}
};
abstract boolean handle(RequestForLeave request);
}
场景类
现在我们有两份假条,一份时10天的,要回家二婚,因此组长拿到请求时发现处理不了,然后到了部长手里,部长发现有一个10天的假条,是要去二婚,想想下属太辛苦了,二婚还是要放的。但是发现了第二封要请100天。那部长哪敢处理,只能等着老板来处理,老板发现,好家伙,要请100天。直接滚蛋吧。
public class EnumDemo9 {
public static void main(String[] args) {
RequestForLeave request1 = new RequestForLeave(10,"二婚");
RequestForLeave request2 = new RequestForLeave(100,"八十大寿");
handle(request1);
System.out.println("-------------------");
handle(request2);
/**
* 请假超过3天,组长处理不了
* 请假成功
* -------------------
* 请假超过3天,组长处理不了
* 请假超过30天,部长处理不了
* 请假时间太长了,还干啥。去财务领这个月薪水走人吧
* 请假失败!
*/
}
public static void handle(RequestForLeave request){
for (RequestHandler handler:RequestHandler.values()){
if(handler.handle(request)) {
System.out.println("请假成功");
return;
}
}
System.out.println("请假失败!");
}
}