枚举
《Java编程的逻辑》读书笔记
1. 基础
定义:
public enum Size {
SAMLL, MEDIUM, LARGE
}
简单使用:
public static void main(String[] args) {
Size size = Size.SAMLL;
System.out.println(size.toString()); // SAMLL
System.out.println(size.name()); // SAMLL
System.out.println(size == Size.SAMLL); // true
System.out.println(size.equals(Size.MEDIUM)); // false
System.out.println(size.ordinal()); // 0
for (Size size : Size.values()) {
System.out.println(size);
}
}
/*
for输出:
SAMLL
MEDIUM
LARGE
*/
也可以作为switch-case的参数使用
枚举类内部有 int ordinal() 方法,返回枚举声明时的顺序,从0开始,声明顺序改变同一个枚举返回的值也会改变
2. 进阶
package com.wang.controller;
public enum WzkEnum {
KUANG("狂","kuang"),
ZHUAI("拽","zhuai"),
KU("酷","ku"),
SHUAI("帅","shuai"),
DIAO("屌","diao"),
ZHA("炸","zha"),
TIAN("天","tian");
private String chinese;
private String pinyin;
public String getChinese() {
return chinese;
}
public void setChinese(String chinese) {
this.chinese = chinese;
}
public String getPinyin() {
return pinyin;
}
public void setPinyin(String pinyin) {
this.pinyin = pinyin;
}
WzkEnum(String chinese, String pinyin) {
this.chinese = chinese;
this.pinyin = pinyin;
}
}
枚举也可以这样使用,定义一个构造方法。
枚举,只写内容列队后面不用分号,如果带入参数,要在内容的列队后面写上分号
优点
- 语法更为简洁,之前要定义静态变量来实现类似功能
- 更安全,一个枚举的值要么为null要么为枚举值之一,不会有其他值
- 自带便利方法:values,valueOf,toString等
EnumMap
这里不详细介绍了,使用上跟其他map相同,只是构造方法需要传入一个枚举类。
内部:
实例变量:
private final Class<K> keyType; // 键的类型
private transient K[] keyUniverse; // 键数组
private transient Object[] vals; // 值数组
private transient int size = 0; // 键值对个数
构造方法
public EnumMap(Class<K> keyType) {
this.keyType = keyType;
keyUniverse = getKeyUniverse(keyType); // 最终调用枚举类型的values方法,返回所有可能的枚举值
vals = new Object[keyUniverse.length];
}
超简单:两个数组,长度相同,一个表示可能的键(枚举值),一个表示对应的值,值为null表示没有该键值对。
根据索引可以直接访问键和值,效率很高,如果需要一个Map且键是枚举类型,那么应该使用EnumMap。
EnumSet
EnumSet可以非常高效的实现Set接口。
HashSet/TreeSet,内部是使用对应的HashMap/TreeMap实现的,但EnumSet不是,它的实现和EnumMap没有半毛钱关系,它是用极为精简和高效的 位向量 实现的。
它是处理枚举类型数据的一把利器,在一些作用领域,它非常高效方便。
基本用法
EnumSet是一个抽象类 它不能通过直接new创建
它提供了若干静态工厂方法,可以创建EnumSet对象:
// 创建一个指定枚举类型的EnumSet,不含任何元素,实际上创建的对象是EnumSet的子类
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType)
// 初始集合包含指定枚举类型的所有枚举值
public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType)
// 初始集合包含枚举值中指定范围的元素
public static <E extends Enum<E>> EnumSet<E> range(E from, E to)
// 包括指定集合的补集
public static <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s)
// 初始集合包含参数中的所有元素---之所以这么多固定个数的方法是因为可变参数的运行效率低一些
public static <E extends Enum<E>> EnumSet<E> of(E e)
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2)
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3)
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4)
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4, E e5)
public static <E extends Enum<E>> EnumSet<E> of(E first, E... rest)
// 包括参数容器中的所有元素
public static <E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s)
public static <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c)
应用场景
在一些工作中,比如客服、医生,并不是每个人每天都在,每个人工作的时间是不一样的。朱元璋可能周一、三、五上班,王保保可能周一二四六上班,给定每个人的工作时间,我们可能面对一些问题:
- 有没有哪天都不上班
- 有哪天至少会有一人上班
- 有哪天至少有两人上班
- 哪天人都来,以便开会
- 哪些人周一、二都会来
使用EnumSet可以方便高效的回答这些问题
我们定义一个枚举类week
public enum Weeks {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
再定义一个表示工作人员的类Worker:
public class Worker {
String name;
Set<Weeks> availableDays;
public Worker(String name, Set<Weeks> availableDays) {
this.name = name;
this.availableDays = availableDays;
}
// 省略getter方法
}
定义一个工作人员数组:
// 工作人员数组
static Worker[] workers = new Worker[]{
// 老朱 一二三五上班
new Worker("朱元璋", EnumSet.of(MONDAY, TUESDAY, WEDNESDAY, FRIDAY)),
// 小朱 二四六上班
new Worker("朱标", EnumSet.of(TUESDAY, THURSDAY, SATURDAY)),
// 小小朱二四上班
new Worker("朱允炆", EnumSet.of(TUESDAY, THURSDAY)),
};
- 哪天都不上班:
Set<Weeks> days = EnumSet.allOf(Weeks.class);
for (Worker worker : workers) {
days.removeAll(worker.getAvailableDays());
}
System.out.println(days); // [SUNDAY]
- 哪天至少会有一人上班
// 2. 哪天至少会有一人上班(并集)
Set<Weeks> days2 = EnumSet.noneOf(Weeks.class);
for (Worker worker : workers) {
days2.addAll(worker.getAvailableDays());
}
System.out.println(days2); // [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY]
- 哪天所有人都来
// 3. 哪天都会来(交集)
Set<Weeks> days3 = EnumSet.allOf(Weeks.class);
for (Worker worker : workers) {
days3.retainAll(worker.getAvailableDays());
}
System.out.println(days3); // [TUESDAY]
- 哪些人周一 周二会来
// 4. 哪些人周一 周二会来
Set<Worker> availableWorkers = new HashSet<>();
for (Worker worker : workers) {
if (worker.getAvailableDays().containsAll(EnumSet.of(Weeks.MONDAY,Weeks.TUESDAY))){
availableWorkers.add(worker);
}
}
for (Worker availableWorker : availableWorkers) {
System.out.println(availableWorker.getName());
}
// 朱元璋
- 哪些天至少有两个人来
// 5. 哪些天至少有两个人来
EnumMap<Weeks,Integer> countMap = new EnumMap<Weeks, Integer>(Weeks.class);
for (Worker worker : workers) {
for (Weeks day : worker.getAvailableDays()) {
Integer count = countMap.get(day);
countMap.put(day,count==null?1:count+1);
}
}
Set<Weeks> days5 = EnumSet.noneOf(Weeks.class);
for (Map.Entry<Weeks,Integer> entry:countMap.entrySet()){
if (entry.getValue()>=2){
days5.add(entry.getKey());
}
}
System.out.println(days5); // [TUESDAY, THURSDAY]
}
实现原理:位向量
所谓位向量就是用一个位表示一个元素的状态,用一组位表示一个集合的状态,每个位对应一个元素,而状态只有两种.
对于之前的枚举类Week,它有7个枚举值,一个Week的集合就可以用一个字节byte表示,最高位不用,设为0,最右边的位对应顺序最小的枚举值,从右到左,每位对应一个枚举值,1表示包含该元素,0表示不含该元素.
对应的整数是23.
具体add.remove等方法就不详细介绍了,可以去看源码,涉及到一些位移操作,取反操作,按位或,按位与等操作