Java 1.5新特性Enum的學習和使用

不少時候咱們定義了一組值來表示用於特定的數值,每每都是習慣性地使用常量:
private static final int COLOR_WHITE = Color.WHITE;
private static final int COLOR_BLACK = Color.BLACK;
後來才知道原來這樣會使得類型不安全,你必須確保是int,並且還要確保它的範圍必須正確。
private void updateBackgourndColor( int color){
         if (color == COLOR_WHITE || color == COLOR_BLACK)
                frameView.setBackgroundColor(color);
        }
}
像以上的代碼,一旦color定義的常量之後增長了,這裏就須要相應地修改。而enum能夠很方便地解決。

首先看看最簡單的enum用法:
public class EnumMonth{
         public static void main(String[] args) {
                 for (Month month : Month.values()) {
                        System.out.println( month);
                }
        }

        private enum Month {
                JAN, SEP, MAR, APR, MAY;
        }

}
運行結果:
JAN
SEP
MAR
APR
MAY

使用javap EnumMonth$Month看到反編譯的結果,Month會被編譯成一個Java類:
Compiled from "EnumMonth.java"
final class EnumMonth$Month extends java.lang.Enum{
         public static final EnumMonth$Month JAN;
         public static final EnumMonth$Month SEP;
         public static final EnumMonth$Month MAR;
         public static final EnumMonth$Month APR;
         public static final EnumMonth$Month MAY;
         public static EnumMonth$Month[] values();
         public static EnumMonth$Month valueOf(java.lang.String);
         static {};
}
  1. 1. 這個Month枚舉類是final class,即不能被繼承,而它自己是繼承於Enum;
  2. 2. 這些枚舉值是Month對象,並且是static final修飾的;
  3. 3. 注意values()方法,它能獲得全部的枚舉值,但這方法在Enum裏面沒有找到,我是在看到反編譯結果後才確認的。
而valueOf(String)方法, 返回帶指定名稱的指定枚舉類型的枚舉常量。在使用javap -c EnumMonth$Month進一步查看彙編代碼後,知道原來是調用Enum.valueOf(Class<T> enumType, String name):
public static EnumMonth$Month valueOf(java.lang.String);
    Code:
     0:  ldc_w  #4; //class EnumMonth$Month
     3:  aload_0
     4:  invokestatic  #5; //Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
     7:  checkcast  #4; //class EnumMonth$Month
     10:  areturn

static{}裏面主要是new這些枚舉值對象。
0:   new  #4; //class EnumMonth$Month
3:  dup
4:  ldc  #7; //String JAN
6:  iconst_0
7:  invokespecial  #8; //Method "<init>":(Ljava/lang/String;I)V
10:  putstatic  #9; //Field JAN:LEnumMonth$Month;

這裏要說說Enum的構造函數Enum(String name, int ordinal),形參name就是枚舉值的string形式("JAN"),ordinal值則是編譯器按照枚舉值的順序從0排着賦值的。 要注意,這個ordinal值原來是不能改變的,即編譯器是從0升序賦值的,在字節碼裏面固定寫着iconst_0(n=0,1,2...)的。
static{
        JAN = new EnumMonth$Month( "JAN", 0);
        SEP = new EnumMonth$Month( "SEP", 1);
        ...
}
因而Enum裏面的String name()和int ordinal()方法就是返回這兩個值的。Enum.toString()也是返回name值,這就是爲何System.out.println(month);會輸出那些值。

看看下面的例子:
public class EnumMonth{
         public static void main(String[] args) {
                 for (Month month : Month.values()) {
                        System.out.println(month.ordinal());
                }
        }

         private enum Month {
                JAN(1), SEP(9), MAR(3), APR(4), MAY(5);
                
                 final int id;
                
                Month( int id){
                         this.id = id;
                }
        }
}
一開始沒有加上id成員變量和構造函數的,編譯時提示找不到構造函數 Month(int),我當初還覺得JAN(1)的1會賦值給形參ordinal呢!修正後,運行結果是
0
1
2
3
4
說明ordinal值是固定不變的。

查看彙編代碼:
0:     new         #4; //class EnumMonth$Month
3:     dup
4:     ldc         #8; //String JAN
6:     iconst_0
7:     iconst_1
8:     invokespecial     #9; //Method "<init>":(Ljava/lang/String;II)V
11:    putstatic             #10; //Field JAN:LEnumMonth$Month;
這時發現構造函數彷佛增長一個形參int類型id。
我再嘗試把int id改成char,運行結果是同樣的:
private enum Month {
        JAN('1'), SEP('9'), MAR('3'), APR('4'), MAY('5');

         final char id;

        Month( char id){
                 this.id = id;
        }
}
但編譯代碼有所不一樣:
0:     new         #4; //class EnumMonth$Month
3:     dup
4:     ldc         #8; //String JAN
6:     iconst_0
7:     bipush    49
9:     invokespecial     #9; //Method "<init>":(Ljava/lang/String;IC)V
12:    putstatic             #10; //Field JAN:LEnumMonth$Month;
因此我的認爲,若是咱們添加了自定義的構造函數,編譯器會自動構建新的構造函數:
Enum(String name, int ordinal, char id) {
         super(name, ordinal);
        
         //copy 自定義構造函數的內容
        this.id = id;
}
注意:構造器只能私有private,絕對不容許有public構造器。不然提示modifier public not allowed here。

