一、枚举类
1. 枚举类介绍
1.1 什么是枚举类
在java.lang
有一个Enum
表示枚举类,枚举类是java5
中新增特性的一部分,它是一种特殊的类,一般表示一组常量。
我们在使用枚举类的时候不用去实现Enum
,只需要在自定义枚举类的时候使用enum
关键字即可。这也是枚举类和其它类不同的原因,自定义的枚举类没有class
修饰而是enum
。
1.2 为什么使用枚举类
用枚举类实现的,我们也可以用常量来实现,那么使用枚举类的优点有哪些:意义明确,常量只是个变量,而枚举类可以加一些附属的描述信息;可以更好的归类,常量一般写在一个文件里面,而枚举类可以按照用途分类。
1.3 定义一个枚举类
定义枚举类有几个点需要注意
1.枚举类的修饰符是enum
不是class
2.枚举类的变量不需要new
来创建
3.枚举类必须要所有参数的构造方法,并且构造方法是private
public enum CoffeeEnum {
ZB(1, "中杯"),
DB(2, "大杯"),
CDB(3, "超大杯");
private int value;
private String desc;
CoffeeEnum(int value, String desc) {
this.value = value;
this.desc = desc;
}
public int getValue () {
return this.value;
}
public String getDesc () {
return this.desc;
}
}
2. 枚举类实现原理
上面的枚举类,我们先试用命令javac CoffeeEnum.java
编译生成CoffeeEnum.class
然后使用反编译工具JAD
去反编译CoffeeEnum.class
生成如下的代码,下载地址:JAD下载
当我们创建一个枚举类的时候,编译器会为我们生成一个相关的类,这个类继承了Enum
类,通过上面反编译的代码可以看到CoffeeEnum
这个类继承了Enum
类,同时我们在枚举类中定义的各个类型也变成了CoffeeEnum
对象,在静态方法中被初始化。
package com.lee.study.basic.enums;
public final class CoffeeEnum extends Enum{
public static CoffeeEnum[] values() {
return (CoffeeEnum[])$VALUES.clone();
}
public static CoffeeEnum valueOf(String s) {
return (CoffeeEnum)Enum.valueOf(com/lee/study/basic/enums/CoffeeEnum, s);
}
private CoffeeEnum(String s, int i, int j, String s1){
super(s, i);
value = j;
desc = s1;
}
public int getValue(){
return value;
}
public String getDesc(){
return desc;
}
public static final CoffeeEnum ZB;
public static final CoffeeEnum DB;
public static final CoffeeEnum CDB;
private int value;
private String desc;
private static final CoffeeEnum $VALUES[];
static {
ZB = new CoffeeEnum("ZB", 0, 1, "\u4E2D\u676F");
DB = new CoffeeEnum("DB", 1, 2, "\u5927\u676F");
CDB = new CoffeeEnum("CDB", 2, 3, "\u8D85\u5927\u676F");
$VALUES = (new CoffeeEnum[] {
ZB, DB, CDB
});
}
}
二、枚举类Enum详解
1. Enum介绍
Enum
是Java.lang
包下面的一个抽象类,我们自定义的枚举类都会自动的继承这个类。
2.Enum方法介绍
下面一一介绍枚举类的用法,先自定义一个枚举类。
public enum CoffeeEnum {
ZB(1, "中杯"),
DB(2, "大杯"),
CDB(3, "超大杯");
private int value;
private String desc;
CoffeeEnum(int value, String desc) {
this.value = value;
this.desc = desc;
}
public int getValue () {
return this.value;
}
public String getDesc () {
return this.desc;
}
}
2.1 valueOf(Class enumType, String name)
valueOf
方法是一个静态方法,有两个参数,一个是枚举类类型,一个是名称,返回的是一个枚举对象。
CoffeeEnum coffeeEnum = Enum.valueOf(CoffeeEnum.class, "CDB");
System.out.println(coffeeEnum.getDesc());
2.2 valuesOf(String s)
这个方法虽然也叫valuesOf
,但是参数只有一个,而且这个是编译器生成的,只需要传入枚举对象的名称,就可以返回对应的枚举对象。
CoffeeEnum coffeeEnum = CoffeeEnum.valueOf( "CDB");
System.out.println(coffeeEnum.getDesc());
2.3 values()
values
方法是以数组的形式返回所有的枚举对象。
CoffeeEnum[] coffeeEnums = CoffeeEnum.values( );
for (CoffeeEnum coffeeEnum : coffeeEnums) {
System.out.println(coffeeEnum.getDesc());
}
2.4 ordinal()
ordinal
方法是返回枚举对象的索引位置,从0开始。那么这个方法是什么时候初始化的时候,我们看一下反编译之后的代码,这里我们可以看到我们定义了两个变量,但是构造方法有四个变量,而且s
和j
直接传递给了父类,这里也就是初始化ordinal
的地方。
private CoffeeEnum(String s, int i, int j, String s1){
super(s, i);
value = j;
desc = s1;
}
2.5 name()
name
和ordinal
方法一样,都是在编译器加的参数里面初始化的,name
的值就是对象名称的字符串,索引就是从0开始,按照位置从上到下递增。
第一个参数就是name
,第二个就是ordinal
,第三个和第四个就是我们自己定义的value
和desc
。
ZB = new CoffeeEnum("ZB", 0, 1, "\u4E2D\u676F");
DB = new CoffeeEnum("DB", 1, 2, "\u5927\u676F");
CDB = new CoffeeEnum("CDB", 2, 3, "\u8D85\u5927\u676F");
三、枚举的应用
1. EnumMap
1.1 EnumMap介绍
EnumMap
是key
是一个枚举对象,value
是Object
的一个Map
,它实现了Map
接口,所以Map
接口的常用方法都具备。EnumMap
的key
是有范围的,只能是在枚举类中定义对象类型。
1.2 构造方法
EnumMap
有三个构造方法,其中第一个是接收一个枚举类类型,另外两个是接收一个Map
集合,EnumMap
的构造方法主要是初始化三个参数:
keyType
:作为Map
的key
的类型
keyUniverse
:是一个数组变量,存放所有key
值的数组
vals
:是一个Object
类型的数组变量,用于存放所有的value
EnumMap(Class<K> keyType);
EnumMap(EnumMap<K, ? extends V> m);
EnumMap(Map<K, ? extends V> m);
EnumMap<CoffeeEnum, String> enumMap = new EnumMap<>(CoffeeEnum.class);
enumMap.put(CoffeeEnum.CDB, "超大");
System.out.println(enumMap.get(CoffeeEnum.CDB));
1.3 put
put
方法首先要检查一下key
的类型是不是初始化的枚举类型,然后获取元素的索引,这个索引的位置也就是枚举对象的ordinal
,所以EnumMap
中元素的顺序和枚举类中定义对象的顺序一致,在EnumMap
中允许值是空的,但是不允许key
是空的,因为key
的范围已经固定了,就是在枚举类中的对象。
public V put(K key, V value) {
typeCheck(key);
int index = key.ordinal();
Object oldValue = vals[index];
vals[index] = maskNull(value);
if (oldValue == null)
size++;
return unmaskNull(oldValue);
}
1.4 remove
remove
方法直接获取key
的ordinal
,然后把对应位置的值置为NULL
public V remove(Object key) {
if (!isValidKey(key))
return null;
int index = ((Enum<?>)key).ordinal();
Object oldValue = vals[index];
vals[index] = null;
if (oldValue != null)
size--;
return unmaskNull(oldValue);
}
1.5 get
get
方法先判断key
是否是有效的,如果key
是无效的直接返回空,然后获取对应索引位置的值,这里使用了unmaskNull
方法作用是如果值不为空就返回值,否则就返回NULL
。
public V get(Object key) {
return (isValidKey(key) ?
unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);
}
2. EnumSet
2.1 EnumSet介绍
EnumSet
不是线程安全的
EnumSet
是一个用于存放枚举对象集合的抽象类,实现了Set
接口,不是一个线程安全的类。EnumSet
由于是一个抽象类,所以不能直接的使用new来创建一个集合。
EnumSet
有两个实现类:JumboEnumSet
和RegularEnumSet
2.2 创建EnumSet
虽然没有构造方法,但是我们还是可以创建一个EnumSet
对象,在EnumSet
里面提供了一些方法用于创建EnumSet
,大概有以下几种
noneOf
:创建一个空的集合
allOf
:创建一个枚举类所有对象的集合
of
:支持传多个对象的集合,of
有多个参数的重载,而不是直接用可变参数,因为可变参数的效率低一些,预留的几个重载方法已经够用了
range
:创建从枚举类某个位置到某个位置的对象的集合
这几个方法最后都是调用noneOf
方法先创建一个空的集合,然后再使用add
方法把元素添加进去,看一下noneOf
的源码
从这个源码我们可以看出,如果枚举类的对象个数小于等于64位的话,就用RegularEnumSet
,如果对象个数大于64个的话就用JumboEnumSet
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum<?>[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}
EnumSet
的对元素的操作都是通过位向量来实现的,关于位向量可以参考