JAVA 枚举深入理解

以下笔记均出自 Thinking in java。


关键词enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。


1 基本的enum特性

values() : 遍历enum实例,返回enum实例的数组,数组中的元素严格保持其在enum中声明的顺序。
ordinal(): 返回一个int值,这是每个enum实例在声明时的顺序,从0开始。
equals()、hashCode()、compareTo():编译器自动提供的方法,因Enum类实现了Comparable接口,所以具有compareTo()方法,同时,它还实现了Serializable接口。
getDeclaringClass():获取实例所属的enum类。
name():返回实例enum实例声明时的名字,与toString()方法 效果相同。
使用例子 : https://github.com/xu509/Java-practise/blob/master/src/enumerated/EnumClass.java

1.1 将静态导入用于enum

使用静态导入(static import)可以将enum实例的标示符带入当前的命名空间,大部分的情况下,这种使用方法是方便的,但会增加代码复杂度。
例子:
//: enumerated/Spiciness.java
package enumerated;

public enum Spiciness {
  NOT, MILD, MEDIUM, HOT, FLAMING
} ///:~
//: enumerated/Burrito.java
package enumerated;
import static enumerated.Spiciness.*;

public class Burrito {
  Spiciness degree;
  public Burrito(Spiciness degree) { this.degree = degree;}
  public String toString() { return "Burrito is "+ degree;}
  public static void main(String[] args) {
    System.out.println(new Burrito(NOT));
    System.out.println(new Burrito(MEDIUM));
    System.out.println(new Burrito(HOT));
  }
} /* Output:
Burrito is NOT
Burrito is MEDIUM
Burrito is HOT
*///:~
注意,在定义enum的同一个文件中,这种技巧无法使用;如果是在默认包中定义enum,这种技巧也无法使用。(暂不理解)

2 向enum中添加新方法


除了不能继承自一个enum之外,我们基本上可以把一个enum看做常规类,我们可以添加方法,甚至是main方法。
例:
public enum OzWitch {
    // Instances must be defined first, before methods:
    WEST("Miss Gulch, aka the Wicked Witch of the West");
    private String description;

    // Constructor must be package or private access:
    private OzWitch(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    public static void main(String[] args) {
        for (OzWitch witch : OzWitch.values())
            print(witch + ": " + witch.getDescription());
    }
}
注意: 1、如果打算定义自己的方法,就必须要在enum实例序列的最后添加一个分号。
     2、必须先定义enum实例,后定义任何方法或属性,否则编译报错。
     3、enum的构造器可访问性局限在private或者包属性,一旦该类编译结束,编译器就不允许我们再使用其构造器创建任何实例

2.1 覆盖enum方法

这里有一个例子,获取枚举属性的首字母大写字符串

public enum SpaceShip {
  SCOUT, CARGO, TRANSPORT, CRUISER, BATTLESHIP, MOTHERSHIP;
  public String toString() {
    String id = name();
    String lower = id.substring(1).toLowerCase();
    return id.charAt(0) + lower;
  }
  public static void main(String[] args) {
    for(SpaceShip s : values()) {
      System.out.println(s);
    }
  }
}

3 switch语句中的enum

在switch语句中使用enum,是enum提供的一项很遍历的功能。可提供一个小型状态机。
 Signal color = Signal.RED;

    public void change() {
        switch (color) {
            // Note that you don't have to say Signal.RED
            // in the case statement:
            case RED:
                color = Signal.GREEN;
                break;
            case GREEN:
                color = Signal.YELLOW;
                break;
            case YELLOW:
                color = Signal.RED;
                break;
        }
    }
完整代码:https://github.com/xu509/Java-practise/blob/master/src/enumerated/TrafficLight.java

4 values()神秘之处

所有的enum类都继承自Enum类,但是Enum类中没有values()方法,于是我们利用反射来观察其中究竟:
详细代码: https://github.com/xu509/Java-practise/blob/master/src/enumerated/Reflection.java
values()是由编译器添加的static方法。

由于Enum没有values()方法,所以通过反射获取枚举类时,即使将实例上转型成Enum也无法获取每一个enum实例,这时候可以用Class中的getEnumConstants()方法。
package enumerated;//: enumerated/UpcastEnum.java
// No values() method if you upcast an enum

enum Search {HITHER, YON}

public class UpcastEnum {
    public static void main(String[] args) {
        Search[] vals = Search.values();
        Enum e = Search.HITHER; // Upcast
        // e.values(); // No values() in Enum
        for (Enum en : e.getClass().getEnumConstants())
            System.out.println(en);
    }
} 

5 实现、而非继承

由于所有的enum都继承自java.lang.Enum类,而Java不支持多重继承,所以你的enum不能再继续其他类。

而我们创建一个新的enum时,可以同时实现一个或多个接口。
示例 :https://github.com/xu509/Java-practise/blob/master/src/enumerated/cartoons/EnumImplementation.java

6 随机选取

设计一个工具类,能随机选取枚举类中的枚举实例。
//: net/mindview/util/Enums.java
package net.mindview.util;

import java.util.Random;

public class Enums {
    private static Random rand = new Random(47);

