1.為什么要有枚舉
Java中的枚舉其實是一種語法糖,在 JDK 1.5之后出現,用來表示固定且有限個的對象。比如一個季節類有春、夏、秋、冬四個對象;一個星期有星期一到星期日七個對象。這些明顯都是固定的,且有限個。那jdk5之前如果想表示有限個對象,代碼是怎么寫的嘞?這樣寫有什么缺點,最終讓jdk官方人員(大部分是美國人)看不下去了吶,我這里給大家舉一個2020火熱的事件,來體會,話不多說,我wcc直接上代碼:
package com.huawei.subtitle.portal.com;
/*
為什么需要Enum
*/
public class AmericanFamily {
public static final String bigSon = "特朗普";
public static final String smallSon = "拜登";
public static final String daughter = "奧巴馬";
//public static final String daughter = "羅斯福";
public static void main(String[] args) {
String person = "拜登";
ourPresident(person);
System.out.println("--------------------");
String chineseBoyWccNickName = "特朗普";
ourPresident(chineseBoyWccNickName);
System.out.println("你看到了,我是中國小子wcc,可不是AmericanFamily成員");
}
public static void ourPresident(String person) {
if (AmericanFamily.bigSon.equals(person)) {
System.out.println(" our president 川建國,2020 我挺你");
}
if (AmericanFamily.smallSon.equals(person)) {
System.out.println(" our president 拜登");
}
if (AmericanFamily.daughter.equals(person)) {
System.out.println(" our president 奧巴馬");
}
/*
如果上面定義了常量羅斯福,這里就要增加個if'判斷',也就是就要改代碼哈
if (AmericanFamily.daughter.equals(person)) {
System.out.println(" our president 奧巴馬");
}
*/
}
}
大家看到了吧,結果竟然是中國小子wcc因為昵稱叫特朗普
,當美國人將我進行校驗的時候,得到的結果是這是美國總統特朗普哈哈。於是jdk官網人員開始反思,我目前主要是看出了2點:
- 美國人肯定不干呀最后,jdk官方這些人更是看不下去,這驗證方式不對呀,這家伙都不會美國人,這也通過了校驗。這不行,我得把類型也得校驗住了,最好在編譯期就提示這個有問題,冒牌貨
- 這個代碼寫的不好呀,隨着時間的更替,美國總統族譜上的人越來越多,每次增加了常量,ourPresident()這個方法就要增加判斷邏輯,這代碼水呀,我可是jdk官方,這得改。
為了讓編譯器能自動檢查某個值在枚舉的集合內,並且,不同用途的枚舉需要不同的類型來標記,不能混用,我們可以使用enum來定義枚舉類,於是終於在jdk1.5版本,Enum枚舉出來了,接下來我們看下怎么使用,以及它的原理。
2.Enum定義和常用方法
一般的定義形式:
在定義枚舉類型時我們使用的關鍵字是enum,與class關鍵字類似,enum 枚舉名{ 枚舉值表 }; 在枚舉值表中應羅列出所有可用值。這些值也稱為枚舉元素。例如:
enum Color {
RED, GREEN, BLUE;
}
枚舉類型Color中定義了顏色的值,這里要注意,值一般是大寫的字母,多個值之間以逗號分隔。同時我們應該知道的是枚舉類型可以像類(class)類型一樣,定義為一個單獨的文件,當然也可以定義在其他類內部,更重要的是枚舉常量在類型安全性和便捷性都很有保證,如果出現類型問題編譯器也會提示我們改進,但務必記住枚舉表示的類型其取值是必須有限的,也就是說每個值都是可以枚舉出來的,比如上述描述的一周共有七天。
使用枚舉直接用枚舉名.枚舉值即可,例如Color.RED
enum Color {
RED, GREEN, BLUE;
}
public class Test {
// 執行輸出結果
public static void main(String[] args) {
Color c1 = Color.RED;
System.out.println(c1);
}
}
2.1Enum類常見的方法:
我們使用enum關鍵字(注意是小寫的),創建對應的枚舉類時,默認是繼承了Enum類的,而Enum類里自帶了一些方法並且進行了實現,大家可以看下Enum源碼。
(1) ordinal()方法: 返回枚舉值在枚舉類種的順序。這個順序根據枚舉值聲明的順序而定。(順序是以0開始的,即0,1,2......)
Color.RED.ordinal(); //返回結果:0
Color.BLUE.ordinal(); //返回結果:2
(2) compareTo()方法: Enum實現了java.lang.Comparable接口,因此可以比較對象與指定對象的順序。Enum中的compareTo返回的是兩個枚舉值的順序之差。當然,前提是兩個枚舉值必須屬於同一個枚舉類,否則會拋出ClassCastException()異常。(具體可見源代碼)
Color.RED.compareTo(Color.BLUE); //返回結果 -2,即(0-2)
(3) values()方法: 靜態方法,返回一個包含全部枚舉值的數組。
Color[] colors=Color.values();
for(Color c:colors){
System.out.print(c+",");
}//返回結果:RED,GREEN,BLUE
(4) toString()方法: 返回枚舉常量的名稱。和.name一樣的效果
Color c=Color.RED;
System.out.println(c);//返回結果: RED
(5) valueOf()方法: 這個方法和toString方法是相對應的,返回帶指定名稱的指定枚舉類型的枚舉常量。
Color.valueOf("BLUE"); //返回結果: Color.BLUE
(6) equals()方法: 比較兩個枚舉類對象的引用是否相同,每一個枚舉值其實都是一個類的實例,這個接下來我會講下。
因為這些api比較簡單,就不寫例子一一舉例了,我們把美國總統這個例子改寫下,讓大家體會下使用枚舉后有么有將我們第一部分的問題給解決:
public enum AmericanFamily {
teRuPu,baiDeng,obama,wccHaHa;
public static void ourPresident(AmericanFamily person) {
//大家看這塊代碼是不是以后都不用管改了哈
for (AmericanFamily human:AmericanFamily.values()){
if (human.equals(person)){
System.out.println("this is our president" +" "+ person);
}
}
}
public static void enumMethodTest(){
AmericanFamily teRuPu = AmericanFamily.teRuPu;
int ordinal = teRuPu.ordinal();
int i = teRuPu.compareTo(AmericanFamily.wccHaHa);
boolean same = teRuPu.equals(AmericanFamily.wccHaHa);
System.out.println(teRuPu);
System.out.println(ordinal);
System.out.println(i);
System.out.println(same);
}
public static void main(String[] args) {
AmericanFamily person = AmericanFamily.teRuPu;
ourPresident(person);
System.out.println("--------------------");
String chineseBoyWccNickName = "特朗普";
System.out.println("你看到了,我是中國小子wcc,可不是AmericanFamily成員,強行使用,我異常了");
// ourPresident(chineseBoyWccNickName);編譯器檢查就通不過了
// 不兼容的類型: java.lang.String無法轉換為com.huawei.subtitle.portal.com.AmericanFamily
ourPresident(AmericanFamily.wccHaHa);
System.out.println("--------------------");
enumMethodTest();
}
}
3.枚舉的原理
我們來看下,我們簡單的創建一個枚舉類,Java底層為我們做了什么,為了搞反編譯,試了很多,因為Java的發展,很多反編譯軟件可能不支持Java的新的語法特性,或者支持不友好:
這里僅附上我使用的反編譯工具下載地址:jad反編譯下載,
**直接使用jad命令反編譯,具體如下:**
- 1. 打開命令,將當前位置指向xjad所在目錄
- 2. 使用Jad -sjava xxx.class
即可在同目錄下生成xxx.java的反編譯后的文件
public enum Day {
MONDAY, TUESDAY, WEDNESDAY,THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}
這里直接展示反編譯的結果,具體使用不同工具反編譯的差別,請查看我另一篇文章--暫時起名反編譯工具使用吧嘻嘻。話不多說,附上反編譯后代碼,從反編譯代碼中看這個類的真實樣子:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Day.java
package com.huawei.subtitle.portal;
public final class Day extends Enum {
public static Day[] values() {
return (Day[])$VALUES.clone();
}
public static Day valueOf(String s) {
return (Day)Enum.valueOf(Day, s);
}
private Day(String s, int i) {
super(s, i);
}
public static final Day MONDAY;
public static final Day TUESDAY;
public static final Day WEDNESDAY;
public static final Day THURSDAY;
public static final Day FRIDAY;
public static final Day SATURDAY;
public static final Day SUNDAY;
private static final Day $VALUES[];
static {
MONDAY = new Day("MONDAY", 0);
TUESDAY = new Day("TUESDAY", 1);
WEDNESDAY = new Day("WEDNESDAY", 2);
THURSDAY = new Day("THURSDAY", 3);
FRIDAY = new Day("FRIDAY", 4);
SATURDAY = new Day("SATURDAY", 5);
SUNDAY = new Day("SUNDAY", 6);
$VALUES = (new Day[] {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
});
}
}
首先我們從第一行
- 可以知道enum定義的枚舉本身也是一個class,和我們常見的類使用上沒有差別
- 這個類默認繼承了Enum接口,由於Java的單繼承,所以enum不能再繼承別的類,同時由於繼承,也就有了Enum接口中的已經實現的方法。
- 這個類被final修飾,也就是太監類,沒有子類,不允許繼承
接下來我們看成員變量和static代碼塊這塊:
- static靜態代碼塊的特性大家應該都知道哈,這里會隨着類的加載使用而執行,且僅執行一次,為我們創建了Day這個對象的實例,共計7個,每一個枚舉值都是一個對象的實例,這點要時刻記住,后面會用到
- 通過私有構造器,並調用父構造器初始化name和ordinal屬性,name值默認就是我們定義的枚舉名稱的字符串形式,如"Monday"
- VALUS常量是一個Day類型數組,也是在靜態代碼塊中進行的初始化,數組中的值為對應的枚舉類對象
到此我們也就明白了,使用關鍵字enum定義的枚舉類型,在編譯期后,也將轉換成為一個實實在在的類,而在該類中,會存在每個在枚舉類型中定義好變量的對應實例對象,如上述的MONDAY枚舉類型對應public static final Day MONDAY;,同時編譯器會為該類創建兩個方法,分別是values()和valueOf()。ok~,到此相信我們對枚舉的實現原理也比較清晰啦。接下來我們來看下枚舉的高級用法:
4.枚舉的高級用法
向enum類添加方法與自定義屬性和構造函數 重新定義一個日期枚舉類,帶有desc成員變量描述該日期的對於中文描述,同時定義一個getDesc方法,返回中文描述內容,自定義私有構造函數,在聲明枚舉實例時傳入對應的中文描述,代碼如下:
public enum DayDesc {
MONDAY("星期一", 1),
TUESDAY("星期二", 2),
WEDNESDAY("星期三", 3),
THURSDAY("星期四", 4),
FRIDAY("星期五", 5),
SATURDAY("星期六", 6),
SUNDAY("星期日", 7);
private String desc;//文字描述
private Integer code; //對應的代碼
/**
* 私有構造,防止被外部調用,默認就是私有構造,且只允許私有構造
*
* @param desc
*/
private DayDesc(String desc, Integer code) {
this.desc = desc;
this.code = code;
}
/**
* 定義方法,返回描述,跟常規類的定義沒區別
*
* @return
*/
public String getDesc() {
return desc;
}
/**
* 定義方法,返回代碼,跟常規類的定義沒區別
*
* @return
*/
public int getCode() {
return code;
}
public static void main(String[] args) {
for (DayDesc day : DayDesc.values()) {
System.out.println("name:" + day.name() +
",desc:" + day.getDesc());
}
}
}
輸出結果:
枚舉類中定義抽象方法,由於每個枚舉值都是一個對象實例,在枚舉類中定義的抽象方法,會由每個對應的枚舉對象去實現,在第一個枚舉值后面打上{},報紅后alt+enter即可實現方法。先上代碼,給大家看看
public enum Human {
MEN{
@Override
public void say() {
System.out.println("男:我可以約你出來看月亮么");
}
},WOMEN {
@Override
public void say() {
System.out.println("女:好噠,月亮不睡我們不睡");
}
};
public abstract void say();
public static void main(String[] args) {
Human.MEN.say();
Human.WOMEN.say();
}
}
探索:可能大家還是有疑問哈,這個就簡單的使用Idea內嵌的反編譯工具叫啥flower給大家看下,大概就知道原理了:
首先執行javac Human.java 對該類進行編譯,我們會發現得到了三個class文件:
直接用Idea內置的反編譯,默認已經嵌入,大家什么都不用干,直接打開.class文件即可。代碼如下:
package com.huawei.subtitle.portal;
public enum Human {
MEN {
public void say() {
System.out.println("男:我可以約你出來看月亮么");
}
},
WOMEN {
public void say() {
System.out.println("女:好噠,月亮不睡我們不睡");
}
};
private Human() {
}
public abstract void say();
public static void main(String[] var0) {
MEN.say();
WOMEN.say();
}
}
---
enum Human$1 {
Human$1() {
}
public void say() {
System.out.println("男:我可以約你出來看月亮么");
}
}
---
enum Human$2 {
Human$2() {
}
public void say() {
System.out.println("女:好噠,月亮不睡我們不睡");
}
}
首先我們看到由於我們定義了抽象方法say(),所以Human這個類是抽象的,要不違背了Java基礎有抽象方法的類肯定是抽象類,然后兩個枚舉對象,分別實現了Human這個類並重寫了say()方法,看到這里大家應該懂了吧。好吧,今天就寫到這里吧,我有點累了,准備去公司樓下健身房鍛煉下。
有什么問題留言即可,基本每天都會登錄我的博客。誰讓我笨但是勤勞嘞嘻嘻