【小家Java】深入理解Java列舉型別(enum)及7種常見的用法(含EnumMap和EnumSet)
相關閱讀
【小家java】java5新特性(簡述十大新特性) 重要一躍
【小家java】java6新特性(簡述十大新特性) 雞肋升級
【小家java】java7新特性(簡述八大新特性) 不溫不火
【小家java】java8新特性(簡述十大新特性) 飽受讚譽
【小家java】java9新特性(簡述十大新特性) 褒貶不一
【小家java】java10新特性(簡述十大新特性) 小步迭代
【小家java】java11新特性(簡述八大新特性) 首個重磅LTS版本
前文
這次當我入職一家新公司的時候,編寫程式碼發現,裡面還在大量的使用public static final…這種語句來神馬一些狀態常量。
很多時候,雖然都能暫時
完成一樣的功能,但武功高低,一看便知。因此我加入之後,迅速全面引入列舉型別,並且指定列舉的使用規範、統一實現的介面。。。
什麼是列舉型別
列舉型別是Java 5中新增特性的一部分,它是一種特殊的資料型別,之所以特殊是因為它既是一種類(class)型別卻又比類型別多了些特殊的約束,但是這些約束的存在也造就了列舉型別的簡潔性、安全性以及便捷性。
列舉型別的定義
這段程式碼,是在enum沒引入之前:
public class DayDemo {
public static final int MONDAY =1;
public static final int TUESDAY=2;
public static final int WEDNESDAY=3;
public static final int THURSDAY=4;
public static final int FRIDAY=5;
public static final int SATURDAY=6;
public static final int SUNDAY=7;
}
上述的常量定義常量的方式稱為int列舉模式,這樣的定義方式並沒有什麼錯,但它存在許多不足:
- 如在型別安全和使用方便性上並沒有多少好處
- 如果存在定義int值相同的變數,混淆的機率還是很大的,編譯器也不會提出任何警告
- 操作上,比如我要拿到所有的列舉值,或者根據列舉值拿到具體的名字等都非常的不方便
因此這種方式在枚舉出現後並不提倡,現在我們利用列舉型別來重新定義上述的常量,同時也感受一把列舉定義的方式,如下定義週一到週日的常量
//列舉型別,使用關鍵字enum
public enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
相當簡潔,使用起來也是異常的方便(下面會著重講解它的用法)。
列舉的真身
需要看真身,首先我們得看看編譯後的.class檔案。
/**
* @author [email protected]
* @description
* @date 2018-11-03 16:49
*/
public enum DayEnum {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
檢視.class檔案如下:
我們發現它和普通的class檔案一樣,還是會生成一個同名的.class檔案。現在我們反編譯看看.class檔案的內容:
public final class DayEnum extends Enum<DayEnum> {
//編譯器為我們新增的靜態的values()方法
public static DayEnum[] values() {
return (DayEnum[])$VALUES.clone();
}
//編譯器為我們新增的靜態的valueOf()方法,注意間接呼叫了Enum也類的valueOf方法
public static DayEnum valueOf(String s) {
return (DayEnum)Enum.valueOf(com/fsx/run/enums/DayEnum, s);
}
//私有建構函式 只能由編譯器來呼叫
private DayEnum(String enumName, int index) {
super(enumName, index);
}
//前面定義的7種列舉例項
public static final DayEnum MONDAY;
public static final DayEnum TUESDAY;
public static final DayEnum WEDNESDAY;
public static final DayEnum THURSDAY;
public static final DayEnum FRIDAY;
public static final DayEnum SATURDAY;
public static final DayEnum SUNDAY;
//裝載所有例項的一個數組
private static final DayEnum $VALUES[];
//通過靜態程式碼快例項這些多例
static {
MONDAY = new DayEnum("MONDAY", 0);
TUESDAY = new DayEnum("TUESDAY", 1);
WEDNESDAY = new DayEnum("WEDNESDAY", 2);
THURSDAY = new DayEnum("THURSDAY", 3);
FRIDAY = new DayEnum("FRIDAY", 4);
SATURDAY = new DayEnum("SATURDAY", 5);
SUNDAY = new DayEnum("SUNDAY", 6);
//都裝載進去
$VALUES = (new DayEnum[] {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
});
}
}
看到了enum型別編譯後的真身,很多結果都一目瞭然了有木有。
我們注意到,DayEnum類是final型別的,將無法被繼承。而且該類繼承自java.lang.Enum類(它是一個抽象類,所有的enum型別的類都是它的子類,提供很多方法和定義)
這裡提醒大家一點,Enum類內部會有一個建構函式,該建構函式只能有編譯器呼叫,我們是無法手動操作的
列舉的Class物件
需求:我們需要一次性獲取到所有的列舉值物件:
public static void main(String[] args) {
DayEnum[] values = DayEnum.values();
DayEnum[] enumConstants = DayEnum.class.getEnumConstants();
//判斷某個class是否為列舉型別
System.out.println(MONDAY.getClass().isEnum()); //true
}
enum中定義抽象方法
與常規抽象類一樣,enum類允許我們為其定義抽象方法,然後使每個列舉例項都實現該方法,以便產生不同的行為方式,注意abstract關鍵字對於列舉類來說並不是必須的如下:
public enum EnumDemo3 {
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("F:"+EnumDemo3.FIRST.getInfo());
System.out.println("S:"+EnumDemo3.SECOND.getInfo());
/**
輸出結果:
F:FIRST TIME
S:SECOND TIME
*/
}
}
常用的7種使用方式
用法一:常量(也是最為常用的使用場景)
在JDK1.5 之前,我們定義常量都是: public static final… 。現在好了,有了列舉,可以把相關的常量分組到一個列舉型別裡,而且列舉提供了比常量更多的方法。
public enum Color {
// RED, GREEN, BLANK, YELLOW //若後續沒有程式碼了,此;可以省略。否則不行
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);
// 成員變數
private String name;
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;
}
// get方法(set方法是沒有必要給出的,因為並不希望改變值)
public String getName() {
return name;
}
public int getIndex() {
return index;
}
}
用法四:覆蓋列舉的方法
下面給出一個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
先思考這樣一個問題,現在我們有一堆size大小相同而顏色不同的資料,需要統計出每種顏色的數量是多少以便將資料錄入倉庫,定義如下列舉用於表示顏色Color:
enum Color {
GREEN,RED,BLUE,YELLOW
}
顯然這個如果用Map來做,估計誰都會做。但是本文采用更方便,更加高效的EnumMap來處理:
//方案2:使用EnumMap
// 使用color作為key,總數Integer作為值即可方便統計
Map<Color,Integer> enumMap=new EnumMap<>(Color.class);
for (Clothes clothes:list){
Color color=clothes.getColor();
Integer count = enumMap.get(color);
if(count!=null){
enumMap.put(color,count+1);
}else {
enumMap.put(color,1);
}
}
System.out.println(enumMap.toString());
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)
使用示例:
enum Color {
GREEN , RED , BLUE , BLACK , YELLOW
}
public static void main(String[] args){
//空集合
EnumSet<Color> enumSet= EnumSet.noneOf(Color.class);
System.out.println("新增前:"+enumSet.toString());
enumSet.add(Color.GREEN);
enumSet.add(Color.RED);
enumSet.add(Color.BLACK);
enumSet.add(Color.BLUE);
enumSet.add(Color.YELLOW);
System.out.println("新增後:"+enumSet.toString());
System.out.println("-----------------------------------");
//使用allOf建立包含所有列舉型別的enumSet,其內部根據Class物件初始化了所有列舉例項
EnumSet<Color> enumSet1= EnumSet.allOf(Color.class)