枚举类型: 就是由一组具备名的值的有限集合组成新的类型。(即新的类)。android
好像仍是不懂,别急,我们先来看一下 为何要引入枚举类型数组
在没有引入枚举类型前,当咱们想要维护一组 常量集合时,咱们是这样作的,看下面的例子:安全
class FavouriteColor_class{ public static final int RED = 1; public static final int BLACK = 3; public static final int GREEN = 2; public static final int BLUE = 4; public static final int WHITE = 5; public static final int BROWN = 6;}复制代码
当咱们有枚举类型后,即可以简写成:bash
//枚举类型public enum FavouriteColor { //枚举成员 RED,GREEN,BLACK,BLUE,WHITE,BROWN}复制代码
是否是很简单,很清晰。这样就能够省掉大量重复的代码,使得代码更加易于维护。cookie
如今有点明白枚举类型的定义了吧!在说的再仔细一点,就是 使用关键字enum来用 一组由常量组成的有限集合 来建立一个新的class类 。至于新的class类型,请继续往下看。app
上面仅仅简单地介绍了枚举类型的最简单的用法,下面咱们将逐步深刻,掌握枚举类型的复杂的用法,以及其原理。ide
上面的枚举类FavouriteColor里面的成员便都是枚举成员,换句话说,枚举成员 就是枚举类中,没有任何类型修饰,只有变量名,也不能赋值的成员。性能
到这里仍是对枚举成员很疑惑,咱们先将上面的例子进行反编译一下:ui
public final class FavouriteColor extends Enum { public static final FavouriteColor RED; public static final FavouriteColor GREEN; public static final FavouriteColor BLACK; public static final FavouriteColor BLUE; public static final FavouriteColor WHITE; public static final FavouriteColor BROWN;}复制代码
从反编译的结果能够看出,枚举成员都被处理成 public static final
的静态枚举常量。即上面例子的枚举成员都是 枚举类FavouriteColor
的实例。this
枚举类型在添加方法、构造器、非枚举成员时,与普通类是没有多大的区别,除了如下几个限制:
枚举成员必须是最早声明,且只能用一行声明(相互间以逗号隔开,分号结束声明)。
构造器的访问权限只能是private(能够不写,默认强制是private),不能是public、protected。
public enum FavouriteColor { //枚举成员 RED, GREEN(2), BLACK(3), BLUE, WHITE, BROWN;// 必需要有分号 // 非枚举类型的成员 private int colorValue; public int aa; // 静态常量也能够 public static final int cc = 2; //无参构造器 private FavouriteColor() { } //有参构造器 FavouriteColor(int colorValue) { this.colorValue = colorValue; } //方法 public void print() { System.out.println(cc); }}复制代码
能够看出,咱们实际上是可使用Eunm类型作不少事情,虽然,咱们通常只使用普通的枚举类型。
仔细看一下全部的枚举成员,咱们会发现GREEN(2)
, BLACK(3)
这两个枚举成员有点奇怪!其实也很简答,前面说了,枚举成员其实就是枚举类型的实例,因此,GREEN(2)
, BLACK(3)
就是指明了用带参构造器,并传入参数,便可以理解成 FavouriteColor GREEN = new FavouriteColor(2)
。其余几个枚举类型则表示使用无参构造器来建立对象。( 事实上,编译器会从新建立每一个构造器,为每一个构造器多加两个参数)。
枚举类型也是容许包含抽象方法的(除了几个小限制外,枚举类几乎与普通类同样),那么包含抽象方法的枚举类型的枚举成员是怎么样的,编译器又是怎么处理的?
咱们知道,上面的例子 FavouriteColor 类通过反编译后获得的类是一个继承了Enum的final类:
public final class FavouriteColor extends Enum 复制代码
那么包含抽象方法的枚举类型是否是也是被编译器处理成 final类,若是是这样,那有怎么被子类继承呢? 仍是处理成 abstract 类呢?
咱们看个包含抽象方法的枚举类的例子,Fruit 类中有三种水果,但愿能为每种水果输出对应的信息:
public enum Frutit { APPLE { @Override public void printFruitInfo() { System.out.println("This is apple"); } },BANANA { @Override public void printFruitInfo() { System.out.println("This is apple"); } },WATERMELON { @Override public void printFruitInfo() { System.out.println("This is apple"); } }; //抽象方法 public abstract void printFruitInfo(); public static void main(String[] arg) { Frutit.APPLE.printFruitInfo(); }}复制代码
运行结果:
This is apple
对于上面的枚举成员的形式也很容易理解,由于枚举成员是一个枚举类型的实例,上面的这种形式就是一种匿名内部类的形式,即每一个枚举成员的建立能够理解成:
BANANA = new Frutit("BANANA", 1) {//此构造器是编译器生成的,下面会说 public void printFruitInfo() {//匿名内部类的抽象方法实现。 System.out.println("This is apple"); } };复制代码
事实上,编译器确实就是这样处理的,即上面的例子中,建立了三个匿名内部类,同时也会多建立三个class文件.
最后,咱们反编译一下fruit类,看fruit类的定义:
public abstract class Frutit extends Enum复制代码
Fruit类被处理成抽象类,因此能够说,枚举类型通过编译器的处理,含抽象方法的将被处理成抽象类,不然处理成final类。
每个枚举类型都继承了Enum,因此是颇有必要来了解一下Enum;
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {//枚举成员的名称private final String name;//枚举成员的顺序,是按照定义的顺序,从0开始private final int ordinal;//构造方法protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; } public final int ordinal() {//返回枚举常量的序数 return ordinal; } } public final String name() {//返回此枚举常量的名称,在其枚举声明中对其进行声明。 return name; } public final boolean equals(Object other) { return this==other;//比较地址 }public final int hashCode() { return super.hashCode(); }public final int compareTo(E o) {//返回枚举常量的序数 //是按照次序 ordinal来比较的} public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { } public String toString() { return name; }复制代码
以上都是一些可能会用到的方法,咱们从上面能够发现两个有趣的地方:
Enum类实现了 Serializable 接口,也就是说能够枚举类型能够进行序列化。
Enum的几乎全部方法都是final方法,也就是说,枚举类型只能重写toString()方法,其余方法不能重写,连hashcode()、equal()等方法也不行。
上面说了这么多,都是片面地、简单地理解了枚举类型,但尚未彻底掌握枚举类型的本质,有了上面的基础,咱们将如鱼得水。
想要真正理解枚举类型的本质,就得了解编译器是如何处理枚举类型的,也就是老办法 -- 反编译。此次看一个完整的反编译代码,先看一个例子:
public enum Fruit { APPLE ,BANANA ,WATERMELON ; private int value; private Fruit() {//默认构造器 this.value = 0; } private Fruit(int value) {//带参数的构造器 this.value = value; }}复制代码
反编译的结果:
public final class Fruit extends Enum { //3个枚举成员实例 public static final Fruit APPLE; public static final Fruit BANANA; public static final Fruit WATERMELON; private int value;//普通变量 private static final Fruit ENUM$VALUES[];//存储枚举常量的枚举数组 static {//静态域,初始化枚举常量,枚举数组 APPLE = new Fruit("APPLE", 0); BANANA = new Fruit("BANANA", 1); WATERMELON = new Fruit("WATERMELON", 2); ENUM$VALUES = (new Fruit[]{APPLE, BANANA, WATERMELON}); } private Fruit(String s, int i) {//编译器改造了默认构造器 super(s, i); value = 0; } private Fruit(String s, int i, int value) {//编译器改造了带参数的构造器 super(s, i); this.value = value; } public static Fruit[] values() {//编译器添加了静态方法values() Fruit afruit[]; int i; Fruit afruit1[]; System.arraycopy(afruit = ENUM$VALUES, 0, afruit1 = new Fruit[i = afruit.length], 0, i); return afruit1; } public static Fruit valueOf(String s) {//编译器添加了静态方法valueOf() return (Fruit) Enum.valueOf(Test_2018_1_16 / Fruit, s); }}复制代码
从反编译的结果能够看出,编译器为咱们建立出来的枚举类作了不少工做:
- 对枚举成员的处理
编译器对全部的枚举成员处理成public static final
的枚举常量,并在静态域中进行初始化。
- 构造器
编译器从新定义了构造器,不只为每一个构造器都增长了两个参数,还添加父类了的构造方法调用。
- 添加了两个类方法
编译器为枚举类添加了 values()
和 valueOf()
。values()
方法返回一个枚举类型的数组,可用于遍历枚举类型。valueOf()
方法也是新增的,并且是重载了父类的valueOf()
方法
注意了: 正由于枚举类型的真正构造器是再编译时才生成的,因此咱们无法建立枚举类型的实例,以及继承扩展枚举类型(即便是被处理成abstract类)。枚举类型的实例只能由编译器来处理建立
Fruit fruit = Fruit.APPLE; switch (fruit) { case APPLE: System.out.println("APPLE"); break; case BANANA: System.out.println("BANANA"); break; case WATERMELON: System.out.println("WATERMELON"); break; }复制代码
实现接口就很少说了。枚举类型继承了Enum类,因此不能再继承其余类,但能够实现接口。
前面说了,枚举类型是没法被子类继承扩展的,这就形成没法知足如下两种状况的需求:
但愿扩展原来的枚举类型中的元素;
但愿使用子类对枚举类型中的元素进行分组;
看一个例子:对食物进行分类,大类是 Food,Food下面有好几种食物类别,类别上才是具体的食物;
public interface Food { enum Appetizer implements Food { SALAD, SOUP, SPRING_ROLLS } enum Coffee implements Food { BLACK_COFFEE, DECAF_COFFEE, ESPERSSO, TEA; } enum Dessert implements Food { FRUIT, GELATO, TIRAMISU; } }复制代码
接口Food做为一个大类,3种枚举类型作为接口的子类;Food管理着这些枚举类型。对于枚举而言,实现接口是使其子类化的惟一办法,因此嵌套在Food中的每一个枚举类都实现了Food接口。从而“全部这东西都是某种类型的Food”。
Food food = Food.Coffee.ESPERSSO;//ESPERSSO不只是coffee,也属于大类Food,达到分类的效果复制代码
对于序列化和反序列化,由于每个枚举类型和枚举变量在JVM中都是惟一的,即Java在序列化和反序列化枚举时作了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被编译器禁用的,所以,对于枚举单例,是不存在实现序列化接口后调用readObject会破坏单例的问题。因此,枚举单例是单利模式的最佳实现方式。
public enum EnumSingletonDemo { SINGLETON; //其余方法、成员等 public int otherMethod() { return 0; }}复制代码
单例的使用方式:
int a = EnumSingletonDemo.SINGLETON.otherMethod();复制代码
此处只是简单地介绍这两个类的使用,并不深刻分析其实现原理。
EnumSet是一个抽象类,继承了AbstractSet
类,其本质上就是一个Set。只不过,Enumset是要与枚举类型一块儿使用的专用 Set 实现。枚举 set 中全部键都必须来自单个枚举类型,该枚举类型在建立 set 时显式或隐式地指定。
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>复制代码
尽管JDK没有提供EnumSet
的实现子类,可是EnumSet
新增的方法都是static方法,并且这些方法都是用来建立一个EnumSet的对象。所以能够看作是一个对枚举中的元素进行操做的Set,并且性能也很高。看下面的例子:
public static void main(String[] args) { //建立对象,并指定EnumSet存储的枚举类型 EnumSet<FavouriteColor> set = EnumSet.allOf(FavouriteColor.class); //移除枚举元素 set.remove(FavouriteColor.BLACK); set.remove(FavouriteColor.BLUE); for(FavouriteColor color : set) {//遍历set System.out.println(color); }}复制代码
运行结果:
RED
GREEN
WHITE
BROWN
EnumSet不支持同步访问。实现线程安全的方式是:
Set<MyEnum> s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));复制代码
EnumMap
是一个类,一样也是与枚举类型键一块儿使用的专用 Map 实现。枚举映射中全部键都必须来自单个枚举类型,该枚举类型在建立映射时显式或隐式地指定。枚举映射在内部表示为数组。此表示形式很是紧凑且高效。
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>复制代码
简单使用的例子:
public static void main(String[] args) { EnumMap< FavouriteColor,Integer> map = new EnumMap<>(FavouriteColor.class); map.put(FavouriteColor.BLACK,1 ); map.put(FavouriteColor.BLUE, 2); map.put(FavouriteColor.BROWN, 3); System.out.println(map.get(FavouriteColor.BLACK));}复制代码
一样,防止意外的同步操做:
Map<EnumKey, V> m = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));复制代码
枚举类型继承于Enum类,因此只能用实现接口,不能再继承其余类。
枚举类型会编译器处理成 抽象类(含抽象方法)或 final类。
枚举成员都是public static final 的枚举实例常量。枚举成员必须是最早声明,且只能声明一行(逗号隔开,分号结束)。
构造方法必须是 private,若是定义了有参的构造器,就要注意枚举成员的声明。没有定义构造方法时,编译器为枚举类自动添加的是一个带两个参数的构造方法,并非无参构造器。
编译器会为枚举类添加 values() 和 valueOf()两个方法。
没有抽象方法的枚举类,被编译器处理成 final 类。若是是包含抽象方法的枚举类则被处理成抽象abstract类。
Enum实现了Serializable接口,而且几乎全部方法都是 final方法