    public static <T extends Enum<T>> T random(Class<T> ec) {
        return random(ec.getEnumConstants());
    }

    public static <T> T random(T[] values) {
        return values[rand.nextInt(values.length)];
    }
} ///:~
使用示例:
package enumerated;//: enumerated/RandomTest.java
import net.mindview.util.*;

enum Activity { SITTING, LYING, STANDING, HOPPING,
  RUNNING, DODGING, JUMPING, FALLING, FLYING }

public class RandomTest {
  public static void main(String[] args) {
    for(int i = 0; i < 20; i++)
      System.out.print(Enums.random(Activity.class) + " ");
  }
}

<T extends Enum<T>>表示T是一个enum的实例。

7 使用接口组织枚举

无法从enum继承子类令人沮丧,这种需求源自我们希望扩展原enum中的元素,有时希望将一个enum中的元素进行分组。
在一个接口内部,创建该接口的枚举,以此将元素进行分组,可以达到将枚举元素分类组织的目的。

假如我们想用enum来表示不同的食物,同时希望每个enum元素仍然保持Food类型。
package enumerated.menu;
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;
    }
}
如上例,实现接口是使其子类化的唯一办法。如下调用:
public class TypeOfFood {
  public static void main(String[] args) {
    Food food = Appetizer.SALAD;
    food = MainCourse.LASAGNE;
    food = Dessert.GELATO;
    food = Coffee.CAPPUCCINO;
  }
}

当你需要与一大堆类型打交道时,接口就没有enum好用了
public enum Course {
    APPETIZER(Food.Appetizer.class),
    MAINCOURSE(Food.MainCourse.class),
    DESSERT(Food.Dessert.class),
    COFFEE(Food.Coffee.class);
    private Food[] values;

    private Course(Class<? extends Food> kind) {
        values = kind.getEnumConstants();
    }

    public Food randomSelection() {
        return Enums.random(values);
    }
} 
如这个例子,每一个Course实例都将其对应的Class对象作为构造器,通过getEnumConstants()方法,可以从该Class对象中取得某个Food子类的所有enum实例。
调用生成菜单:
package enumerated.menu;

public class Meal {
  public static void main(String[] args) {
    for(int i = 0; i < 5; i++) {
      for(Course course : Course.values()) {
        Food food = course.randomSelection();
        System.out.println(food);
      }
      System.out.println("---");
    }
  }
}

更清晰的代码编写:
num SecurityCategory {
  STOCK(Security.Stock.class), BOND(Security.Bond.class);
  Security[] values;
  SecurityCategory(Class<? extends Security> kind) {
    values = kind.getEnumConstants();
  }
  interface Security {
    enum Stock implements Security { SHORT, LONG, MARGIN }
    enum Bond implements Security { MUNICIPAL, JUNK }
  }
  public Security randomSelection() {
    return Enums.random(values);
  }
  public static void main(String[] args) {
    for(int i = 0; i < 10; i++) {
      SecurityCategory category =
        Enums.random(SecurityCategory.class);
      System.out.println(category + ": " +
        category.randomSelection());
    }
  }
}

8 使用EnumSet替代标志

Java SE5引入EnumSet,是为了通过enum创建一种替代品,以替代传统的基于int的“位标志”。

例子:
//: enumerated/AlarmPoints.java
package enumerated;
public enum AlarmPoints {
  STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,
  OFFICE4, BATHROOM, UTILITY, KITCHEN
} ///:~

package enumerated;
import java.util.*;
import static enumerated.AlarmPoints.*;
import static net.mindview.util.Print.*;

public class EnumSets {
  public static void main(String[] args) {
    EnumSet<AlarmPoints> points =
      EnumSet.noneOf(AlarmPoints.class); // Empty set
    points.add(BATHROOM);
    print(points);
    points.addAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));
    print(points);
    points = EnumSet.allOf(AlarmPoints.class);
    points.removeAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));
    print(points);
    points.removeAll(EnumSet.range(OFFICE1, OFFICE4));
    print(points);
    points = EnumSet.complementOf(points);
    print(points);
  }
} /* Output:
[BATHROOM]
[STAIR1, STAIR2, BATHROOM, KITCHEN]
[LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM, UTILITY]
[LOBBY, BATHROOM, UTILITY]
[STAIR1, STAIR2, OFFICE1, OFFICE2, OFFICE3, OFFICE4, KITCHEN]
*///:~

