目录html
本文转载自博客 - Java枚举类型, 博主对原文内容及结构做了必定的修改.java
修改时参考到的文章:深刻理解Java枚举类型(enum).数组
从JDK 5开始, Java中多了一个关键字 —— enum
: 能够将一组具备名称的值(包括String、Integer等)的有限集合建立为一种新的类型, 而这些具名的值能够做为常规的程序组件使用.dom
这些具名的值称为枚举值, 这种新的类型称为枚举类型.ide
下面是一个简单的表示星期几的枚举类:函数
enum Day { // 结尾处能够不用“;”, 但如有其余方法, 必须经过“;”结束枚举实例的声明 SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }
建立enum时, 编译器会自动为enum类添加一些特性, 好比:工具
a) 建立
toString()
方法: 以便显示某个枚举实例的名字;测试b) 建立
name()
方法: 获取枚举类型中某个实例的名称;thisc) 建立
ordinal()
方法: 表示某个特定枚举常量的申明顺序, 从0开始;.netd) 建立
values()
方法: 按照枚举常量的申明顺序产生这些常量构成的数组…...
关于这些特性, 编写测试方法以下:
public class EnumTest { public static void main(String[] args) { // 获取枚举类型的父类型 System.out.println(Day.class.getSuperclass()); // 遍历枚举类型中的值 for (Day day : Day.values()) { System.out.println(day.name() + ", ordinal: " + day.ordinal()); } } }
程序的输出结果是:
class java.lang.Enum // 全部enum的父类都是它 SUNDAY, ordinal: 0 // ordinal()方法从0开始计数 MONDAY, ordinal: 1 TUESDAY, ordinal: 2 WEDNESDAY, ordinal: 3 THURSDAY, ordinal: 4 FRIDAY, ordinal: 5 SATURDAY, ordinal: 6
从输出结果能够看到: 枚举类型Day的父类是java.lang.Enum
——咱们的代码中并无指明extends
动做, 因此这是由编译器完成的.
(1) 利用javac
编译EnumTest.java
文件后, 会生成Day.class
和EnumTest.class
文件, 这里的Day.class
就是枚举类型 —— 使用关键字enum
定义枚举类型并编译后, 编译器会自动生成一个与枚举相关的类;
(2) 反编译查看Day.class
文件:
// final修饰的类, 不容许再被继承 final class Day extends Enum { // 编译器添加的静态values()方法 public static Day[] values() { return (Day[])$VALUES.clone(); } // 编译器添加的静态valueOf()方法 —— 间接调用了Enum类的valueOf()方法 public static Day valueOf(String s) { return (Day)Enum.valueOf(com/healchow/java/Day, s); } // 私有构造函数, 不容许被外部调用 private Day(String s, int i) { super(s, i); } // 前面定义的7种枚举实例, 都是静态常量 public static final Day MONDAY; public static final Day TUESDAY; public static final Day WEDNESDAY; public static final Day THURSDAY; public static final Day FRIDAY; public static final Day SATURDAY; public static final Day SUNDAY; private static final Day $VALUES[]; static { // 实例化枚举实例, 并指定声明的次序 MONDAY = new Day("MONDAY", 0); TUESDAY = new Day("TUESDAY", 1); WEDNESDAY = new Day("WEDNESDAY", 2); THURSDAY = new Day("THURSDAY", 3); FRIDAY = new Day("FRIDAY", 4); SATURDAY = new Day("SATURDAY", 5); SUNDAY = new Day("SUNDAY", 6); $VALUES = (new Day[] { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }); } }
(3) 编译器生成的静态方法:
①
values()
: 获取枚举类中的全部变量, 并做为数组返回;
②valueOf(String name)
: 与Enum类中的valueOf()
的做用相似 —— 根据名称获取枚举变量, 只不过编译器生成的valueOf()
更简洁: 只需传递一个参数.==> 这两个方法是编译器自动加入的, Enum类中并无, 因此若是咱们将枚举实例向上转型为Enum, 那这两个方法就没法被调用了.
(4) 枚举类不能继承其余任何类:
因为枚举类已经继承了java.lang.Enum
(是一个抽象类), 而Java又不支持多继承, 因此enum
不能再继承其余的类, 可是能实现接口. —— 这在反编译后的信息中能够看到, 编译器为enum生成的class被final修饰, 也就是不容许被继承.
==> 因此, enum
只是看起来像一种新的数据类型, 除了上面讲到的这些特殊的编译行为, 并无什么特殊的地方.
除了不能继承一个enum
外, 基本上能够把enum
看成一个普通的类来处理, 也就是说能够向enum
中添加方法, 好比返回其自身描述的方法, 还能够添加main方法.
下面是一个演示enum
添加自定义方法和实现接口的示例:
(1) 定义一个对象描述信息的接口:
interface ObjectDescription { String todo(); }
(2) 建立枚举类:
public enum Signal implements ObjectDescription { // 结尾处能够不用“;”, 但如有其余方法, 必须经过“;”结束枚举实例的声明 Red("红灯", "敢过去就是6分, 还要罚款哦"), Yellow("黄灯", "黄灯你也不敢过"), Green("绿灯", "绿灯也得当心过啊"); // 其余属性、方法都必须定义在枚举实例的声明以后, 不然编译器将报错 private String name; private String description; /** * 构造方法, 对内部变量进行初始化 */ Signal(String name, String description) { this.name = name; this.description = description; } public String getName() { return name; } public String getDescription() { return description; } @Override public String todo() { return "Signal类用于表示交通讯号灯, [" + this + "] 表示 [" + this.getName() + "]"; } public static void main(String[] args) { // 调用实现的接口中的方法 for (Signal signal : Signal.values()) { System.out.println(signal.todo()); } // 调用自定义的方法 for (Signal signal : Signal.values()) { System.out.println(signal.getName() + ": " + signal.getDescription()); } } }
(3) 运行结果以下:
Signal类用于表示交通讯号灯, [Red] 表示 [红灯] Signal类用于表示交通讯号灯, [Yellow] 表示 [黄灯] Signal类用于表示交通讯号灯, [Green] 表示 [绿灯] 红灯: 敢过去就是6分, 还要罚款哦 黄灯: 黄灯你也不敢过 绿灯: 绿灯也得当心过啊
使用注意事项:
a) 若是要自定义方法, 就必须在enum实例序列的最后添加一个分号, 同时Java要求必须先定义enum实例, 不然编译时会报错.
b)
enum
的构造器只能是private
, 它只能在enum
内部被用来建立enum
实例, 一旦enum
定义结束, 编译器就不容许再使用其构造器来建立任何实例了.
与常规抽象类同样, 枚举类容许咱们为其定义抽象方法, 而后灵每一个枚举实例都实现该方法, 以便产生不一样的行为方式.
注意:
abstract
关键字对于枚举类来讲并非必须的.
public enum EnumTest { // 枚举实例的声明必须在最前 FIRST { // 实现抽象方法 @Override public String getInfo() { return "FIRST TIME"; } }, SECOND { // 实现抽象方法 @Override public String getInfo() { return "SECOND TIME"; } } ; // 若是以后还有其余成员, 就必须用“;”结束 /** * 定义抽象方法 * @return */ public abstract String getInfo(); // 测试 public static void main(String[] args) { System.out.println("First: " + EnumTest.FIRST.getInfo()); // First: FIRST TIME System.out.println("Second: " + EnumTest.SECOND.getInfo()); // Second: SECOND TIME } }
上述方式为每一个枚举实例定义了不一样的行为.
咱们可能注意到, 枚举类的实例彷佛表现出了多态的特性, 惋惜的是枚举类型的实例终究不能做为类型传递使用, 就像下面的使用方式, 是不能经过编译器的检查的:
// 没法经过编译, 毕竟EnumTest.FIRST是个实例对象 public void text(EnumTest.FIRST instance){ }
没法使用继承限制了枚举的使用, 好比须要用enum
表示食物, 但同时须要区分水果、点心等类型, 这个时候就没不够灵活了.
咱们经过在一个接口内部建立实现该接口的枚举, 从而达到对元素进行分类组织的目的:
public interface Food { /** * 开胃菜 */ enum Appetizer implements Food { // 结尾处能够不用“;”, 但如有其余方法, 必须经过“;”结束枚举实例的声明 SALAD, SOUP, SPRING_ROLLS } /** * 主菜 */ enum MainCourse implements Food { RICE, NOODLES, VINDALOO, BEEF } /** * 甜品 */ enum Dessert implements Food { TIRAMISU, ICE_CREAM, BISCUIT, FRUIT } /** * 咖啡 */ enum Coffee implements Food { BLACK_COFFEE, DECAF_COFFEE, LATTE } }
对enum
而言, 实现接口是使其子类化的惟一方法.
经过上面的形式, 成功地完成了对不一样食物的分组, 而且它们都是Food.
下面是一个枚举的随机选择器, 是一个工具类:
public class Enums { private static Random rand = new Random(47); public static <T extends Enum<T>> T random(Class<T> enumClazz) { return random(enumClazz.getEnumConstants()); } public static <T> T random(T[] values) { return values[rand.nextInt(values.length)]; } }
结合工具类及上面Food接口的内容, 下面经过枚举的枚举实现一个产生随机菜单的例子:
public enum Course { // 结尾处能够不用“;”, 但如有其余方法, 必须经过“;”结束枚举实例的声明 APPETIZER(Food.Appetizer.class), MAINCOURSE(Food.MainCourse.class), DESSERT(Food.Dessert.class), COFFEE(Food.Coffee.class); // 其余属性、方法都必须定义在枚举实例的声明以后, 不然编译器将报错 private Food[] values; Course(Class<? extends Food> kind) { // 返回枚举中全部的元素, 及全部实例构成的数组, 若是kind不是枚举返回null values = kind.getEnumConstants(); } public Food randomSelection() { return Enums.random(values); } public static void main(String[] args) { // 产生5份随机菜单 for (int i = 0; i < 5; i++) { for (Course c : Course.values()) { Food food = c.randomSelection(); System.out.println(food + " "); } System.out.println("---------------"); } } }
下面的代码用来验证values()
方法是enum
自身的, 而不是继承自父类java.lang.Enum
的:
public enum Signal implements ObjectDescription { // 结尾处能够不用“;”, 但如有其余方法, 必须经过“;”结束枚举实例的声明 Red("红灯", "敢过去就是6分, 还要罚款哦"), Yellow("黄灯", "黄灯你也不敢过"), Green("绿灯", "绿灯也得当心过啊"); // 其余属性、方法都必须定义在枚举实例的声明以后, 不然编译器将报错 private String name; private String description; Signal(String name, String description) { this.name = name; this.description = description; } public String getName() { return name; } public String getDescription() { return description; } @Override public String todo() { return "Signal类用于表示交通讯号灯, [" + this + "] 表示 [" + this.getName() + "]"; } public static void main(String[] args) { Set<Method> methodSet = new HashSet<Method>(); // 获取本类的全部方法 Method[] signalMethods = Signal.class.getMethods(); for (Method m : signalMethods) { methodSet.add(m); } // 获取父类中的方法 Method[] superClassMethods = Signal.class.getSuperclass().getMethods(); // 去除本类中继承的父类方法 for (Method m : superClassMethods) { methodSet.remove(m); } // 遍历输出本类中独有的方法 for(Method m : methodSet) { System.out.println(m); } } }
结果以下, 其中各个字段的含义依次为访问权限 [是否静态] 返回值类型的全限定名称 方法的全限定名称
:
public static com.healchow.Signal com.healchow.Signal.valueOf(java.lang.String) public static com.healchow.Signal[] com.healchow.Signal.values() public static void com.healchow.Signal.main(java.lang.String[]) public java.lang.String com.healchow.Signal.todo()
版权声明
本文版权归原做者全部, 若有侵权, 请联系博主, 定当当即删除.
若要转载, 请在文章页面明显位置标明原始连接, 不然一切责任自负.