在編程語言中我們蒲牧,都會(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? ??加群鏈接