如今瞭解了枚舉值就是該枚舉類的對象,能夠傳常量數值做爲枚舉對象的屬性以及自定義構造函數,接下來看看咱們平時經常使用的switch例子:
switch(color) {
case RED:
        System.out.println( "紅色");
         break;
case GREEN:
        System.out.println( "綠色");
         break;
case BLACK:
        System.out.println( "黑色");
         break;
...
default:
         break;
}
實際上用如下代碼能夠解決,每一個枚舉對象都包含desc屬性值和getDesc()方法:
public class EnumColor {
         public static void main(String[] args) {
                 for (Color color : Color.values())
                        System.out.println(color + " is : " + color.getDesc());
        }

         private enum Color {
                RED( "紅色"), GREEN( "綠色"), BLUE( "藍色"), YELLOW( "×××"), BLACK( "黑色"), WHITE( "白色");

                 private final String desc;

                 private Color(String desc) {
                         this.desc = desc;
                }

                 public String getDesc() {
                         return desc;
                }
        }
}
運行結果:
RED is : 紅色
GREEN is : 綠色
BLUE is : 藍色
YELLOW is : ×××
BLACK is : 黑色
WHITE is : 白色

以上的例子提醒咱們,不須要在外面的代碼添加switch邏輯來判斷以賦予不一樣的值,直接在enum裏面處理就完成了。

注意:在case標籤中,枚舉前綴不能出現,即case Color.RED是不合法的,只能直接用枚舉值RED。而在其餘地方出現時則必須用Color.RED。

爲何 switch能夠支持enum呢?switch實際上是支持int基本類型,而由於byte,short,char能夠向上轉換爲int,因此switch也支持它們,但long由於轉換int會截斷便不能支持。
    而enum在switch中也是int類型,看了《 switch之enum》,應該這樣解釋:
    我使用jd反編譯上面那段switch代碼:
??? = EnumColor.Color.RED;
switch (EnumColor.1.$SwitchMap$EnumColor$Color[???.ordinal()]) {
case 1:
    System.out.println( "紅色");
     break;
case 2:
    System.out.println( "綠色");
     break;
case 3:
    System.out.println( "黑色");
     break;
}
能夠看到case後面的值變成int值,但這個數值並非枚舉值的ordinal。jd沒有把EnumColor$1裏面的代碼反編譯出來,但看了文章裏面那段 static int[] $SWITCH_TABLE$meiju$EnumTest(),原來這裏面有一個int數組,按照枚舉值的ordinal值做爲索引值,依次給數組元素賦值從1開始:
ai[Color.RED.ordinal()] = 1;
ai[Color.GREEN.ordinal()] = 2;
...
再看看switch括號裏面的就是數組[ ???.ordinal()],明瞭!

下面說一下 方法枚舉(多態),我一開始寫的是如下代碼:
public class EnumGrade {
         public static void main(String[] args) {
                 for (Grade grade : Grade.values())
                        System.out.println(grade + "'s result is : " + grade.getResult());
        }

         private enum Grade {
                A(1) {
                         public int getResult() {
                                 return base;
                        }
                },

                B(3) {
                         public int getResult() {
                                 return base*2;
                        }
                },

                C(5) {
                         public int getResult() {
                                 return base*3;
                        }
                };

                 private final int base;

                 private Grade( int base) {
                         this.base = base;
                }

                 public abstract int getResult();
        }
}
結果編譯不經過:
EnumGrade.java:10: 沒法從靜態上下文中引用非靜態 變量 base
                return base;
                       ^
EnumGrade.java:16: 沒法從靜態上下文中引用非靜態 變量 base
                return base*2;
                       ^
EnumGrade.java:22: 沒法從靜態上下文中引用非靜態 變量 base
                return base*3;
                       ^
因而修改成如下代碼:
public class EnumGrade {
         public static void main(String[] args) {
                 for (Grade grade : Grade.values())
                        System.out.println(grade + "'s result is : " + grade.getResult(grade.ordinal()));
        }

         private enum Grade {
                A {
                         public int getResult( int base) {
                                 return base;
                        }
                },

                B {
                         public int getResult( int base) {
                                 return base*2;
                        }
                },

                C {
                         public int getResult( int base) {
                                 return base*3;
                        }
                };

                 public abstract int getResult( int base);
        }
}
運行結果:
A's result is : 0
B's result is : 2
C's result is : 6

另外Java1.5還有 EnumMap和EnumSet(參考 Java枚舉類型enum使用詳解):
import java.util.EnumMap;
import java.util.EnumSet;

public class EnumState{
         public static void main(String[] args) {
                 // EnumSet的使用
                EnumSet<State> stateSet = EnumSet.allOf(State. class);
                 for (State s : stateSet) {
                        System.out.println(s);
                }
                 // EnumMap的使用
                EnumMap<State,String> stateMap = new EnumMap<State,String>(State. class);
                stateMap.put(State.ON, "is On");
                stateMap.put(State.OFF, "is off");
                 for (State s : State.values()) {
                        System.out.println(s.name() + ":" + stateMap.get(s));
                }
        }

         private enum State {
                ON, OFF
        };
}
運行結果:
ON
OFF
ON:is On
OFF:is off
*****由於本人使用javap和jd也得不到更好的反編譯代碼,某些反編譯結果的分析是結合網上的文章和本身看到的反編譯結果推理所得。
相關文章
相關標籤/搜索