18.7.18
一、概述
枚舉常量在類型安全性和便捷性都很有保證,如果出現類型問題編譯器也會提示我們改進。
除了不能繼承,其他跟普通類一樣。編譯器默認給繼承了一個Enum類,并且加了final修飾
enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
Day day =Day.MONDAY;
二、原理
反編譯后就是一個類
1、final class Day extends Enum
2、默認生成一個values方法,返回屬性的數組
3、valueOf(String s)方法,
4、//私有構造函數,構造器只能私有private,絕對不允許有public構造器。
private Day(String s, int i)
{
super(s, i);
}
父類Enum類的 //枚舉的構造方法,只能由編譯器調用
5、//前面定義的7種枚舉實例
public static final Day MONDAY;
public static final Day TUESDAY;
6、static
{
//實例化枚舉實例
MONDAY = new Day("MONDAY", 0);
TUESDAY = new Day("TUESDAY", 1);
$VALUES = (new Day[] {
MONDAY, TUESDAY
});
}
三、提供的常用方法
返回類型 方法名稱 方法說明
int compareTo(E o) 比較此枚舉與指定對象的順序
boolean equals(Object other) 當指定對象等于此枚舉常量時,返回 true。
Class<?> getDeclaringClass() 返回與此枚舉常量的枚舉類型相對應的 Class 對象
String name() 返回此枚舉常量的名稱,在其枚舉聲明中對其進行聲明
int ordinal() 返回枚舉常量的序數(它在枚舉聲明中的位置,其中初始常量序數為零)
String toString() 返回枚舉常量的名稱,它包含在聲明中
static<T extends Enum<T>> T static valueOf(Class<T> enumType, String name) 返回帶指定名稱的指定枚舉類型的枚舉常量。
{
Day[] days2 = Day.values();
System.out.println("day2:"+Arrays.toString(days2));
Day day = Day.valueOf("MONDAY");
System.out.println("day:"+day);
}
四、使用技巧
//正常使用
Day[] ds=Day.values();
//向上轉型Enum
Enum e = Day.MONDAY;
//無法調用,沒有此方法
//e.values();
當枚舉實例向上轉型為Enum類型后,values()方法將會失效,也就無法一次性獲取所有枚舉實例變量,但是由于Class對象的存在,即使不使用values()方法,還是有可能一次獲取到所有枚舉實例變量的,在Class對象中存在如下方法:
返回類型 方法名稱 方法說明
T[] getEnumConstants() 返回該枚舉類型的所有元素,如果Class對象不是枚舉類型,則返回null。
boolean isEnum() 當且僅當該類聲明為源代碼中的枚舉時返回 true
因此通過getEnumConstants()方法,同樣可以輕而易舉地獲取所有枚舉實例變量下面通過代碼來演示這個功能:
//正常使用
Day[] ds=Day.values();
//向上轉型Enum
Enum e = Day.MONDAY;
//無法調用,沒有此方法
//e.values();
//獲取class對象引用
Class<?> clasz = e.getDeclaringClass();
if(clasz.isEnum()) {
Day[] dsz = (Day[]) clasz.getEnumConstants();
System.out.println("dsz:"+Arrays.toString(dsz));
}
/**
輸出結果:
dsz:[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
/
五、進階
1、枚舉,除了不能繼承,其他與類無他。
可以自定義構造函數和方法。
2、覆蓋父類的方法只能覆蓋toString方法,其他的父類方法都被final修飾
3、枚舉中每個成員都是一個枚舉的實例。
枚舉中可以定義抽象類,每個成員實例都必須實現該抽象類。
通過這種方式就可以輕而易舉地定義每個枚舉實例的不同行為方式。
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
*/
}
}
4、枚舉可以實現多接口
有時候,我們可能需要對一組數據進行分類,比如進行食物菜單分類而且希望這些菜單都屬于food類型,appetizer(開胃菜)、mainCourse(主菜)、dessert(點心)、Coffee等,每種分類下有多種具體的菜式或食品,此時可以利用接口來組織,如下(代碼引用自Thinking in Java):
public interface Food {
enum Appetizer implements Food {
SALAD, SOUP, SPRING_ROLLS;
}
enum MainCourse implements Food {
LASAGNE, BURRITO, PAD_THAI,
LENTILS, HUMMOUS, VINDALOO;
}
enum Dessert implements Food {
TIRAMISU, GELATO, BLACK_FOREST_CAKE,
FRUIT, CREME_CARAMEL;
}
enum Coffee implements Food {
BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
LATTE, CAPPUCCINO, TEA, HERB_TEA;
}
}
public class TypeOfFood {
public static void main(String[] args) {
Food food = Appetizer.SALAD;
food = MainCourse.LASAGNE;
food = Dessert.GELATO;
food = Coffee.CAPPUCCINO;
}
}
通過這種方式可以很方便組織上述的情景,同時確保每種具體類型的食物也屬于Food,現在我們利用一個枚舉嵌套枚舉的方式,把前面定義的菜譜存放到一個Meal菜單中,通過這種方式就可以統一管理菜單的數據了。
public enum Meal{
APPETIZER(Food.Appetizer.class),
MAINCOURSE(Food.MainCourse.class),
DESSERT(Food.Dessert.class),
COFFEE(Food.Coffee.class);
private Food[] values;
private Meal(Class<? extends Food> kind) {
//通過class對象獲取枚舉實例
values = kind.getEnumConstants();
}
public interface Food {
enum Appetizer implements Food {
SALAD, SOUP, SPRING_ROLLS;
}
enum MainCourse implements Food {
LASAGNE, BURRITO, PAD_THAI,
LENTILS, HUMMOUS, VINDALOO;
}
enum Dessert implements Food {
TIRAMISU, GELATO, BLACK_FOREST_CAKE,
FRUIT, CREME_CARAMEL;
}
enum Coffee implements Food {
BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
LATTE, CAPPUCCINO, TEA, HERB_TEA;
}
}
}
5、枚舉與switch
6、枚舉與單例模式
特點:枚舉無法通過反射實例化
關于單例,我們總是應該記住:線程安全,延遲加載,序列化與反序列化安全,反射安全是很重重要的。
其他單例需要些很多代碼來保證安全。但是枚舉則不需要:
/**
- Created by wuzejian on 2017/5/9.
- 枚舉單利
*/
public enum SingletonEnum {
INSTANCE;
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
我們完全不用考慮序列化和反射的問題。枚舉序列化是由jvm保證的,每一個枚舉類型和定義的枚舉變量在JVM中都是唯一的,在枚舉類型的序列化和反序列化上,Java做了特殊的規定:在序列化時Java僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是通過java.lang.Enum的valueOf方法來根據名字查找枚舉對象。同時,編譯器是不允許任何對這種序列化機制的定制的并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,從而保證了枚舉實例的唯一性。
并且枚舉無法通過反射實例化。
六、擴展
1、EnumMap
專門存<枚舉,String>映射的Map
現在我們有一堆size大小相同而顏色不同的數據,需要統計出每種顏色的數量是多少以便將數據錄入倉庫。
List<Clothes> list = new ArrayList<>();
list.add(new Clothes("C001",Color.BLUE));
list.add(new Clothes("C002",Color.YELLOW));
//方案1:使用HashMap
Map<String,Integer> map = new HashMap<>();
for (Clothes clothes:list){
String colorName=clothes.getColor().name();
Integer count = map.get(colorName);
if(count!=null){
map.put(colorName,count+1);
}else {
map.put(colorName,1);
}
}
System.out.println(map.toString());
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());
我們使用兩種解決方案,一種是HashMap,一種EnumMap,雖然都統計出了正確的結果。
EnumMap要求其Key必須為Enum類型,因而使用Color枚舉實例作為key是最恰當不過了,也避免了獲取name的步驟,更重要的是EnumMap效率更高,因為其內部是通過數組實現的。由于枚舉類型實例的數量相對固定并且有限,所以EnumMap使用數組來存放與枚舉類型對應的值,畢竟數組是一段連續的內存空間,根據程序局部性原理,效率會相當高。注意EnumMap的key值不能為null,雖說是枚舉專屬集合,但其操作與一般的Map差不多。它只能接收同一枚舉類型的實例作為鍵值且不能為null。
先看構造函數:
//創建一個具有指定鍵類型的空枚舉映射。
EnumMap(Class<K> keyType)
//創建一個其鍵類型與指定枚舉映射相同的枚舉映射,最初包含相同的映射關系(如果有的話)。
EnumMap(EnumMap<K,? extends V> m)
//創建一個枚舉映射,從指定映射對其初始化。
EnumMap(Map<K,? extends V> m)
與HashMap不同,它需要傳遞一個類型信息,即Class對象,通過這個參數EnumMap就可以根據類型信息初始化其內部數據結構,另外兩只是初始化時傳入一個Map集合,代碼演示如下:
//使用第一種構造
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);
至于EnumMap的方法,跟普通的map幾乎沒有區別,注意與HashMap的主要不同在于構造方法需要傳遞類型參數和EnumMap保證Key順序與枚舉中的順序一致,但請記住Key不能為null。
2、EnumMap源碼
3、EnumSet
EnumSet是與枚舉類型一起使用的專用 Set 集合,EnumSet 中所有元素都必須是枚舉類型。與其他Set接口的實現類HashSet/TreeSet(內部都是用對應的HashMap/TreeMap實現的)不同的是,EnumSet在內部實現是位向量(稍后分析),它是一種極為高效的位運算操作,由于直接存儲和操作都是bit,因此EnumSet空間和時間性能都十分可觀,足以媲美傳統上基于 int 的“位標志”的運算,重要的是我們可像操作set集合一般來操作位運算,這樣使用代碼更簡單易懂同時又具備類型安全的優勢。注意EnumSet不允許使用 null 元素。試圖插入 null 元素將拋出 NullPointerException,但試圖測試判斷是否存在null 元素或移除 null 元素則不會拋出異常,與大多數collection 實現一樣,EnumSet不是線程安全的,因此在多線程環境下應該注意數據同步問題
4、EnumSet用法
5、EnumSet實現原理
參考:https://blog.csdn.net/javazejian/article/details/71333103