简介
枚举类型是Java 5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性
枚举类使用
枚举的Class对象
需求:需要一次性获取到所有的枚举值对象:
DayEnum[] values = DayEnum.values();
DayEnum[] enumConstants = DayEnum.class.getEnumConstants();
// 判断某个class是否为枚举类型
System.out.println(DayEnum.MONDAY.getClass().isEnum());
enum中定义抽象方法
与常规抽象类一样,enum类也可以为其定义抽象方法,然后使每个枚举实例都实现该方法,以便产生不同的行为方式,注意abstract关键字对于枚举类来说是必须的。如下:
public enum EnumDemo {
FIRST {
@Override
public String getInfo() {
return "FIRST TIME";
}
},
SECOND {
@Override
public String getInfo() {
return "SECOND TIME";
}
};
public abstract String getInfo();
}
也可以向上抽取为一个接口
常用的7种方式
常量
在JDK1.5 之前,定义常量都是: public static final… 。现在好了,有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法
public enum Color {
// 若后续没有代码了,此;可以省略。否则不行
RED, GREEN, BLANK, YELLOW;
}
switch
JDK1.6之前的switch语句只支持int,char,enum类型,使用枚举,能让我们的代码可读性更强
enum Signal {
GREEN, YELLOW, RED
}
public class TrafficLight {
Signal color = Signal.RED;
public void change() {
switch (color) {
case RED:
color = Signal.GREEN;
break;
case YELLOW:
color = Signal.RED;
break;
case GREEN:
color = Signal.YELLOW;
break;
}
}
}
向枚举中添加新方法
如果打算自定义自己的方法,那么必须在enum实例序列的最后添加一个分号。而且 Java 要求必须先定义 enum 实例
public enum Color {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
@Getter
private String name;
@Getter
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
// 普通静态方法方法 可通过枚举类直接调用
public static String getName(int index) {
for (Color c : Color.values()) {
if (c.getIndex() == index) {
return c.name;
}
}
return null;
}
}
覆盖枚举的方法
下面给出一个toString()方法覆盖的例子
public enum Color {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
//覆盖方法
@Override
public String toString() {
return this.index+"_"+this.name;
}
}
实现接口(规范、统一控制非常有效)
所有的枚举都继承自java.lang.Enum类。由于Java 不支持多继承,所以枚举对象不能再继承其他类
public interface Behaviour {
void print();
String getInfo();
}
public enum Color implements Behaviour{
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
//接口方法
@Override
public String getInfo() {
return this.name;
}
//接口方法
@Override
public void print() {
System.out.println(this.index+":"+this.name);
}
}
使用接口组织枚举
这个思想是很好的。如果一个模块需要有多个枚举,建议可以放在接口内来统一组织。这样方便管理,也方便做一些多态的使用
public interface Food {
enum Coffee implements Food{
BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO
}
enum Dessert implements Food{
FRUIT, CAKE, GELATO
}
}
枚举集合的使用(EnumSet和EnumMap)
java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的 key是enum类型,而value则可以是任意类型
EnumMap基本用法
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
implements java.io.Serializable, Cloneable
先思考一个问题,有一堆颜色不同的数据,需要统计出每种颜色的数量,定义如下枚举用于表示颜色:
enum Color {
GREEN,RED,BLUE,YELLOW
}
显然这个如果用Map来做,估计谁都会做。但是本文采用更方便,更加高效的EnumMap来处理:
EnumMap<Color, Integer> enumMap = new EnumMap<>(Color.class);
for (Color color : list) {
int count = enumMap.getOrDefault(color, 0) + 1;
enumMap.put(color, count);
}
EnumMap作为枚举的专属集合,没有理由再去使用HashMap,毕竟EnumMap要求其Key必须为Enum类型,因而使用Color枚举实例作为key是最恰当不过了,也避免了获取name的步骤
更重要的是EnumMap效率更高,因为其内部是通过数组实现的。注意EnumMap的key值不能为null,虽说是枚举专属集合,但其操作与一般的Map差不多
概括性来说EnumMap是专门为枚举类型量身定做的Map实现,虽然使用其它的Map(如HashMap)也能完成相同的功能,但是使用EnumMap会更加高效
它只能接收同一枚举类型的实例作为键值且不能为null,由于枚举类型实例的数量相对固定并且有限,所以EnumMap使用数组来存放与枚举类型对应的值,毕竟数组是一段连续的内存空间,根据程序局部性原理,效率会相当高
它有三个构造函数:
// 创建一个具有指定键类型的空枚举映射
EnumMap(Class<K> keyType) ;
// 创建一个其键类型与指定枚举映射相同的枚举映射,最初包含相同的映射关系(如果有的话)
EnumMap(EnumMap<K,? extends V> m) ;
// 创建一个枚举映射,从指定映射对其初始化
EnumMap(Map<K,? extends V> m);
//使用第一种构造
Map<Color,Integer> enumMap=new EnumMap<>(Color.class);
//使用第二种构造
Map<Color,Integer> enumMap2=new EnumMap<>(enumMap);
//使用第三种构造
Map<Color,Integer> hashMap = new HashMap<>();
hashMap.put(Color.GREEN, 2);
hashMap.put(Color.BLUE, 3);
Map<Color, Integer> enumMap = new EnumMap<>(hashMap);
EnumSet用法
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
implements Cloneable, java.io.Serializable
EnumSet 中所有元素都必须是枚举类型
与其他Set接口的实现类HashSet/TreeSet(内部都是用对应的HashMap/TreeMap实现的)不同的是,EnumSet在内部实现是位向量,它是一种极为高效的位运算操作
由于直接存储和操作都是bit,因此EnumSet空间和时间性能都十分可观,足以媲美传统上基于 int 的“位标志”运算,重要的是我们可像操作set集合一般来操作位运算,这样使用代码更简单易懂同时又具备类型安全的优势
创建EnumSet并不能使用new关键字,因为它是个抽象类,而应该使用其提供的静态工厂方法,EnumSet的静态工厂方法比较多,如下:
// 创建一个具有指定元素类型的空EnumSet。
EnumSet<E> noneOf(Class<E> elementType)
//创建一个指定元素类型并包含所有枚举值的EnumSet
<E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType)
// 创建一个包括枚举值中指定范围元素的EnumSet
<E extends Enum<E>> EnumSet<E> range(E from, E to)
// 初始集合包括指定集合的补集
<E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s)
// 创建一个包括参数中所有元素的EnumSet
<E extends Enum<E>> EnumSet<E> of(E e)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4, E e5)
<E extends Enum<E>> EnumSet<E> of(E first, E... rest)
//创建一个包含参数容器中的所有元素的EnumSet
<E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s)
<E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c)
// 空集合
EnumSet<Color> enumSet = EnumSet.noneOf(Color.class);
System.out.println("添加前:" + enumSet.toString());
enumSet.add(Color.GREEN);
enumSet.add(Color.RED);
enumSet.add(Color.BLUE);
enumSet.add(Color.YELLOW);
System.out.println("添加后:" + enumSet.toString());
// 使用allOf创建包含所有枚举类型的enumSet,其内部根据Class对象初始化了所有枚举实例
EnumSet<Color> enumSet1 = EnumSet.allOf(Color.class);
System.out.println("allOf直接填充:" + enumSet1.toString());
// 初始集合包括枚举值中指定范围的元素
EnumSet<Color> enumSet2 = EnumSet.range(Color.RED, Color.YELLOW);
System.out.println("指定初始化范围:" + enumSet2.toString());
// 指定补集,也就是从全部枚举类型中去除参数集合中的元素
EnumSet<Color> enumSet3 = EnumSet.complementOf(enumSet2);
System.out.println("指定补集:" + enumSet3.toString());
// 初始化时直接指定元素
EnumSet<Color> enumSet4 = EnumSet.of(Color.RED);
System.out.println("指定Color.RED元素:" + enumSet4.toString());
EnumSet<Color> enumSet5 = EnumSet.of(Color.RED, Color.GREEN);
System.out.println("指定Color.RED和Color.GREEN元素:" + enumSet5.toString());
// 复制enumSet5容器的数据作为初始化数据
EnumSet<Color> enumSet6 = EnumSet.copyOf(enumSet5);
System.out.println("enumSet6:" + enumSet6.toString());
List<Color> list = new ArrayList<>();
list.add(Color.GREEN);
list.add(Color.GREEN);// 重复元素
list.add(Color.RED);
list.add(Color.BLUE);
System.out.println("list:" + list.toString());
//使用copyOf(Collection<E> c) 可以达到去重的效果
EnumSet enumSet7 = EnumSet.copyOf(list);
System.out.println("enumSet7:" + enumSet7.toString());
其实EnumSet最有价值的是其内部实现原理,采用的是位向量,它体现出来的是一种高效的数据处理方式,这点很值得我们去学习它
总结:多使用枚举,枚举的好处
enum这个关键字,可以理解为跟class差不多,也是个单独的类
一般的class可以自己new对象,想几个就几个,而这个enum关键字,他就不行,他的实例对象,只能在这个enum里体现。也就是说,他对应的实例是有限的。这也就是枚举的好处了,限制了某些东西的范围,举个例子:
一年四季,只能有春夏秋冬,你要是字符串表示的话,那就海了去了,但是,使用枚举的话,在enum的里把所有选项全部列出来,那么这个季节的属性,对应的值,只能在里面挑。不能有其他的
使用枚举的一些规范推荐
- 枚举类名建议带上Enum后缀,枚举成员名称需要全部大写。单词间使用下划线分隔
- 强制规范:所有的枚举类型成员必须要有注释,说明每个字段的用途(一般可以使用接口进行强制规范)
枚举类型对象之间的值比较,是可以使用== ,直接来比较值,是否相等的,不是必须使用equals方法。 并且,强烈建议使用==,效率更高
Commons-lang工具
EnumUtils:辅助操作枚举的一些工具
- getEnum(Class enumClass, String enumName) 通过类返回一个枚举,可能返回空
- getEnum(final Class enumClass, final String enumName, final E defaultEnum)
- getEnumIgnoreCase(final Class enumClass, final String enumName)
- getEnumIgnoreCase(final Class enumClass, final String enumName, final E defaultEnum)
- getEnumList(Class enumClass) 通过类返回一个枚举集合
- getEnumMap(Class enumClass) 通过类返回一个枚举map
- isValidEnum(Class enumClass, String enumName) 验证enumName是否在枚举中
- isValidEnumIgnoreCase(final Class enumClass, final String enumName)
public enum ImagesTypeEnum {
JPG, JPEG, PNG, GIF
}
ImagesTypeEnum imagesTypeEnum =
EnumUtils.getEnum(ImagesTypeEnum.class, "JPG");
List<ImagesTypeEnum> imagesTypeEnumList =
EnumUtils.getEnumList(ImagesTypeEnum.class);
Map<String, ImagesTypeEnum> enumMap =
EnumUtils.getEnumMap(ImagesTypeEnum.class);
boolean jpg = EnumUtils.isValidEnum(ImagesTypeEnum.class, "JPG");
System.out.println(EnumUtils.isValidEnum(ImagesTypeEnum.class, null));