什么是枚举
一组常量。
比如一年的 4 个季节,一年的 12 个月份,一个星期的 7 天,方向有东南西北等。
Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割。
例如定义一个颜色的枚举类。
enum Color
{ RED, GREEN, BLUE;
}
这时候一定有人会说,常量直接用static final来定义来定义不就好了,花里胡哨的搞个额外的枚举类有什么意义?
static final定义常量存在的问题
例如,定义3种颜色的常量:
public class Color {public static final String RED = "r";public static final String GREEN = "g";public static final String BLUE = "b";
}
使用常量的时候,可以这么引用:
String color = ...
if (Color.RED.equals(color)) {// TODO:
}
这样存在最大的问题:
编译器无法检查每个值的合理性
也就是我这里的color我想写什么字符串都可以。那这样我定义一个String color = “想写什么都行”,拿这种乱七八糟的值进行比较是没有任何意义的,但是程序依然不会在编译的时候给你提示有异常。
我们如果用枚举,就可以避免这种潜在的错误。
枚举代替static final定义常量
为了让编译器能自动检查某个值在枚举的集合内,并且,不同用途的枚举需要不同的类型来标记,不能混用,我们可以使用enum来定义枚举类:
public enum Color {RED, GREEN, BLUE
}
和String定义的常量相比,使用enum定义枚举有如下好处:
-
首先,enum常量本身带有类型信息,即Color.RED类型是Color,编译器会自动检查出类型错误。例如,下面的语句不可能编译通过:
-
其次,不可能引用到非枚举的值,因为无法通过编译。
这就使得编译器可以在编译期自动检查出所有可能的潜在错误。
enum和class区别
enum本质就是class,只不过是特殊的class,和其它class相比它有如下特点:
- 定义的enum类型总是继承自java.lang.Enum,且无法被继承;
- 只能定义出enum的实例,而无法通过new操作符创建enum的实例;
- 定义的每个实例都是引用类型的唯一实例;
- 可以将enum类型用于switch语句。
这个时候有人又要问了:你说enum就是class,为什么我定义一个类的时候,一般都得写成员变量、构造方法等,定义枚举比如下面这种,里面就写了几个用逗号分隔的大写“单词”?
public enum Color {RED, GREEN, BLUE;
}
是的,我们直接看编译器编译出来后的结果:
public final class Color extends Enum { // 继承自Enum,标记为final class// 每个实例均为全局唯一:public static final Color RED = new Color();public static final Color GREEN = new Color();public static final Color BLUE = new Color();// private构造方法,确保外部无法调用new操作符:private Color() {}
}
这么一看是不是就明白了:
- Color类是final的,就不能继成
- 构造方法的private的,就不能new
- 所谓大写的“单词”,其实就是Color唯一对象
所以,枚举就是在类基础上做了一些小小的改动,其实它就是类。枚举还有一些内置的方法。
枚举常用的方法
-
示例
// 调用 values()Color[] arr = Color.values();// 迭代枚举for (Color col : arr) {// 查看索引System.out.println(col + " at index " + col.ordinal());}// 使用 valueOf() 返回枚举常量,不存在的会报错 IllegalArgumentExceptionSystem.out.println(Color.valueOf("RED"));// System.out.println(Color.valueOf("WHITE"));
-
结果:
RED at index 0 GREEN at index 1 BLUE at index 2 RED
我们在使用ordinal方法的时候可能会有一个疑问:ordinal()可以找到每个枚举常量的索引,就像数组索引一样。但是如果不小心修改了枚举的顺序,编译器是无法检查出这种逻辑错误的,这是不是很危险吗?
的确是这样的,使用这个方法有个前提:我们认为枚举顺序的不会改变的。
但是程序是很多人在开发,谁也无法保证,我们在新增枚举的时候,每次都放在最后。
要编写健壮的代码,就不要依靠ordinal()的返回值。因为enum本身是class,所以我们可以定义private的构造方法,并且,给每个枚举常量添加字段,接下来我们看看详细操作。
给枚举常量加字段
枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用。
枚举既可以包含具体方法,也可以包含抽象方法。 如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它。
因此,我们这里说的给枚举常量加字段,说白了,就是给类加成员方法。
public enum Color {RED(255, 0, 0),GREEN(0, 255, 0),BLUE(0, 0, 255);private int red;private int green;private int blue;// 私有的构造方法Color(int red, int green, int blue) {this.red = red;this.green = green;this.blue = blue;}// 获取颜色值的方法public int getRed() {return red;}public int getGreen() {return green;}public int getBlue() {return blue;}
}
那么比如有其它人想要在这个枚举类中新增一个白色,那么他可以放在任何位置:
public enum Color {RED(255, 0, 0, 0),WHITE(0, 0, 0, 255),GREEN(0, 255, 0, 0),BLUE(0, 0, 255, 0);private int red;private int green;private int blue;private int white;// 私有的构造方法Color(int red, int green, int blue, int white) {this.red = red;this.green = green;this.blue = blue;this.white = white;}// 获取颜色值的方法public int getRed() {return red;}public int getGreen() {return green;}public int getBlue() {return blue;}public int getWhite() {return white;}
}
我们可以测试一下:
-
测试代码:
Color[] arr = Color.values();for (Color col : arr) {// 查看索引System.out.println(col + " at index " + col.ordinal());}System.out.println(Color.valueOf("WHITE"));
-
结果:
RED at index 0 WHITE at index 1 GREEN at index 2 BLUE at index 3 WHITE
在 switch 中使用枚举类
枚举类可以应用在switch语句中。因为枚举类天生具有类型信息和有限个枚举常量,所以比int、String类型更适合用在switch语句中。
-
代码
public class TestColor {public static void main(String[] args) {Color myColor = Color.RED;switch (myColor) {case RED:System.out.println("红色");break;case GREEN:System.out.println("绿色");break;case BLUE:System.out.println("蓝色");break;}} }
-
结果
红色