JDK閱讀之Enum
JDK學習之Enum
enum的使用
在沒有enum之前如果想要定義一些常量,就會採用如下的方式
假設要定義四個常量表示不同的季節
public class SeasonWithoutEnum { public static final int spring=1; public static final int summer=2; public static final int autumn=3; public static final int winter=4; public static void getSession(int a){ switch(a){ case SeasonWithoutEnum.spring: System.out.println("春天!"); break; case SeasonWithoutEnum.summer: System.out.println("夏天!"); break; case SeasonWithoutEnum.autumn: System.out.println("秋天!"); break; case SeasonWithoutEnum.winter: System.out.println("winter is comming"); break; default: System.out.println("查無此季"); break; } } public static void main(String[] args) { SeasonWithoutEnum.getSession(SeasonWithoutEnum.spring); SeasonWithoutEnum.getSession(5); } }
缺點:
-
對於第一個呼叫,似乎沒有什麼問題也是常見的呼叫,但是第二個呼叫就存在這型別不安全的問題由於沒有限制引數a的範圍,導致隨便傳入一個數字都可以,如果還需要考慮這個數字的限制,那麼程式碼的邏輯就會變得複雜
-
可讀性差,對於上面的示例來說,使用了數字來表示季節,而我們通常使用String來進行季節的表示,如果我們使用String,雖然jdk現在提供了String的switch的支援,但是使用了String的hashcode做比較,還需要處理hash衝突,自然是比較麻煩的
對於上面的這種需求場景採用enum來改進
public enum Season { SPRING(1,"春天!"),SUMMER(2,"夏天!"),AUTUMN(3,"秋天!"),WINTTER(4,"冬天!"); private int num; private String sName; Season(int num,String sName){ this.num=num; this.sName=sName; } public String getsName(){ return this.sName; } public static void getSession(Season season){ switch(season){ case SPRING: System.out.println(season.getsName()); break; case SUMMER: System.out.println(season.getsName()); break; case AUTUMN: System.out.println(season.getsName()); break; case WINTTER: System.out.println(season.getsName()); break; } } public static void main(String[] args) { Season.getSession(Season.SPRING); Season.getSession(Season.WINTTER); } }
此時通過season進行列舉,避免了型別安全問題,只能傳入已有的列舉例項,此外,又提高了表意性
enum是如何實現的
對於以下的程式碼:
public enum Season{
SPRING,SUMMER;
}
就這麼簡簡單單的幾行程式碼怎麼就定義了列舉呢?為什麼又說列舉類呢?
通過jad反編譯Season.class
得到如下程式碼
jad反編譯工具的下載連結如下:https://varaneckas.com/jad/
package com.hustdj.jdkStudy; //實際上它就是繼承自Enum的一個final類,也就是我們宣告的enum就是一個繼承自Enum的final類 public final class Season extends Enum { private Season(String s, int i) { super(s, i); } //values方法,通過arraycoapy的方式返回所有列舉例項 public static Season[] values() { Season aseason[]; int i; Season aseason1[]; System.arraycopy(aseason = ENUM$VALUES, 0, aseason1 = new Season[i = aseason.length], 0, i); return aseason1; } public static Season valueOf(String s) { return (Season)Enum.valueOf(com/hustdj/jdkStudy/Season, s); } public static final Season SPRING; public static final Season SUMMER; private static final Season ENUM$VALUES[]; //靜態程式碼塊 static { SPRING = new Season("SPRING", 0); SUMMER = new Season("SUMMER", 1); ENUM$VALUES = (new Season[] { SPRING, SUMMER }); } }
結論
- enum關鍵字的背後,是編譯器為我們做了事情,它其實是一個繼承了
Enum
的final類,自然它就不能再被繼承 - 我們所宣告的列舉例項,實際上也是由靜態程式碼塊為我們建立的
- 有一個隱藏的方法
values()
,它提供所有的列舉例項物件的拷貝,有意思的是這個方法你是追蹤不到的,也就是說你用ctrl
是追蹤 不進去的,原因就是它是在編譯期生成的方法
Enum
既然知道了enum是通過繼承了Enum的final類,那麼就來看看Enum這個超類吧
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
private final String name;
public final String name() {
return name;
}
//優先順序,這個預設從0開始累加,從反編譯中可以看出來
private final int ordinal;
public final int ordinal() {
return ordinal;
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
public String toString() {
return name;
}
//對於enum來說==和equals的作用相同,因為equals使用的就是==
public final boolean equals(Object other) {
return this==other;
}
public final int hashCode() {
return super.hashCode();
}
//不允許使用clone方法
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
//compareTo比較的是ordinal
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;
}
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
protected final void finalize() { }
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
}
有以下幾個點需要注意:
- enum的==與equals方法等效
- enum的排序使用的ordinal成員變數的大小排序
- enum禁止使用clone方法,會直接丟擲異常
Switch對於enum的支援
對於上面enum使用的中的例子,反編譯得到的程式碼如下:
public static void getSession(Season season)
{
switch($SWITCH_TABLE$com$hustdj$jdkStudy$Season()[season.ordinal()])
{
case 1: // '\001'
System.out.println("\u6625\u5929\uFF01");
break;
case 2: // '\002'
System.out.println("\u590F\u5929\uFF01");
break;
case 3: // '\003'
System.out.println("\u79CB\u5929\uFF01");
break;
case 4: // '\004'
System.out.println("\u51AC\u5929\uFF01");
break;
}
}
static int[] $SWITCH_TABLE$com$hustdj$jdkStudy$Season()
{
$SWITCH_TABLE$com$hustdj$jdkStudy$Season;
if($SWITCH_TABLE$com$hustdj$jdkStudy$Season == null) goto _L2; else goto _L1
_L1:
return;
_L2:
JVM INSTR pop ;
int ai[] = new int[values().length];
try
{
ai[AUTUMN.ordinal()] = 3;
}
catch(NoSuchFieldError _ex) { }
try
{
ai[SPRING.ordinal()] = 1;
}
catch(NoSuchFieldError _ex) { }
try
{
ai[SUMMER.ordinal()] = 2;
}
catch(NoSuchFieldError _ex) { }
try
{
ai[WINTTER.ordinal()] = 4;
}
catch(NoSuchFieldError _ex) { }
return $SWITCH_TABLE$com$hustdj$jdkStudy$Season = ai;
}
通過檢視反編譯的程式碼可以看到,$SWITCH_TABLE$com$hustdj$jdkStudy$Season
方法把enum的ordinal
和自定義的num
關聯起來返回陣列,在switch時通過enum的ordinal獲取到對應的num,然後再switch,比較的最終還是自定義的num
常用列舉TimeUnit
public enum TimeUnit {
NANOSECONDS {
public long toNanos(long d) { return d; }
public long toMicros(long d) { return d/(C1/C0); }
public long toMillis(long d) { return d/(C2/C0); }
public long toSeconds(long d) { return d/(C3/C0); }
public long toMinutes(long d) { return d/(C4/C0); }
public long toHours(long d) { return d/(C5/C0); }
public long toDays(long d) { return d/(C6/C0); }
public long convert(long d, TimeUnit u) { return u.toNanos(d); }
int excessNanos(long d, long m) { return (int)(d - (m*C2)); }
},
/*還有很多就不一一列舉了*/
}
非常的amazing啊,在例項NANOSECONDS中它重寫了TimeUnit中定義的方法,這個可以通過反編譯檢視,在老朋友season中試了一下,效果如下
//原始碼
SPRING(1,"春天!"){
public String getsName(){
return "春天!過載";
}
},
//反編譯
static
{
SPRING = new Season("SPRING", 0, 1, "\u6625\u5929\uFF01") {
public String getsName()
{
return "\u6625\u5929\uFF01\u91CD\u8F7D";
}
}
;
SUMMER = new Season("SUMMER", 1, 2, "\u590F\u5929\uFF01");
AUTUMN = new Season("AUTUMN", 2, 3, "\u79CB\u5929\uFF01");
WINTTER = new Season("WINTTER", 3, 4, "\u51AC\u5929\uFF01");
ENUM$VALUES = (new Season[] {
SPRING, SUMMER, AUTUMN, WINTTER
});
}
列舉與單例
【回頭再好好補充】