枚舉相關(guān)

18.7.18
一、概述
枚舉常量在類(lèi)型安全性和便捷性都很有保證,如果出現(xiàn)類(lèi)型問(wèn)題編譯器也會(huì)提示我們改進(jìn)。
除了不能繼承,其他跟普通類(lèi)一樣。編譯器默認(rèn)給繼承了一個(gè)Enum類(lèi),并且加了final修飾
enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
Day day =Day.MONDAY;
二、原理
反編譯后就是一個(gè)類(lèi)
1、final class Day extends Enum
2、默認(rèn)生成一個(gè)values方法,返回屬性的數(shù)組
3、valueOf(String s)方法,
4、//私有構(gòu)造函數(shù),構(gòu)造器只能私有private,絕對(duì)不允許有public構(gòu)造器。
private Day(String s, int i)
{
super(s, i);
}
父類(lèi)Enum類(lèi)的 //枚舉的構(gòu)造方法,只能由編譯器調(diào)用
5、//前面定義的7種枚舉實(shí)例
public static final Day MONDAY;
public static final Day TUESDAY;
6、static
{
//實(shí)例化枚舉實(shí)例
MONDAY = new Day("MONDAY", 0);
TUESDAY = new Day("TUESDAY", 1);
$VALUES = (new Day[] {
MONDAY, TUESDAY
});
}
三、提供的常用方法
返回類(lèi)型 方法名稱(chēng) 方法說(shuō)明
int compareTo(E o) 比較此枚舉與指定對(duì)象的順序
boolean equals(Object other) 當(dāng)指定對(duì)象等于此枚舉常量時(shí),返回 true。
Class<?> getDeclaringClass() 返回與此枚舉常量的枚舉類(lèi)型相對(duì)應(yīng)的 Class 對(duì)象
String name() 返回此枚舉常量的名稱(chēng),在其枚舉聲明中對(duì)其進(jìn)行聲明
int ordinal() 返回枚舉常量的序數(shù)(它在枚舉聲明中的位置,其中初始常量序數(shù)為零)
String toString() 返回枚舉常量的名稱(chēng),它包含在聲明中
static<T extends Enum<T>> T static valueOf(Class<T> enumType, String name) 返回帶指定名稱(chēng)的指定枚舉類(lèi)型的枚舉常量。
{
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();
//向上轉(zhuǎn)型Enum
Enum e = Day.MONDAY;
//無(wú)法調(diào)用,沒(méi)有此方法
//e.values();

當(dāng)枚舉實(shí)例向上轉(zhuǎn)型為Enum類(lèi)型后,values()方法將會(huì)失效,也就無(wú)法一次性獲取所有枚舉實(shí)例變量,但是由于Class對(duì)象的存在,即使不使用values()方法,還是有可能一次獲取到所有枚舉實(shí)例變量的,在Class對(duì)象中存在如下方法:
返回類(lèi)型 方法名稱(chēng) 方法說(shuō)明
T[] getEnumConstants() 返回該枚舉類(lèi)型的所有元素,如果Class對(duì)象不是枚舉類(lèi)型,則返回null。
boolean isEnum() 當(dāng)且僅當(dāng)該類(lèi)聲明為源代碼中的枚舉時(shí)返回 true
因此通過(guò)getEnumConstants()方法,同樣可以輕而易舉地獲取所有枚舉實(shí)例變量下面通過(guò)代碼來(lái)演示這個(gè)功能:
//正常使用
Day[] ds=Day.values();
//向上轉(zhuǎn)型Enum
Enum e = Day.MONDAY;
//無(wú)法調(diào)用,沒(méi)有此方法
//e.values();
//獲取class對(duì)象引用
Class<?> clasz = e.getDeclaringClass();
if(clasz.isEnum()) {
Day[] dsz = (Day[]) clasz.getEnumConstants();
System.out.println("dsz:"+Arrays.toString(dsz));
}
/**
輸出結(jié)果:
dsz:[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
/
五、進(jìn)階
1、枚舉,除了不能繼承,其他與類(lèi)無(wú)他。
可以自定義構(gòu)造函數(shù)和方法。
2、覆蓋父類(lèi)的方法只能覆蓋toString方法,其他的父類(lèi)方法都被final修飾
3、枚舉中每個(gè)成員都是一個(gè)枚舉的實(shí)例。
枚舉中可以定義抽象類(lèi),每個(gè)成員實(shí)例都必須實(shí)現(xiàn)該抽象類(lèi)。
通過(guò)這種方式就可以輕而易舉地定義每個(gè)枚舉實(shí)例的不同行為方式。
public enum EnumDemo3 {
FIRST{
@Override
public String getInfo() {
return "FIRST TIME";
}
},
SECOND{
@Override
public String getInfo() {
return "SECOND TIME";
}
}
;
/
*
* 定義抽象方法
* @return
/
public abstract String getInfo();
//測(cè)試
public static void main(String[] args){
System.out.println("F:"+EnumDemo3.FIRST.getInfo());
System.out.println("S:"+EnumDemo3.SECOND.getInfo());
/
*
輸出結(jié)果:
F:FIRST TIME
S:SECOND TIME
*/
}
}
4、枚舉可以實(shí)現(xiàn)多接口
有時(shí)候,我們可能需要對(duì)一組數(shù)據(jù)進(jìn)行分類(lèi),比如進(jìn)行食物菜單分類(lèi)而且希望這些菜單都屬于food類(lèi)型,appetizer(開(kāi)胃菜)、mainCourse(主菜)、dessert(點(diǎn)心)、Coffee等,每種分類(lèi)下有多種具體的菜式或食品,此時(shí)可以利用接口來(lái)組織,如下(代碼引用自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;
}
}
通過(guò)這種方式可以很方便組織上述的情景,同時(shí)確保每種具體類(lèi)型的食物也屬于Food,現(xiàn)在我們利用一個(gè)枚舉嵌套枚舉的方式,把前面定義的菜譜存放到一個(gè)Meal菜單中,通過(guò)這種方式就可以統(tǒng)一管理菜單的數(shù)據(jù)了。
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) {
//通過(guò)class對(duì)象獲取枚舉實(shí)例
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、枚舉與單例模式
特點(diǎn):枚舉無(wú)法通過(guò)反射實(shí)例化
關(guān)于單例,我們總是應(yīng)該記?。壕€程安全,延遲加載,序列化與反序列化安全,反射安全是很重重要的。
其他單例需要些很多代碼來(lái)保證安全。但是枚舉則不需要:
/**

  • 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;
    }
    }
    我們完全不用考慮序列化和反射的問(wèn)題。枚舉序列化是由jvm保證的,每一個(gè)枚舉類(lèi)型和定義的枚舉變量在JVM中都是唯一的,在枚舉類(lèi)型的序列化和反序列化上,Java做了特殊的規(guī)定:在序列化時(shí)Java僅僅是將枚舉對(duì)象的name屬性輸出到結(jié)果中,反序列化的時(shí)候則是通過(guò)java.lang.Enum的valueOf方法來(lái)根據(jù)名字查找枚舉對(duì)象。同時(shí),編譯器是不允許任何對(duì)這種序列化機(jī)制的定制的并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,從而保證了枚舉實(shí)例的唯一性。
    并且枚舉無(wú)法通過(guò)反射實(shí)例化。
    六、擴(kuò)展
    1、EnumMap
    專(zhuān)門(mén)存<枚舉,String>映射的Map
    現(xiàn)在我們有一堆size大小相同而顏色不同的數(shù)據(jù),需要統(tǒng)計(jì)出每種顏色的數(shù)量是多少以便將數(shù)據(jù)錄入倉(cāng)庫(kù)。
    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,雖然都統(tǒng)計(jì)出了正確的結(jié)果。
    EnumMap要求其Key必須為Enum類(lèi)型,因而使用Color枚舉實(shí)例作為key是最恰當(dāng)不過(guò)了,也避免了獲取name的步驟,更重要的是EnumMap效率更高,因?yàn)槠鋬?nèi)部是通過(guò)數(shù)組實(shí)現(xiàn)的。由于枚舉類(lèi)型實(shí)例的數(shù)量相對(duì)固定并且有限,所以EnumMap使用數(shù)組來(lái)存放與枚舉類(lèi)型對(duì)應(yīng)的值,畢竟數(shù)組是一段連續(xù)的內(nèi)存空間,根據(jù)程序局部性原理,效率會(huì)相當(dāng)高。注意EnumMap的key值不能為null,雖說(shuō)是枚舉專(zhuān)屬集合,但其操作與一般的Map差不多。它只能接收同一枚舉類(lèi)型的實(shí)例作為鍵值且不能為null。
    先看構(gòu)造函數(shù):
    //創(chuàng)建一個(gè)具有指定鍵類(lèi)型的空枚舉映射。
    EnumMap(Class<K> keyType)
    //創(chuàng)建一個(gè)其鍵類(lèi)型與指定枚舉映射相同的枚舉映射,最初包含相同的映射關(guān)系(如果有的話)。
    EnumMap(EnumMap<K,? extends V> m)
    //創(chuàng)建一個(gè)枚舉映射,從指定映射對(duì)其初始化。
    EnumMap(Map<K,? extends V> m)
    與HashMap不同,它需要傳遞一個(gè)類(lèi)型信息,即Class對(duì)象,通過(guò)這個(gè)參數(shù)EnumMap就可以根據(jù)類(lèi)型信息初始化其內(nèi)部數(shù)據(jù)結(jié)構(gòu),另外兩只是初始化時(shí)傳入一個(gè)Map集合,代碼演示如下:
    //使用第一種構(gòu)造
    Map<Color,Integer> enumMap=new EnumMap<>(Color.class);
    //使用第二種構(gòu)造
    Map<Color,Integer> enumMap2=new EnumMap<>(enumMap);
    //使用第三種構(gòu)造
    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幾乎沒(méi)有區(qū)別,注意與HashMap的主要不同在于構(gòu)造方法需要傳遞類(lèi)型參數(shù)和EnumMap保證Key順序與枚舉中的順序一致,但請(qǐng)記住Key不能為null。
    2、EnumMap源碼
    3、EnumSet
    EnumSet是與枚舉類(lèi)型一起使用的專(zhuān)用 Set 集合,EnumSet 中所有元素都必須是枚舉類(lèi)型。與其他Set接口的實(shí)現(xiàn)類(lèi)HashSet/TreeSet(內(nèi)部都是用對(duì)應(yīng)的HashMap/TreeMap實(shí)現(xiàn)的)不同的是,EnumSet在內(nèi)部實(shí)現(xiàn)是位向量(稍后分析),它是一種極為高效的位運(yùn)算操作,由于直接存儲(chǔ)和操作都是bit,因此EnumSet空間和時(shí)間性能都十分可觀,足以媲美傳統(tǒng)上基于 int 的“位標(biāo)志”的運(yùn)算,重要的是我們可像操作set集合一般來(lái)操作位運(yùn)算,這樣使用代碼更簡(jiǎn)單易懂同時(shí)又具備類(lèi)型安全的優(yōu)勢(shì)。注意EnumSet不允許使用 null 元素。試圖插入 null 元素將拋出 NullPointerException,但試圖測(cè)試判斷是否存在null 元素或移除 null 元素則不會(huì)拋出異常,與大多數(shù)collection 實(shí)現(xiàn)一樣,EnumSet不是線程安全的,因此在多線程環(huán)境下應(yīng)該注意數(shù)據(jù)同步問(wèn)題
    4、EnumSet用法
    5、EnumSet實(shí)現(xiàn)原理
    參考:https://blog.csdn.net/javazejian/article/details/71333103
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容