關(guān)于Java中枚舉Enum的深入剖析

在編程語言中我們蒲牧,都會(huì)接觸到枚舉類型惦费,通常我們進(jìn)行有窮的列舉來實(shí)現(xiàn)一些限定趟脂。Java也不例外泰讽。Java中的枚舉類型為Enum,本文將對(duì)枚舉進(jìn)行一些比較深入的剖析昔期。

什么是Enum

Enum是自Java 5 引入的特性已卸,用來方便Java開發(fā)者實(shí)現(xiàn)枚舉應(yīng)用。一個(gè)簡單的Enum使用如下硼一。

// ColorEnum.java

public enum ColorEmun {

? ? RED,

? ? GREEN,

? ? YELLOW

}

public void setColorEnum(ColorEmun colorEnum) {

? ? //some code here

}

setColorEnum(ColorEmun.GREEN);

為什么會(huì)有Enum

在Enum之前的我們使用類似如下的代碼實(shí)現(xiàn)枚舉的功能.

public static final int COLOR_RED = 0;

public static final int COLOR_GREEN = 1;

public static final int COLOR_YELLOW = 2;

public void setColor(int color) {

? ? //some code here

}

//調(diào)用

setColor(COLOR_RED)

然而上面的還是有不盡完美的地方

setColor(COLOR_RED)與setColor(0)效果一樣,而后者可讀性很差,但卻可以正常運(yùn)行

setColor方法可以接受枚舉之外的值,比如setColor(3),這種情況下程序可能出問題

概括而言,傳統(tǒng)枚舉有如下兩個(gè)弊端

安全性

可讀性,尤其是打印日志時(shí)

因此Java引入了Enum,使用Enum,我們實(shí)現(xiàn)上面的枚舉就很簡單了累澡,而且還可以輕松避免傳入非法值的風(fēng)險(xiǎn).

枚舉原理是什么

Java中Enum的本質(zhì)其實(shí)是在編譯時(shí)期轉(zhuǎn)換成對(duì)應(yīng)的類的形式。

首先,為了探究枚舉的原理,我們先簡單定義一個(gè)枚舉類,這里以季節(jié)為例,類名為Season,包含春夏秋冬四個(gè)枚舉條目.

public enum Season {

? ? SPRING,

? ? SUMMER,

? ? AUTUMN,

? ? WINTER

}

然后我們使用javac編譯上面的類,得到class文件.

javac Season.java

然后,我們利用反編譯的方法來看看字節(jié)碼文件究竟是什么.這里使用的工具是javap的簡單命令,先列舉一下這個(gè)Season下的全部元素.

?? company javap Season

Warning: Binary file Season contains com.company.Season

Compiled from "Season.java"

public final class com.company.Season extends java.lang.Enum<com.company.Season> {

? public static final com.company.Season SPRING;

? public static final com.company.Season SUMMER;

? public static final com.company.Season AUTUMN;

? public static final com.company.Season WINTER;

? public static com.company.Season[] values();

? public static com.company.Season valueOf(java.lang.String);

? static {};

}

從上反編譯結(jié)果可知

java代碼中的Season轉(zhuǎn)換成了繼承自的java.lang.enum的類

既然隱式繼承自java.lang.enum,也就意味java代碼中,Season不能再繼承其他的類

Season被標(biāo)記成了final,意味著它不能被繼承

static代碼塊

使用javap具體反編譯class文件,得到靜態(tài)代碼塊相關(guān)的結(jié)果為

static {};

? ? Code:

? ? ? 0: new? ? ? ? ? #4? ? ? ? ? ? ? ? ? // class com/company/Season

? ? ? 3: dup

? ? ? 4: ldc? ? ? ? ? #7? ? ? ? ? ? ? ? ? // String SPRING

? ? ? 6: iconst_0

? ? ? 7: invokespecial #8? ? ? ? ? ? ? ? ? // Method "<init>":(Ljava/lang/String;I)V

? ? ? 10: putstatic? ? #9? ? ? ? ? ? ? ? ? // Field SPRING:Lcom/company/Season;

? ? ? 13: new? ? ? ? ? #4? ? ? ? ? ? ? ? ? // class com/company/Season

? ? ? 16: dup

? ? ? 17: ldc? ? ? ? ? #10? ? ? ? ? ? ? ? // String SUMMER

? ? ? 19: iconst_1

? ? ? 20: invokespecial #8? ? ? ? ? ? ? ? ? // Method "<init>":(Ljava/lang/String;I)V

? ? ? 23: putstatic? ? #11? ? ? ? ? ? ? ? // Field SUMMER:Lcom/company/Season;

