點贊再看,養成習慣,微信搜索【三太子敖丙】關注這個互聯網苟且偷生的工具人。
本文 GitHub https://github.com/JavaFamily 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。
Java基礎:枚舉的用法與原理
在學習過程中,我們也只是在定義常量的時候,會意識到枚舉的存在,而定義常量其實可以在類中實現,這時就會感覺枚舉有點雞肋。但在實際項目開發的過程中,枚舉因相當迷人的特性而受到越來越多的關注。
本文將按以下小節點來,一一介紹枚舉:
枚舉的實現 枚舉的用法 枚舉的原理 枚舉與單例
1. 枚舉的實現
枚舉是JDK1.5之後的特性,在此之前一般是在類中對常量進行定義。那麼爲什麼需要枚舉呢?舉個栗子:
使用靜態變量定義四季
假如我們需要使用四個變量來代表“春夏秋冬”:
public class Season {
public final static int SRPING = 1;
public final static int SUMMER = 2;
public final static int AUTUMN = 3;
public final static int WINTER = 4;
}
這時候只要直接引用Season.SPRING就可以了,我們不需要去操心SPRING在存儲時是什麼數據。但是如果我們想做更多的事:知道下一個季節是什麼,還想把季節打印出來:
public class Season {
private Season(){}
public final static Season SPRING = new Season();
public final static Season SUMMER = new Season();
public final static Season AUTUMN = new Season();
public final static Season WINTER = new Season();
public static Season getNextSeason(Season nowSeason){
if(nowSeason == SPRING){
return SUMMER;
}else if(nowSeason == SUMMER){
return AUTUMN;
}else if(nowSeason == AUTUMN){
return WINTER;
}else{
return SPRING;
}
}
public static void printNowSeason(Season nowSeason){
if(nowSeason == SPRING){
System.out.println("春季");
}else if(nowSeason == SUMMER){
System.out.println("夏季");
}else if(nowSeason == AUTUMN){
System.out.println("秋季");
}else{
System.out.println("冬季");
}
}
public static void main(String[] args){
Season nowSeason = Season.SUMMER;
Season.printNowSeason(nowSeason);
Season nextSeason = Season.getNextSeason(nowSeason);
Season.printNowSeason(nextSeason);
}
}
因爲將Season類的構造方法私有化,外界就不能創建該類的對象了,這就避免了其他奇怪的季節的出現,所有Season對象都在該內部創建。
但是有個問題,用於存儲的int值不見了,所以我們還需要設定另一個方法:
public static int toInt(Season nowSeason){
if(nowSeason == SPRING){
return 1;
}else if(nowSeason == SUMMER){
return 2;
}else if(nowSeason == AUTUMN){
return 3;
}else{
return 4;
}
}
這時如果需要一個Season對象對應的int數據,只需要Season.toInt(Season.SPRING)即可。
但是這種寫法有一個隱患:如果想要擴展功能,需要寫大量的if-else判斷。
這時,枚舉來啦。
枚舉定義四季
我們還是以四季作爲栗子:
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER;
}
好啦,枚舉定義完了。我們來看看怎麼使用它:
class Test{
public static void main(String[] args){
System.out.println(Season.SUMMER); //輸出:SUMMER
}
}
在枚舉中,默認的toString()方法返回的就是枚舉類中對應的名稱。但是我們上面要求打印出來的是如”春季“等,而不是名稱本身,且四季對應的int值也是必要的。所以我們還得自己完善枚舉:
public enum Season {
SPRING(0), SUMMER(1), AUTUMN(2), WINTER(3);
private int value;
private Season(int value){
this.value = value;
}
public static Season getNextSeason(Season nowSeason){
int nextDayValue = nowSeason.value;
if(++nextDayValue == 3){
nextDayValue = 0;
}
return getSeasonByValue(nextDayValue);
}
public static Season getSeasonByValue(int value){
for(Season s : Season.values()){
if(s.value == value){
return s;
}
}
return null;
}
}
class Test{
public static void main(String[] args){
System.out.println("nowSeason->"+Season.SPRING+", value->"+Season.SPRING.ordinal());
System.out.println("nextSeason->"+Season.getNextSeason(Season.SPRING));
}
}
這樣,我們就實現了既定的目標,和之前的代碼相比,沒有那麼多if-else,是不是感覺少了很多煩惱呢?
所以,我們在定義有限的序列時,如星期、性別等,一般會通過靜態變量的形式進行定義,但是這種形式在添加功能的時候,就會需要很多不利於擴展和維護的代碼,所以枚舉的實現,可以簡化這些操作。
2. 枚舉的用法
枚舉類中有些方法還是比較常用的,在此演示幾個比較重要的方法。以四季爲例:
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
Season.valueOf()方法
此方法的作用是傳來一個字符串,然後將它轉換成對應的枚舉變量。前提是傳入的字符串和定義枚舉變量的字符串一模一樣,須區分大小寫。如果傳入了一個不存在的字符串,那麼會拋出異常。
System.out.println(Season.valueOf("spring".toUpperCase()));
System.out.println(Season.valueOf("nyfor2020"));
運行結果爲:
Exception in thread "main" SPRING
java.lang.IllegalArgumentException: No enum constant Season.nyfor2020
at java.lang.Enum.valueOf(Enum.java:238)
at Season.valueOf(Season.java:5)
at Test.main(Season.java:11)
Season.values()方法和Season.ordinal()方法
Season.values()方法會返回包括所有枚舉變量的數據。
默認情況下,枚舉會給所有的枚舉變量提供一個默認的次序,該次序類似數組的下標,從0開始,而Season.ordinal()方法正是可以獲取其次序的方法。
for (Season s: Season.values()){
System.out.println(s + ".ordinal() --> "+s.ordinal());
}
運行結果爲:
SPRING.ordinal() --> 0
SUMMER.ordinal() --> 1
AUTUMN.ordinal() --> 2
WINTER.ordinal() --> 3
Season.toString()方法和Season.name()方法
Season.toString()方法會返回枚舉定義枚舉變量時的字符串。此方法同Season.name()方法是一樣的。
System.out.println("SEASON.SPRING.name --> "+Season.SPRING.name());
System.out.println("SEASON.SPRING.toString --> "+Season.SPRING.toString());
運行結果爲:
SEASON.SPRING.name --> SPRING
SEASON.SPRING.toString --> SPRING
從實現過程來看,name()方法和toString()方法可以說是一樣的。
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
...
public final String name() {
return name;
}
public String toString() {
return name;
}
...
}
但它們之間唯一的區別是,toString()方法可以重寫,但name()方法被final修飾了,不能重寫。
Season.compareTo()方法
這個方法用於比較兩個枚舉變量的“大小”,實際上比較的是兩個枚舉變量之間的次序,並返回次序相減之後的結果。
System.out.println("SEASON.SPRING.compareTo(SEASON.WINTER) --> "+ Season.SPRING.compareTo(Season.WINTER));
運行結果爲:
SEASON.SPRING.compareTo(SEASON.WINTER) --> -3
我們來看看它的源碼:
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
在這裏其實我們就已經可以看到了,compareTo()方法中會先判斷是否屬於同一個枚舉的變量,然後再返回差值。
那麼枚舉有什麼要注意的東西呢?
枚舉使用的是enum關鍵字,而不是class; 枚舉變量之間用逗號隔開,且枚舉變量最好用大寫,多個單詞之間使用“_"隔開(INT_SUM)。 定義完變量之後,以分號結束,如果只是有枚舉變量,而不是自定義變量,分號可以省略。 只需要類名.變量名就可以召喚枚舉變量了,跟使用靜態變量一樣。
枚舉與switch
枚舉是JDK1.5纔有的特性,同時switch也更新了。使用switch進行條件判斷的時候,條件整數一般只能是整型,字符型,而枚舉型確實也被switch所支持。還是用“四季“舉個栗子:
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
class SeasonSwitch{
public void judge(Season s){
switch (s){
case SPRING:
System.out.println("spring");
break;
case SUMMER:
System.out.println("summer");
break;
case AUTUMN:
System.out.println("autumn");
break;
case WINTER:
System.out.println("winter");
break;
}
}
public static void main(String[] args){
Season s = Season.SPRING;
SeasonSwitch seasonSwitch = new SeasonSwitch();
seasonSwitch.judge(s);
}
}
運行結果爲:
spring
枚舉的高級使用方法
我們還是拿四季來做個例子:
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
在這裏,SPRING對應的ordinal值對應的就是0,SUMMER對應的就是1。如果我們想將SPRING的值爲1,那麼就需要自己定義變量:
public enum Season {
SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
private int value;
private Season(int value){
this.value = value;
}
}
如果我們想對一個枚舉變量做兩個維度的描述呢?
public enum Season {
SPRING(1, "spring"), SUMMER(2, "summer"), AUTUMN(3, "autumn"), WINTER(4, "winter");
private int value;
private String lab;
private Season(int value, String lab){
this.value = value;
this.lab = lab;
}
}
總結一下,如果需要自定義枚舉變量,需要注意一下幾點:
一定要把枚舉變量的定義放在第一行,並且以分號結尾; 構造函數必須私有化,但也不是一定要寫private,事實上枚舉的構造函數默認並強制爲private,寫public是無法通過編譯的。 ordinal還是按照它的規則給每個枚舉變量按次序賦值,自定義變量與默認的ordinal屬性並不衝突。
3. 枚舉的原理
我們還是拿“四季”作爲栗子:
public enum Season {
SPRING() {
@Override
public Season getNextSeason() {
return SUMMER;
}
}, SUMMER() {
@Override
public Season getNextSeason() {
return AUTUMN;
}
}, AUTUMN() {
@Override
public Season getNextSeason() {
return WINTER;
}
}, WINTER() {
@Override
public Season getNextSeason() {
return SPRING;
}
};
public abstract Season getNextSeason();
}
反編譯之後,我們可以看到:
>javap Season.class
Compiled from "Season.java"
public abstract class Season extends java.lang.Enum<Season> {
public static final Season SPRING;
public static final Season SUMMER;
public static final Season AUTUMN;
public static final Season WINTER;
public static Season[] values();
public static Season valueOf(java.lang.String);
public abstract Season getNextSeason();
Season(java.lang.String, int, Season$1);
static {};
}
經過編譯器編譯之後,Season是一個繼承了Enum類的抽象類,而且枚舉中定義的枚舉變量變成了相應的public static final屬性,其類型爲抽象類Season類型,名字就是枚舉變量的名字。
同時我們可以看到,Season.class的相同路徑下看到四個內部類的.class文件:
也就是說,這四個枚舉常量分別使用了內部類來實現。
同時還添加了兩個方法values()和valueOf(String s)。我們使用的是默認的無參構造函數,但現在的構造函數有兩個參數。還生成了一個靜態代碼塊。下面我們來詳細看下是怎麼回事兒:
>javap -c -v Season.class
Classfile /E:/Intellij IDEA/project/JVMTest/src/Season.class
Last modified 2020-5-6; size 1114 bytes
MD5 checksum 5fb619a1f14495913ba7820312371ded
Compiled from "Season.java"
public abstract class Season extends java.lang.Enum<Season>
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER, ACC_ABSTRACT, ACC_ENUM
Constant pool:
#1 = Methodref #5.#50 // Season."<init>":(Ljava/lang/String;
I)V
#2 = Fieldref #5.#51 // Season.$VALUES:[LSeason;
#3 = Methodref #52.#53 // "[LSeason;".clone:()Ljava/lang/Obje
ct;
#4 = Class #32 // "[LSeason;"
#5 = Class #54 // Season
#6 = Methodref #24.#55 // java/lang/Enum.valueOf:(Ljava/lang/
Class;Ljava/lang/String;)Ljava/lang/Enum;
#7 = Methodref #24.#50 // java/lang/Enum."<init>":(Ljava/lang
/String;I)V
#8 = Class #56 // Season$1
#9 = String #26 // SPRING
#10 = Methodref #8.#50 // Season$1."<init>":(Ljava/lang/Strin
g;I)V
#11 = Fieldref #5.#57 // Season.SPRING:LSeason;
#12 = Class #58 // Season$2
#13 = String #28 // SUMMER
#14 = Methodref #12.#50 // Season$2."<init>":(Ljava/lang/Strin
g;I)V
#15 = Fieldref #5.#59 // Season.SUMMER:LSeason;
#16 = Class #60 // Season$3
#17 = String #29 // AUTUMN
#18 = Methodref #16.#50 // Season$3."<init>":(Ljava/lang/Strin
g;I)V
#19 = Fieldref #5.#61 // Season.AUTUMN:LSeason;
#20 = Class #62 // Season$4
#21 = String #30 // WINTER
#22 = Methodref #20.#50 // Season$4."<init>":(Ljava/lang/Strin
g;I)V
#23 = Fieldref #5.#63 // Season.WINTER:LSeason;
#24 = Class #64 // java/lang/Enum
#25 = Utf8 InnerClasses
#26 = Utf8 SPRING
#27 = Utf8 LSeason;
#28 = Utf8 SUMMER
#29 = Utf8 AUTUMN
#30 = Utf8 WINTER
#31 = Utf8 $VALUES
#32 = Utf8 [LSeason;
#33 = Utf8 values
#34 = Utf8 ()[LSeason;
#35 = Utf8 Code
#36 = Utf8 LineNumberTable
#37 = Utf8 valueOf
#38 = Utf8 (Ljava/lang/String;)LSeason;
#39 = Utf8 <init>
#40 = Utf8 (Ljava/lang/String;I)V
#41 = Utf8 Signature
#42 = Utf8 ()V
#43 = Utf8 getNextSeason
#44 = Utf8 ()LSeason;
#45 = Utf8 (Ljava/lang/String;ILSeason$1;)V
#46 = Utf8 <clinit>
#47 = Utf8 Ljava/lang/Enum<LSeason;>;
#48 = Utf8 SourceFile
#49 = Utf8 Season.java
#50 = NameAndType #39:#40 // "<init>":(Ljava/lang/String;I)V
#51 = NameAndType #31:#32 // $VALUES:[LSeason;
#52 = Class #32 // "[LSeason;"
#53 = NameAndType #65:#66 // clone:()Ljava/lang/Object;
#54 = Utf8 Season
#55 = NameAndType #37:#67 // valueOf:(Ljava/lang/Class;Ljava/lan
g/String;)Ljava/lang/Enum;
#56 = Utf8 Season$1
#57 = NameAndType #26:#27 // SPRING:LSeason;
#58 = Utf8 Season$2
#59 = NameAndType #28:#27 // SUMMER:LSeason;
#60 = Utf8 Season$3
#61 = NameAndType #29:#27 // AUTUMN:LSeason;
#62 = Utf8 Season$4
#63 = NameAndType #30:#27 // WINTER:LSeason;
#64 = Utf8 java/lang/Enum
#65 = Utf8 clone
#66 = Utf8 ()Ljava/lang/Object;
#67 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
{
public static final Season SPRING;
descriptor: LSeason;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final Season SUMMER;
descriptor: LSeason;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final Season AUTUMN;
descriptor: LSeason;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final Season WINTER;
descriptor: LSeason;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static Season[] values();
descriptor: ()[LSeason;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #2 // Field $VALUES:[LSeason;
3: invokevirtual #3 // Method "[LSeason;".clone:()Ljav
a/lang/Object;
6: checkcast #4 // class "[LSeason;"
9: areturn
LineNumberTable:
line 7: 0
public static Season valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)LSeason;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #5 // class Season
2: aload_0
3: invokestatic #6 // Method java/lang/Enum.valueOf:(
Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #5 // class Season
9: areturn
LineNumberTable:
line 7: 0
public abstract Season getNextSeason();
descriptor: ()LSeason;
flags: ACC_PUBLIC, ACC_ABSTRACT
Season(java.lang.String, int, Season$1);
descriptor: (Ljava/lang/String;ILSeason$1;)V
flags: ACC_SYNTHETIC
Code:
stack=3, locals=4, args_size=4
0: aload_0
1: aload_1
2: iload_2
3: invokespecial #1 // Method "<init>":(Ljava/lang/Str
ing;I)V
6: return
LineNumberTable:
line 7: 0
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: new #8 // class Season$1
3: dup
4: ldc #9 // String SPRING
6: iconst_0
7: invokespecial #10 // Method Season$1."<init>":(Ljava/lang/String;I)V
10: putstatic #11 // Field SPRING:LSeason;
13: new #12 // class Season$2
16: dup
17: ldc #13 // String SUMMER
19: iconst_1
20: invokespecial #14 // Method Season$2."<init>":(Ljava/lang/String;I)V
23: putstatic #15 // Field SUMMER:LSeason;
26: new #16 // class Season$3
29: dup
30: ldc #17 // String AUTUMN
32: iconst_2
33: invokespecial #18 // Method Season$3."<init>":(Ljava/lang/String;I)V
36: putstatic #19 // Field AUTUMN:LSeason;
39: new #20 // class Season$4
42: dup
43: ldc #21 // String WINTER
45: iconst_3
46: invokespecial #22 // Method Season$4."<init>":(Ljava/lang/String;I)V
49: putstatic #23 // Field WINTER:LSeason;
52: iconst_4
53: anewarray #5 // class Season
56: dup
57: iconst_0
58: getstatic #11 // Field SPRING:LSeason;
61: aastore
62: dup
63: iconst_1
64: getstatic #15 // Field SUMMER:LSeason;
67: aastore
68: dup
69: iconst_2
70: getstatic #19 // Field AUTUMN:LSeason;
73: aastore
74: dup
75: iconst_3
76: getstatic #23 // Field WINTER:LSeason;
79: aastore
80: putstatic #2 // Field $VALUES:[LSeason;
83: return
LineNumberTable:
line 8: 0
line 13: 13
line 18: 26
line 23: 39
line 7: 52
}
Signature: #47 // Ljava/lang/Enum<LSeason;>;
SourceFile: "Season.java"
InnerClasses:
static #20; //class Season$4
static #16; //class Season$3
static #12; //class Season$2
static #8; //class Season$1
下面分析一下字節碼中各部分內容,先拿靜態代碼塊下手:
靜態代碼塊
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
//創建一個Season$1的內部類對象
0: new #8 // class Season$1
3: dup
//接下來的兩條指令,是將兩個參數推送到棧頂,調用Season$1的編譯器生成的<init>方法
4: ldc #9 // String SPRING
6: iconst_0
//調用Season$1的<init>方法
7: invokespecial #10 // Method Season$1."<init>":(Ljava/lang/String;I)V
//設置SPRING屬性的值爲新創建的對象
10: putstatic #11 // Field SPRING:LSeason;
//接下來說是分別初始化另外三個屬性SUMMER、AUTUMU、WINTER,此處就不贅述了
13: new #12 // class Season$2
16: dup
17: ldc #13 // String SUMMER
19: iconst_1
20: invokespecial #14 // Method Season$2."<init>":(Ljava/lang/String;I)V
23: putstatic #15 // Field SUMMER:LSeason;
26: new #16 // class Season$3
29: dup
30: ldc #17 // String AUTUMN
32: iconst_2
33: invokespecial #18 // Method Season$3."<init>":(Ljava/lang/String;I)V
36: putstatic #19 // Field AUTUMN:LSeason;
39: new #20 // class Season$4
42: dup
43: ldc #21 // String WINTER
45: iconst_3
46: invokespecial #22 // Method Season$4."<init>":(Ljava/lang/String;I)V
49: putstatic #23 // Field WINTER:LSeason;
52: iconst_4
53: anewarray #5 // class Season
56: dup
57: iconst_0
58: getstatic #11 // Field SPRING:LSeason;
61: aastore
62: dup
63: iconst_1
64: getstatic #15 // Field SUMMER:LSeason;
67: aastore
68: dup
69: iconst_2
70: getstatic #19 // Field AUTUMN:LSeason;
73: aastore
74: dup
75: iconst_3
76: getstatic #23 // Field WINTER:LSeason;
79: aastore
//將剛創建的數組設置爲屬性$VALUES的值
80: putstatic #2 // Field $VALUES:[LSeason;
83: return
靜態代碼塊部分做的工作,就是分別設置生成的四個公共靜態常量字段的值,同時編譯器還生成一個靜態字段$VALUES,保存的是枚舉類型定義的所有枚舉常量。相當於以下代碼:
Season SPRING = new Season1();
Season SUMMER = new Season2();
Season AUTUMN = new Season3();
Season WINTER = new Season4();
Season[] $VALUES = new Season[4];
$VALUES[0] = SPRING;
$VALUES[1] = SUMMER;
$VALUES[2] = AUTUMN;
$VALUES[3] = WINTER;
values()方法
接下來我們來看看編譯器爲我們生成的values()方法:
public static Season[] values();
descriptor: ()[LSeason;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #2 // Field $VALUES:[LSeason;
3: invokevirtual #3 // Method "[LSeason;".clone:()Ljav
a/lang/Object;
6: checkcast #4 // class "[LSeason;"
9: areturn
values()方法是一個公共的靜態方法,所以我們可以直接調用該方法,返回枚舉的數組。而這個方法實現的是,將靜態代碼塊中初始化的$VALUES字段的值克隆出來,並且強制轉換成Season[]類型返回,就相當於以下代碼:
public static Season[] values(){
return (Season[])$VALUES.clone();
}
valueOf()方法
接下來我們來看另一個由編譯器生成的valueOf()方法:
public static Season valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)LSeason;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #5 // class Season
2: aload_0
3: invokestatic #6 // Method java/lang/Enum.valueOf:(
Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #5 // class Season
9: areturn
valueOf()也是一個公共的靜態方法,所以可以直接調用這個方法並返回參數字符串表示的枚舉變量,另外,這個方法的實現是調用Enum.valueOf()方法,並把類型強制轉換爲Season,它相當於如下的代碼:
public static Season valueOf(String s){
return (Season)Enum.valueOf(Season.class, s);
}
最後,我們來看下編譯器生成的內部類是什麼樣的。
內部類
我們以Season$1爲例:
>javap Season$1.class
Compiled from "Season.java"
final class Season$1 extends Season {
Season$1(java.lang.String, int);
public Season getNextSeason();
}
可以看到,Season1的構造函數有兩個入參呢?
關於這個問題,我們還是得從Season的父類Enum說起。
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
private final String name;
public final String name() {
return name;
}
private final int ordinal;
public final int ordinal() {
return ordinal;
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
......
}
從Enum中我們可以看到,每個枚舉都定義了兩個屬性,name和ordinal,name表示枚舉變量的名稱,而ordinal則是根據變量定義的順序授予的整型值,從0開始。
在枚舉變量初始化的時候,會自動初始化這兩個字段,設置相應的值,所以會在Season()的構造方法中添加兩個參數。
而且我們可以從Enum的源碼中看到,大部分的方法都是final修飾的,特別是clone、readObject、writeObject這三個方法,保證了枚舉類型的不可變性,不能通過克隆、序列化和反序列化複製枚舉,這就保證了枚舉變量只是一個實例,即是單例的。
總結一下,其實枚舉本質上也是通過普通的類來實現的,只是編譯器爲我們進行了處理。每個枚舉類型都繼承自Enum類,並由編譯器自動添加了values()和valueOf()方法,每個枚舉變量是一個靜態常量字段,由內部類實現,而這個內部類繼承了此枚舉類。
所有的枚舉變量都是通過靜態代碼塊進行初始化,也就是說在類加載期間就實現了。
另外,通過把clone、readObject、writeObject這三個方法定義爲final,保證了每個枚舉類型及枚舉常量都是不可變的,也就是說,可以用枚舉實現線程安全的單例。
4. 枚舉與單例
枚舉類實現單例模式相當硬核,因爲枚舉類型是線程安全的,且只會裝載一次。使用枚舉類來實現單例模式,是所有的單例實現中唯一一種不會被破壞的單例模式實現。
public class SingletonObject {
private SingletonObject() {
}
private enum Singleton {
INSTANCE;
private final SingletonObject instance;
Singleton() {
instance = new SingletonObject();
}
private SingletonObject getInstance() {
return instance;
}
}
public static SingletonObject getInstance() {
return Singleton.INSTANCE.getInstance();
}
}
結語
在學習Java枚舉類的時候,原本列出來了很多問題如Java枚舉的線程安全和序列化問題,但是在瞭解完Java枚舉的原理之後,這些問題,都迎刃而解了,也許在未來可能會碰上枚舉的特例吧。
本文主要對final關鍵字進行介紹,如果本文對你有幫助,請給一個贊吧,這會是我最大的動力~
我是敖丙,一個在互聯網苟且偷生的工具人。
你知道的越多,你不知道的越多,人才們的 【三連】 就是丙丙創作的最大動力,我們下期見!
注:如果本篇博客有任何錯誤和建議,歡迎人才們留言,你快說句話啊!
文章持續更新,可以微信搜索「 三太子敖丙 」第一時間閱讀,回覆【資料】有我準備的一線大廠面試資料和簡歷模板,本文 GitHub https://github.com/JavaFamily 已經收錄,有大廠面試完整考點,歡迎Star。