一、声明和使用基本枚举
枚举可以被认为是一个密封类的语法糖,该类在编译时仅实例化了若干次,以定义一组常量。
例子:一个简单的枚举列举出不同的季节:
public enum Season {
WINTER,
SPRING,
SUMMER,
FALL
}
我们可以在单独文件中声明枚举, 也可以在类中声明
public class Day {
private Season season;
public String getSeason() {
return season.name();
}
public void setSeason(String season) {
this.season = Season.valueOf(season);
}
private enum Season {
WINTER,
SPRING,
SUMMER,
FALL
}
}
枚举的每个常量默认情况下都是public、static和final的。由于每个常量都是静态的,因此可以使用enum名称直接访问它们。
枚举常数可以作为方法参数传递:
public static void display(Season s) {
System.out.println(s.name());//name()是一个内置方法,它获取枚举常量
}
display(Season.WINTER); // 打印 "WINTER"
可以使用values()方法获得枚举常量的数组。保证返回数组中的值按照声明顺序:
Season[] seasons = Season.values();
注意:这个方法每次调用时都会分配一个新的值数组。
迭代枚举常量:
public static void enumIterate() {
for (Season s : Season.values()) {
System.out.println(s.name());
}
}
可以在switch语句中使用枚举:
public static void enumSwitchExample(Season s) {
switch(s) {
case WINTER:
System.out.println("It's pretty cold");
break;
case SPRING:
System.out.println("It's warming up");
break;
case SUMMER:
System.out.println("It's pretty hot");
break;
case FALL:
System.out.println("It's cooling down");
break;
}
}
可以通过==来对比枚举:
Season.FALL == Season.WINTER; // false
Season.SPRING == Season.SPRING; // true
也可以通过equals对比:
Season.FALL.equals(Season.FALL); // true
Season.FALL.equals(Season.WINTER); // false
二、枚举的构造函数
枚举不能具有公共构造函数;但是,私有构造函数是可以接受的(枚举的构造函数默认是包私有的):
public enum Coin {
PENNY(1), NICKEL(5), DIME(10), QUARTER(25);
//注意,上面的括号和构造函数参数匹配
private int value;
Coin(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
int p = Coin.NICKEL.getValue(); // p = 5
如果你要实现一个枚举作为一个类,可以这样写:
public class Coin<T extends Coin<T>> implements Comparable<T>, Serializable {
public static final Coin PENNY = new Coin(1);
public static final Coin NICKEL = new Coin(5);
public static final Coin DIME = new Coin(10);
public static final Coin QUARTER = new Coin(25);
private int value;
private Coin(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
int p = Coin.NICKEL.getValue(); // p = 5
枚举常量在技术上是可变的,因此可以添加setter来更改枚举常量的内部结构。然而,这被认为是非常不好的做法,应该避免。
最佳实践是使枚举字段不可变,使用final:
public enum Coin {
PENNY(1), NICKEL(5), DIME(10), QUARTER(25);
private final int value;
Coin(int value){
this.value = value;
}
...
}
也可以在同一个枚举中定义多个构造函数。这样做的话,你在enum声明中传递的参数将决定调用哪个构造函数:
public enum Coin {
PENNY(1, true), NICKEL(5, false), DIME(10), QUARTER(25);
private final int value;
private final boolean isCopperColored;
Coin(int value){
this(value, false);
}
Coin(int value, boolean isCopperColored){
this.value = value;
this.isCopperColored = isCopperColored;
}
...
}
注意:所有非基本枚举字段都应该实现Serializable,因为枚举类实现了序列化。
枚举可以定义抽象方法,每个枚举成员都需要实现抽象方法。
enum Action {
DODGE {
public boolean execute(Player player) {
return player.isAttacking();
}
},
ATTACK {
public boolean execute(Player player) {
return player.hasWeapon();
}
},
JUMP {
public boolean execute(Player player) {
return player.getCoordinates().equals(new Coordinates(0, 0));
}
};
public abstract boolean execute(Player player);
}
这允许每个enum成员为给定的操作定义自己的行为,而不必在顶级定义中的方法中切换类型。
注意,此模式是使用多态性和/或实现接口通常实现的一种简短形式。
下面代码, 是一个enum,也是一个可调用函数,它根据预编译的正则表达式模式测试字符串输入。
import java.util.regex.Pattern;
import java.util.function.Predicate;
enum RegEx implements Predicate<String> {
UPPER("[A-Z]+"), LOWER("[a-z]+"), NUMERIC("[+-]?[0-9]+");
private final Pattern pattern;
private RegEx(final String pattern) {
this.pattern = Pattern.compile(pattern);
}
@Override
public boolean test(final String input) {
return this.pattern.matcher(input).matches();
}
}
public class Main {
public static void main(String[] args) {
System.out.println(RegEx.UPPER.test("ABC"));
System.out.println(RegEx.LOWER.test("abc"));
System.out.println(RegEx.NUMERIC.test("+111"));
}
}
enum的每个成员也可以实现该方法:
import java.util.function.Predicate;
enum Acceptor implements Predicate<String> {
NULL {
@Override
public boolean test(String s) {
return s == null;
}
},
EMPTY {
@Override
public boolean test(String s) {
return s.equals("");
}
},
NULL_OR_EMPTY {
@Override
public boolean test(String s) {
return NULL.test(s) || EMPTY.test(s);
}
};
}
public class Main {
public static void main(String[] args) {
System.out.println(Acceptor.NULL.test(null)); // true System.out.println(Acceptor.EMPTY.test("")); // true System.out.println(Acceptor.NULL_OR_EMPTY.test(" ")); // false
}
}
枚举常量在第一次引用枚举时实例化。因此,这允许使用单元素枚举实现单例设计模式。
public enum Attendant {
INSTANCE;
private Attendant() {
// 初始化
}
public void sayHello() {
System.out.println("Hello!");
}
}
public class Main {
public static void main(String... args) {
Attendant.INSTANCE.sayHello();// instantiated at this point
}
}
这种方法实现单例模式有以下优点:
如实现接口一节所示,这个单例也可能实现一个或多个接口。
枚举可以像任何类一样包含方法。为了了解它是如何工作的,我们将像这样声明一个枚举:
public enum Direction {
NORTH, SOUTH, EAST, WEST;
}
写一个方法,返回枚举在相反的方向:
public enum Direction {
NORTH, SOUTH, EAST, WEST;
public Direction getOpposite(){
switch (this){
case NORTH:
return SOUTH;
case SOUTH:
return NORTH;
case WEST:
return EAST;
case EAST:
return WEST;
default:
return null;
}
}
}
可以通过使用字段和静态初始化块进一步改进:
public enum Direction {
NORTH, SOUTH, EAST, WEST;
private Direction opposite;
public Direction getOpposite(){
return opposite;
}
static {
NORTH.opposite = SOUTH;
SOUTH.opposite = NORTH;
WEST.opposite = EAST;
EAST.opposite = WEST;
}
}
在本例中,反向存储在一个私有实例字段opposite中,该字段在第一次使用方向时被静态初始化。
在这种特殊的情况下(因为NORTH引用SOUTH,反之亦然),我们不能在这里将枚举与构造函数一起使用(构造函数NORTH(SOUTH)、SOUTH(NORTH)、SOUTH(NORTH)、EAST(WEST)、WEST(EAST)会更优雅,并且允许将opposite声明为final,但它是自引用的,因此是不允许的)。
当一个方法需要接受一组“可扩展”的枚举值时,可以像在普通类上应用多态性一样,创建一个接口,在枚举应使用的任何地方都可以使用这个接口:
public interface ExtensibleEnum {
String name();
}
这样,任何由接口(implementing)标记的枚举都可以用作参数,从而允许我们创建方法可接受的可变数量的枚举。这可能很有用,例如,在api中存在一个默认的(不可修改的)枚举,并且这些api的用户希望用更多的值“扩展”枚举。
一组默认enum值可以定义如下:
public enum DefaultValues implements ExtensibleEnum {
VALUE_ONE, VALUE_TWO;
}
然后可以这样定义附加值:
public enum ExtendedValues implements ExtensibleEnum {
VALUE_THREE, VALUE_FOUR;
}
演示如何使用枚举的示例——注意printEnum()如何接受来自这两种枚举类型的值:
private void printEnum(ExtensibleEnum val) {
System.out.println(val.name());
}
printEnum(DefaultValues.VALUE_ONE); // VALUE_ONE
printEnum(DefaultValues.VALUE_TWO); // VALUE_TWO
printEnum(ExtendedValues.VALUE_THREE); // VALUE_THREE
printEnum(ExtendedValues.VALUE_FOUR); // VALUE_FOUR
我们定义一个一周的枚举:
enum DayOfWeek {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
}
枚举使用内置方法valueOf(),该方法可用于根据常量的名称查找该常量:
String dayName = DayOfWeek.SUNDAY.name();
assert dayName.equals("SUNDAY");
DayOfWeek day = DayOfWeek.valueOf(dayName);
assert day == DayOfWeek.SUNDAY;
这也可以使用动态枚举类型:
Class<DayOfWeek> enumType = DayOfWeek.class;
DayOfWeek day = Enum.valueOf(enumType, "SUNDAY");
assert day == DayOfWeek.SUNDAY;
如果指定的枚举没有具有匹配名称的常量,这两个valueOf()方法都会抛出IllegalArgumentException。
Guava库提供了一个helper方法Enums.getIfPresent(),它返回一个可选的
Guava,以消除显式异常处理:
DayOfWeek defaultDay = DayOfWeek.SUNDAY;
DayOfWeek day = Enums.getIfPresent(DayOfWeek.class, "INVALID").or(defaultDay);
assert day == DayOfWeek.SUNDAY;
总结
enum 类型提出给 JAVA 编程带了了极大的便利,让程序的控制更加的容易,也不容易出现错误。所以在遇到需要控制程序流程时候,可以多想想是否可以利用 enum 来实现。