? ? ? 26: new? ? ? ? ? #4? ? ? ? ? ? ? ? ? // class com/company/Season

? ? ? 29: dup

? ? ? 30: ldc? ? ? ? ? #12? ? ? ? ? ? ? ? // String AUTUMN

? ? ? 32: iconst_2

? ? ? 33: invokespecial #8? ? ? ? ? ? ? ? ? // Method "<init>":(Ljava/lang/String;I)V

? ? ? 36: putstatic? ? #13? ? ? ? ? ? ? ? // Field AUTUMN:Lcom/company/Season;

? ? ? 39: new? ? ? ? ? #4? ? ? ? ? ? ? ? ? // class com/company/Season

? ? ? 42: dup

? ? ? 43: ldc? ? ? ? ? #14? ? ? ? ? ? ? ? // String WINTER

? ? ? 45: iconst_3

? ? ? 46: invokespecial #8? ? ? ? ? ? ? ? ? // Method "<init>":(Ljava/lang/String;I)V

? ? ? 49: putstatic? ? #15? ? ? ? ? ? ? ? // Field WINTER:Lcom/company/Season;

? ? ? 52: iconst_4

? ? ? 53: anewarray? ? #4? ? ? ? ? ? ? ? ? // class com/company/Season

? ? ? 56: dup

? ? ? 57: iconst_0

? ? ? 58: getstatic? ? #9? ? ? ? ? ? ? ? ? // Field SPRING:Lcom/company/Season;

? ? ? 61: aastore

? ? ? 62: dup

? ? ? 63: iconst_1

? ? ? 64: getstatic? ? #11? ? ? ? ? ? ? ? // Field SUMMER:Lcom/company/Season;

? ? ? 67: aastore

? ? ? 68: dup

? ? ? 69: iconst_2

? ? ? 70: getstatic? ? #13? ? ? ? ? ? ? ? // Field AUTUMN:Lcom/company/Season;

? ? ? 73: aastore

? ? ? 74: dup

? ? ? 75: iconst_3

? ? ? 76: getstatic? ? #15? ? ? ? ? ? ? ? // Field WINTER:Lcom/company/Season;

? ? ? 79: aastore

? ? ? 80: putstatic? ? #1? ? ? ? ? ? ? ? ? // Field $VALUES:[Lcom/company/Season;

? ? ? 83: return

}

其中

0~52為實(shí)例化SPRING, SUMMER, AUTUMN, WINTER

53~83為創(chuàng)建Season[]數(shù)組$VALUES,并將上面的四個(gè)對(duì)象放入數(shù)組的操作.

values方法

values方法的的返回值實(shí)際上就是上面$VALUES數(shù)組對(duì)象

swtich中的枚舉

在Java中,switch-case是我們經(jīng)常使用的流程控制語句.當(dāng)枚舉出來之后,switch-case也很好的進(jìn)行了支持.

比如下面的代碼是完全正常編譯,正常運(yùn)行的.

public static void main(String[] args) {

? ? ? ? Season season = Season.SPRING;

? ? ? ? switch(season) {

? ? ? ? ? ? case SPRING:

? ? ? ? ? ? ? ? System.out.println("It's Spring");

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? case WINTER:

? ? ? ? ? ? ? ? System.out.println("It's Winter");

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? case SUMMER:

? ? ? ? ? ? ? ? System.out.println("It's Summer");

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? case AUTUMN:

? ? ? ? ? ? ? ? System.out.println("It's Autumn");

? ? ? ? ? ? ? ? break;

? ? ? ? }

? ? }

不過,通常情況下switch-case支持類似int的類型,那么它是怎么做到對(duì)Enum的支持呢,我們反編譯上述方法看一下字節(jié)碼的真實(shí)情況.

public static void main(java.lang.String[]);

? ? Code:

? ? ? 0: getstatic? ? #2? ? ? ? ? ? ? ? ? // Field com/company/Season.SPRING:Lcom/company/Season;

? ? ? 3: astore_1

? ? ? 4: getstatic? ? #3? ? ? ? ? ? ? ? ? // Field com/company/Main$1.$SwitchMap$com$company$Season:[I

? ? ? 7: aload_1

? ? ? 8: invokevirtual #4? ? ? ? ? ? ? ? ? // Method com/company/Season.ordinal:()I

? ? ? 11: iaload

? ? ? 12: tableswitch? { // 1 to 4

? ? ? ? ? ? ? ? ? ? 1: 44

? ? ? ? ? ? ? ? ? ? 2: 55

? ? ? ? ? ? ? ? ? ? 3: 66

? ? ? ? ? ? ? ? ? ? 4: 77

? ? ? ? ? ? ? default: 85

? ? ? ? ? }