9 使用EnumMap

EnumMap是一种特殊的Map,它要求其中的键(key)必须来自一个enum。

以下是EnumMap配合命令模式的例子:
interface Command { void action(); }

public class EnumMaps {
  public static void main(String[] args) {
    EnumMap<AlarmPoints,Command> em =
      new EnumMap<AlarmPoints,Command>(AlarmPoints.class);
    em.put(KITCHEN, new Command() {
      public void action() { print("Kitchen fire!"); }
    });
    em.put(BATHROOM, new Command() {
      public void action() { print("Bathroom alert!"); }
    });
    for(Map.Entry<AlarmPoints,Command> e : em.entrySet()) {
      printnb(e.getKey() + ": ");
      e.getValue().action();
    }
    try { // If there's no value for a particular key:
      em.get(UTILITY).action();
    } catch(Exception e) {
      print(e);
    }
  }
} /* Output:
BATHROOM: Bathroom alert!
KITCHEN: Kitchen fire!
java.lang.NullPointerException
*///:~

10 常量相关的方法

要实现常量相关的方法,你需要为enum定义一个或者多个abstract方法,然后为每个enum实例实现该方法。参考例子如下:

public enum ConstantSpecificMethod {
    DATE_TIME {
        String getInfo() {
            return
                    DateFormat.getDateInstance().format(new Date());
        }
    },
    CLASSPATH {
        String getInfo() {
            return System.getenv("CLASSPATH");
        }
    },
    VERSION {
        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());
    }
} /* (Execute to see output) *///:~

另一个例子:涉及abstract方法以及EnumSet特性,
https://github.com/xu509/Java-practise/blob/master/src/enumerated/CarWash.java

覆盖常量相关方法例子:
public enum OverrideConstantSpecific {
    NUT, BOLT,
    WASHER {
        void f() {
            print("Overridden method");
        }
    };

    void f() {
        print("default behavior");
    }

    public static void main(String[] args) {
        for (OverrideConstantSpecific ocs : values()) {
            printnb(ocs + ": ");
            ocs.f();
        }
    }
} /* Output:
NUT: default behavior
BOLT: default behavior
WASHER: Overridden method
*///:~

10.1 使用enum的职责链

在职责链(Chain of Responsibility)设计模式中,我们以不同的方式来解决一个问题,然后将它们链接到一起。当一个请求到来时,它遍历整个链,直到链中的某个解决方案能够处理该请求。
https://github.com/xu509/Java-practise/blob/master/src/enumerated/PostOffice.java

10.2 使用enum的状态机

枚举类型非常适合用来创建状态机。一个状态机可以具有有限个特定的状态,它通常根据输入,从一个状态转移到另一个状态,不过也可能存在瞬时状态,一旦任务执行结束,状态机就会立刻离开瞬时状态。
例子:https://github.com/xu509/Java-practise/blob/master/src/enumerated/Input.java
https://github.com/xu509/Java-practise/blob/master/src/enumerated/VendingMachine.java

11 多路分发

        Java只支持单路分发。也就是说,如果要执行的操作包含不止一个位置的对象时,Java的动态绑定机制只能处理其中一个的类型。我们必须要自己判定其他类型,从而实现自己的动态绑定(暂时不理解)。
        解决这个问题的办法是多路分发。多态只能发生在方法调用时,所以,如果你想使用两路分发,那么就必须有两个方法调用:第一个方法调用决定第一个位置类型,第二个方法调用决定第二个位置的类型。
以下是一个多路分发的例子:https://github.com/xu509/Java-practise/blob/master/src/enumerated/RoShamBo1.java

11.1 使用enum分发

使用enum实现多路分发,例子:
https://github.com/xu509/Java-practise/blob/master/src/enumerated/RoShamBo2.java
接口 :https://github.com/xu509/Java-practise/blob/master/src/enumerated/Competitor.java
   工具类:https://github.com/xu509/Java-practise/blob/master/src/enumerated/RoShamBo.java

11.2 使用常量相关的方法

使用常量相关方法完成分发:
例子1,https://github.com/xu509/Java-practise/blob/master/src/enumerated/RoShamBo3.java
优化后:https://github.com/xu509/Java-practise/blob/master/src/enumerated/RoShamBo4.java

11.3 使用EnumMap分发

实现例子: https://github.com/xu509/Java-practise/blob/master/src/enumerated/RoShamBo5.java

11.4 使用二维数组

https://github.com/xu509/Java-practise/blob/master/src/enumerated/RoShamBo6.java






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值