? ? ? 44: getstatic? ? #5? ? ? ? ? ? ? ? ? // Field java/lang/System.out:Ljava/io/PrintStream;

? ? ? 47: ldc? ? ? ? ? #6? ? ? ? ? ? ? ? ? // String It's Spring

? ? ? 49: invokevirtual #7? ? ? ? ? ? ? ? ? // Method java/io/PrintStream.println:(Ljava/lang/String;)V

? ? ? 52: goto? ? ? ? ? 85

? ? ? 55: getstatic? ? #5? ? ? ? ? ? ? ? ? // Field java/lang/System.out:Ljava/io/PrintStream;

? ? ? 58: ldc? ? ? ? ? #8? ? ? ? ? ? ? ? ? // String It's Winter

? ? ? 60: invokevirtual #7? ? ? ? ? ? ? ? ? // Method java/io/PrintStream.println:(Ljava/lang/String;)V

? ? ? 63: goto? ? ? ? ? 85

? ? ? 66: getstatic? ? #5? ? ? ? ? ? ? ? ? // Field java/lang/System.out:Ljava/io/PrintStream;

? ? ? 69: ldc? ? ? ? ? #9? ? ? ? ? ? ? ? ? // String It's Summer

? ? ? 71: invokevirtual #7? ? ? ? ? ? ? ? ? // Method java/io/PrintStream.println:(Ljava/lang/String;)V

? ? ? 74: goto? ? ? ? ? 85

? ? ? 77: getstatic? ? #5? ? ? ? ? ? ? ? ? // Field java/lang/System.out:Ljava/io/PrintStream;

? ? ? 80: ldc? ? ? ? ? #10? ? ? ? ? ? ? ? // String It's Autumn

? ? ? 82: invokevirtual #7? ? ? ? ? ? ? ? ? // Method java/io/PrintStream.println:(Ljava/lang/String;)V

? ? ? 88: return

注意上面代碼塊有這樣的一段代碼

8: invokevirtual #4? ? ? ? ? ? ? ? ? // Method com/company/Season.ordinal:()I

事實(shí)果真如此,在switch-case中,還是將Enum轉(zhuǎn)成了int值(通過調(diào)用Enum.oridinal()方法)

枚舉與混淆

在Android開發(fā)中,進(jìn)行混淆是我們?cè)诎l(fā)布前必不可少的工作,混下后,我們能增強(qiáng)反編譯的難度,在一定程度上保護(hù)了增強(qiáng)了安全性.

而開發(fā)人員處理混淆更多的是將某些元素加入不混淆的名單,這里枚舉就是需要排除混淆的.

在默認(rèn)的混淆配置文件中,已經(jīng)加入了關(guān)于對(duì)枚舉混淆的處理

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations

-keepclassmembers enum * {

? ? public static **[] values();

? ? public static ** valueOf(java.lang.String);

}

關(guān)于為什么要保留values()方法和valueOf()方法般贼,請(qǐng)參考文章讀懂 Android 中的代碼混淆 關(guān)于枚舉的部分

使用proguard優(yōu)化

使用Proguard進(jìn)行優(yōu)化愧哟,可以將枚舉盡可能的轉(zhuǎn)換成int。配置如下

-optimizations class/unboxing/enum

確保上述代碼生效哼蛆,需要確proguard配置文件不包含-dontoptimize指令蕊梧。

當(dāng)我們使用gradlew打包是,看到類似下面的輸出腮介,即Number of unboxed enum classes:1代表已經(jīng)將一個(gè)枚舉轉(zhuǎn)換成了int的形式肥矢。

Optimizing...

? Number of finalized classes:? ? ? ? ? ? ? ? 0? (disabled)

? Number of unboxed enum classes:? ? ? ? ? ? ? 1

? Number of vertically merged classes:? ? ? ? 0? (disabled)

? Number of horizontally merged classes:? ? ? 0? (disabled)

枚舉單例

單例模式是我們?cè)谌粘i_發(fā)中可謂是最常用的設(shè)計(jì)模式.

然后要設(shè)計(jì)好單例模式,無非考慮一下幾點(diǎn)

確保只有唯一實(shí)例,不多創(chuàng)建多余實(shí)例

確保實(shí)例按需創(chuàng)建.

因此傳統(tǒng)的做法想要實(shí)現(xiàn)單例,大致有一下幾種

餓漢式加載

懶漢式synchronize和雙重檢查

利用java的靜態(tài)加載機(jī)制

相比上述的方法,使用枚舉也可以實(shí)現(xiàn)單例,而且還更加簡單.

public enum AppManager {

? ? INSTANCE;

? ? private String tagName;

? ? public void setTag(String tagName) {

? ? ? ? this.tagName = tagName;

? ? }

? ? public String getTag() {

? ? ? ? return tagName;

? ? }

}

調(diào)用起來也更加簡單

AppManager.INSTANCE.getTag();

枚舉如何確保唯一實(shí)例

因?yàn)楂@得實(shí)例只能通過AppManager.INSTANCE

下面的方式是不可以的

AppManager appManager = new AppManager(); //compile error

關(guān)于單例模式,可以閱讀單例這種設(shè)計(jì)模式了解更多叠洗。

(Android中)該不該用枚舉

既然上面提到了枚舉會(huì)轉(zhuǎn)換成類甘改,這樣理論上造成了下面的問題

增加了dex包的大小旅东,理論上dex包越大,加載速度越慢

同時(shí)使用枚舉十艾,運(yùn)行時(shí)的內(nèi)存占用也會(huì)相對(duì)變大

關(guān)于上面兩點(diǎn)的驗(yàn)證抵代,秋百萬已經(jīng)做了詳細(xì)的論證,大家可以參考這篇文章《Android 中的 Enum 到底占多少內(nèi)存疟羹?該如何用主守?》

關(guān)于枚舉是否使用的結(jié)論,大家可以參考

如果你開發(fā)的是Framework不建議使用enum

如果是簡單的enum榄融,可以使用int很輕松代替参淫,則不建議使用enum

另外,如果是Android中愧杯,可以使用下面介紹的枚舉注解來實(shí)現(xiàn)涎才。

除此之外,我們還需要對(duì)比可讀性和易維護(hù)性來與性能進(jìn)行衡量力九,從中進(jìn)行做出折中

在Android中的替代

Android中新引入的替代枚舉的注解有IntDef和StringDef,這里以IntDef做例子說明一下.

public class Colors {

? ? @IntDef({RED, GREEN, YELLOW})

? ? @Retention(RetentionPolicy.SOURCE)

? ? public @interface LightColors{}

? ? public static final int RED = 0;

? ? public static final int GREEN = 1;

? ? public static final int YELLOW = 2;

}

聲明必要的int常量

聲明一個(gè)注解為LightColors

使用@IntDef修飾LightColors,參數(shù)設(shè)置為待枚舉的集合

使用@Retention(RetentionPolicy.SOURCE)指定注解僅存在與源碼中,不加入到class文件中

比如我們用來標(biāo)注方法的參數(shù)

private void setColor(@Colors.LightColors int color) {

? ? ? ? Log.d("MainActivity", "setColor color=" + color);

}

調(diào)用的該方法的時(shí)候

setColor(Colors.GREEN);

Java高架構(gòu)師耍铜、分布式架構(gòu)、高可擴(kuò)展跌前、高性能棕兼、高并發(fā)、性能優(yōu)化抵乓、Spring boot伴挚、Redis、ActiveMQ灾炭、Nginx茎芋、Mycat、Netty蜈出、Jvm大型分布式項(xiàng)目實(shí)戰(zhàn)學(xué)習(xí)架構(gòu)師視頻免費(fèi)獲取架構(gòu)群:854180697? ??加群鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末田弥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子铡原,更是在濱河造成了極大的恐慌偷厦,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件燕刻,死亡現(xiàn)場(chǎng)離奇詭異沪哺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)酌儒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門辜妓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事籍滴±乙模” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵孽惰,是天一觀的道長晚岭。 經(jīng)常有香客問我,道長勋功,這世上最難降的妖魔是什么坦报? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮狂鞋,結(jié)果婚禮上片择,老公的妹妹穿的比我還像新娘。我一直安慰自己骚揍,他們只是感情好字管,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著信不,像睡著了一般嘲叔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抽活,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天硫戈,我揣著相機(jī)與錄音,去河邊找鬼下硕。 笑死丁逝,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的卵牍。 我是一名探鬼主播果港,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼沦泌,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼糊昙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谢谦,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤释牺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后回挽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體没咙,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年千劈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祭刚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖涡驮,靈堂內(nèi)的尸體忽然破棺而出暗甥,到底是詐尸還是另有隱情,我是刑警寧澤捉捅,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布撤防,位于F島的核電站,受9級(jí)特大地震影響棒口,放射性物質(zhì)發(fā)生泄漏寄月。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一无牵、第九天 我趴在偏房一處隱蔽的房頂上張望漾肮。 院中可真熱鬧,春花似錦合敦、人聲如沸初橘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽保檐。三九已至,卻和暖如春崔梗,著一層夾襖步出監(jiān)牢的瞬間夜只,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國打工蒜魄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扔亥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓谈为,卻偏偏與公主長得像旅挤,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子伞鲫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容