一.整數枚舉替代法
比如說我們要定義一個春夏秋冬四季的枚舉類型,如果使用整數來模擬,其樣子大概爲:
public class Season { public static final int SPRING = 0; public static final int SUMMER = 1; public static final int FALL = 2; public static final int WINTER = 3; } |
對於上面這段程序大家可能不會陌生,因爲你可能在你的程序中已經多次使用了這樣的整數類型枚舉。儘管這是非常普遍的一種枚舉替代品,但這並不說明它是一種好的替代品。這種簡單的方法有很多嚴重的問題。
問題1:類型安全問題
首先,使用整數我們無法保證類型安全問題。比如我我們設計一個函數,我們的意圖是讓調用者傳入春夏秋冬之中的某一個值,但是,使用 “整數枚舉”我們無法保證用戶不傳入其它意想不到的值。如下所示:
…… public void seasonTest(int season) { //season 應該是 Season.SPRING,Season.SUMMER, Season.FALL, Season.WINTER //但是我們無法保證這一點 …… } public void foo() { seasonTest(Season.SUMMER); //理想中的使用方法 seasonTest(5); //錯誤調用 } …… |
程序seasonTest(Season.SUMMER)是我們期望的使用方式,而seasonTest(5)是一個明顯的錯誤,但是在編譯的時候,編譯器會認爲這是合法的函數調用而給於通過。這顯然是不符合Java類型安全的宗旨的。
問題2:字符串的表達問題
使用枚舉的大多數場合,我們需要很方便的得到枚舉類型的字符表達形式,比如Spring, Summer,Fall,Winter,甚至是漢語的春,夏,秋,冬。但這種整數類型的枚舉和字符沒有任何聯繫,我們要使用一些其他輔助函數來達到這樣的效果,顯得不夠方便,也就是外國人講的不是一個“Generic solution”。
…… public String getSeasonString(int season) { If(season == Season.SPRING) return “Spring; else If(season == Season.SUMMRT) return “Summer; …… } |
二.類型安全類替代法
比較好的Enum替代品是一種被叫做類型安全的枚舉方法。雖然不同的人的具體實現可能會有些不同,但它們的核心思想是一致的。讓我們先看一個簡單的例子。
public class Season { private Season(String s) { m_name = s; } public String toString() { return m_name; } private final String m_name; public static final Season SPRING = new Season("Spring"); public static final Season SUMMER = new Season("Summer"); public static final Season FALL = new Season("Fall"); public static final Season WINTER = new Season("Winter"); } |
它的特點是:
1. 定義一個類,用這個類的實例來表達枚舉值
2. 不提供公開構造函數以杜絕客戶自己生成該類的實例
3. 所有的類的實例都是final的,不允許有任何改動
4. 所有的類的實例都是public static的,這樣客戶可以直接使用它
5. 所有的枚舉值都是唯一的,所以程序中可以使用==運算符,而不必使用費時的equals()方法
以上這些特點保證了類型安全。如果有這樣的調用程序
public class ClientProgam { …. public String myFunction(Season season) { …… } } |
那麼我們可以放心的是,myFunction方法傳入的參數一定是Season類型,絕對不可能是其他類型。而具體的值只能是我們給出的春夏秋冬的某一個(唯一的例外就是傳入一個null。那是一個其它性質的問題,是所有Java程序共有的,不是我們今天討論的話題)。這不就是使用枚舉的最根本初衷嗎!
它的缺點是:
1. 不夠直觀,不夠簡潔
2. 有些情況下不如整數方便,比如不能使用switch語句
3. 內存開銷比整數型的要大,雖然對於大部分Java程序這不是一個問題,但對於Java移動設備卻可能會是一個潛在的問題
比較完整的實現
上面的源程序是一個最基本的框架。在現實的程序開發中,我們會給它增加一些東西,使它更完善,更便於使用。最常見的是增加一個整數變量,來表示枚舉值的先後順序或是大小級別,英文裏叫做Ordinal。這樣我們就可以在各個枚舉值之間可以進行比較了。另外我們可能會需要得到這個枚舉的所有值來進行遍歷或是循環等操作。有時候我們可能還希望給出一個字符串(比如Summer)而得到相應的枚舉類。如果將這些常見的要求加到我們的具體實現中,那麼我們上面的那個程序將會擴展爲:
public class Season implements Comparable { private Season(String s) { m_ordinal = m_nextOrdinal++; m_name = s; } public String toString() { return m_name; } public String Name() { return m_name; } public int compareTo(Object obj) { return m_ordinal - ((Season)obj).m_ordinal; } public static final Season[] values() { return m_seasons; } public static Season valueOf(String s) { for(int i = 0; i < m_seasons.length; i++) if(m_seasons[i].Name().equals(s)) return m_seasons[i]; throw new IllegalArgumentException(s); } private final String m_name; private static int m_nextOrdinal = 0; private final int m_ordinal; public static final Season SPRING; public static final Season SUMMER; public static final Season FALL; public static final Season WINTER; private static final Season m_seasons[]; static { SPRING = new Season("Spring"); SUMMER = new Season("Summer"); FALL = new Season("Fall"); WINTER = new Season("Winter"); m_seasons = (new Season[] { SPRING, SUMMER, FALL, WINTER }); } } |
上面給出的這個例子雖然比較好的解決了我們對枚舉類型的基本要求,但它顯然不夠簡潔。如果我們要寫很多這樣的枚舉類,那將會是一個不小的任務。並且重複的寫類似的程序是非常枯燥和容易犯錯誤的。爲了將程序員從這些繁瑣的工作中解放出來,人們開發了一些工具軟件來完成這些重複的工作。比如比較流行的JEnum,請參看《再談在Java中使用枚舉》(http://tech.ccidnet.com/pub/article/c1078_a95621_p1.html )
這些工具軟件其實是一些“程序生成器”。你按照它的語法規則定義你的枚舉(語法相對簡單直觀),然後運行這些工具軟件,它會將你的定義轉化爲一個完整的Java類,就像我們上面所寫的那個程序一樣。看到這裏,讀者也許會想:“爲什麼不能將這種工具軟件的功能放到Java編譯器中呢?那樣我們不是就可以簡單方便的定義枚舉了嗎?而具體的Java類程序由編譯器來生成,我們不必再手工完成那麼冗長的程序行,也不必使用什麼第三方工具來生成這樣的程序行了嗎?”如果你有這樣的想法,那麼就要恭喜你了。因爲你和Java的設計開發人員想到一塊兒去了。好,現在就讓我們來看看Java 1.5中新增的枚舉的功能。
Java 1.5的枚舉類型
在新的Java 1.5中,如果定義我們上面提到的春夏秋冬四季的枚舉,那麼語句非常簡單,如下所示:
public enum Season { SPRING, SUMMER, FALL, WINTER } |
怎麼樣,夠簡單了吧。在我們全面展開討論之前,讓我們先從這個小例子中我們看一下在Java 1.5中定義一個枚舉類型的基本要求。
1. 使用關鍵字enum
2. 類型名稱,比如這裏的Season
3. 一串允許的值,比如上面定義的春夏秋冬四季
4. 枚舉可以單獨定義在一個文件中,也可以嵌在其它Java類中
除了這樣的基本要求外,用戶還有一些其他選擇
1.枚舉可以實現一個或多個接口(Interface)
2.可以定義新的變量
3.可以定義新的方法
4.可以定義根據具體枚舉值而相異的類
這些選項的具體使用和特點我們會在後面的例子中逐步提到。那麼這樣的小程序和我們前面提到的“類型安全枚舉替代”方案有什麼內在聯繫呢?從表面上看,好像大相徑庭,但如果我們深入一步就會發現這兩者的本質幾乎是一模一樣的。怎麼樣,有些吃驚嗎?把上面的枚舉編譯後,我們得到了Season.class。我們把這個Season.class反編譯後就會發現Java 1.5枚舉的真實面目,其反編譯後的源程序爲:
public final class Season extends Enum { public static final Season[] values() { return (Season[])$VALUES.clone(); } public static Season valueOf(String s) { Season aseason[] = $VALUES; int i = aseason.length; for(int j = 0; j < i; j++) { Season season = aseason[j]; if(season.name().equals(s)) return season; } throw new IllegalArgumentException(s); } private Season(String s, int i) { super(s, i); } public static final Season SPRING; public static final Season SUMMER; public static final Season FALL; public static final Season WINTER; private static final Season $VALUES[]; static { SPRING = new Season("SPRING", 0); SUMMER = new Season("SUMMER", 1); FALL = new Season("FALL", 2); WINTER = new Season("WINTER", 3); $VALUES = (new Season[] { SPRING, SUMMER, FALL, WINTER }); } } |
對比一下這個例子和我們前面給出的“類型安全枚舉”,你會發現他們幾乎是同出一轍。比較顯著的一個區別是Java 1.5的所有枚舉都是Enum類型的衍生子類。但如果你看看Enum類的源程序,你就會發現它只不過是提供了一些基本服務的基類,就本質而言,Java 1.5的枚舉和我們所說的“類型安全枚舉”是一致的。
/* * @(#)Enum.java 1.12 04/06/08 * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package java.lang; import java.io.Serializable; /** * This is the common base class of all Java language enumeration types. * * @author Josh Bloch * @author Neal Gafter * @version 1.12, 06/08/04 * @since 1.5 */ 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; } public String toString() { return name; } public final boolean equals(Object other) { return this==other; } public final int hashCode() { return System.identityHashCode(this); } protected final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } public final int compareTo(E o) { Enum other = (Enum)o; Enum self = this; if (self.getClass() != other.getClass() && // optimization self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); return self.ordinal - other.ordinal; } public final Class<E> getDeclaringClass() { Class clazz = getClass(); Class zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? clazz : 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 const " + enumType +"." + name); } } |
帶有參數的構造函數
現在讓我們來看一個比較複雜一點的例子,來進一步闡述Java 1.5枚舉的本質。 下面這段程序是Sun公司提供的枚舉示範程序,是用太陽系中九大行星來說明枚舉的一種能力-- 即我們可以創建新的構造函數,而不是僅僅侷限於Enum的缺省的那一個。我們還可以自己定義新的方法,常數等等。
public enum Planet { MERCURY (3.303e+23, 2.4397e6), VENUS (4.869e+24, 6.0518e6), EARTH (5.976e+24, 6.37814e6), MARS (6.421e+23, 3.3972e6), JUPITER (1.9e+27, 7.1492e7), SATURN (5.688e+26, 6.0268e7), URANUS (8.686e+25, 2.5559e7), NEPTUNE (1.024e+26, 2.4746e7), PLUTO (1.27e+22, 1.137e6); private final double mass; // in kilograms private final double radius; // in meters Planet(double mass, double radius) { this.mass = mass; this.radius = radius; } private double mass() { return mass; } private double radius() { return radius; } // universal gravitational constant (m3 kg-1 s-2) public static final double G = 6.67300E-11; double surfaceGravity() { return G * mass / (radius * radius); } double surfaceWeight(double otherMass) { return otherMass * surfaceGravity(); } } |
將這段程序編譯後然後再反編譯,我們得到了這樣的程序,也就是Java虛擬機實際使用的源程序。
public final class Planet extends Enum { public static final Planet[] values() { return (Planet[])$VALUES.clone(); } public static Planet valueOf(String s) { Planet aplanet[] = $VALUES; int i = aplanet.length; for(int j = 0; j < i; j++) { Planet planet = aplanet[j]; if(planet.name().equals(s)) return planet; } throw new IllegalArgumentException(s); } private Planet(String s, int i, double d, double d1) { super(s, i); mass = d; radius = d1; } private double mass() { return mass; } private double radius() { return radius; } double surfaceGravity() { return (6.6729999999999999E-011D * mass) / (radius * radius); } double surfaceWeight(double d) { return d * surfaceGravity(); } public static final Planet MERCURY; public static final Planet VENUS; public static final Planet EARTH; public static final Planet MARS; public static final Planet JUPITER; public static final Planet SATURN; public static final Planet URANUS; public static final Planet NEPTUNE; public static final Planet PLUTO; private final double mass; private final double radius; public static final double G = 6.6729999999999999E-011D; private static final Planet $VALUES[]; static { MERCURY = new Planet("MERCURY", 0, 3.3030000000000001E+023D, 2439700D); VENUS = new Planet("VENUS", 1, 4.8690000000000001E+024D, 6051800D); EARTH = new Planet("EARTH", 2, 5.9760000000000004E+024D, 6378140D); MARS = new Planet("MARS", 3, 6.4209999999999999E+023D, 3397200D); JUPITER = new Planet("JUPITER", 4, 1.9000000000000001E+027D, 71492000D); SATURN = new Planet("SATURN", 5, 5.6879999999999998E+026D, 60268000D); URANUS = new Planet("URANUS", 6, 8.686E+025D, 25559000D); NEPTUNE = new Planet("NEPTUNE", 7, 1.0239999999999999E+026D, 24746000D); PLUTO = new Planet("PLUTO", 8, 1.2700000000000001E+022D, 1137000D); $VALUES = (new Planet[] { MERCURY, VENUS, EARTH, MARS, JUPITER, SATURN, URANUS, NEPTUNE, PLUTO }); } } |
從反編譯的程序來看,這個Java 1.5的枚舉的其實沒有任何神祕之處。它只不過是稍微改變了一下構造函數,增加了幾個變量和函數。我們前面提到的“類型安全枚舉”一樣可以完成同樣的工作。
依附於具體變量之上的方法
下面這段小程序也是一個比較有代表性的例子。即在列出的加,減,乘,除四個枚舉值中,每一個值都有其相應的類定義段(就是所謂的Value-Specific Class Bodies)。這樣,加減乘除四個枚舉值就有了各自版本的eval函數實現方法。使用Java 1.5的Enum服務,其源程序爲:
public enum Operation { PLUS { double eval(double x, double y) { return x + y; } }, MINUS { double eval(double x, double y) { return x - y; } }, TIMES { double eval(double x, double y) { return x * y; } }, DIVIDE { double eval(double x, double y) { return x / y; } }; abstract double eval(double x, double y); } |
這段程序看起來比較複雜,如果我們反編譯一下Java編譯器生成的Operation.class文件,我們就發現其原理並不複雜。
public abstract class Operation extends Enum { public static final Operation[] values() { return (Operation[])$VALUES.clone(); } public static Operation valueOf(String s) { Operation aoperation[] = $VALUES; int i = aoperation.length; for(int j = 0; j < i; j++) { Operation operation = aoperation[j]; if(operation.name().equals(s)) return operation; } throw new IllegalArgumentException(s); } private Operation(String s, int i) { super(s, i); } abstract double eval(double d, double d1); public static final Operation PLUS; public static final Operation MINUS; public static final Operation TIMES; public static final Operation DIVIDE; private static final Operation $VALUES[]; static { PLUS = new Operation("PLUS", 0) { double eval(double d, double d1) { return d + d1; } }; MINUS = new Operation("MINUS", 1) { double eval(double d, double d1) { return d - d1; } }; TIMES = new Operation("TIMES", 2) { double eval(double d, double d1) { return d * d1; } }; DIVIDE = new Operation("DIVIDE", 3) { double eval(double d, double d1) { return d / d1; } }; $VALUES = (new Operation[] { PLUS, MINUS, TIMES, DIVIDE }); } } |
那麼在Java 1.5以前我們是怎樣解決這樣的問題的呢?如果使用面向對象的原則來解決這一問題,其源程序爲:
public abstract class Operation { private final String m_name; Operation(String name) {m_name = name;} public static final Operation PLUS = new Operation("Plus"){ protected double eval(double x, double y){ return x + y; } } public static final Operation MINUS = new Operation("Minus"){ protected double eval(double x, double y){ return x - y; } } public static final Operation TIMES = new Operation("Times"){ protected double eval(double x, double y){ return x * y; } } public static final Operation DEVIDE = new Operation("Devide"){ protected double eval(double x, double y){ return x / y; } } abstract double eval (double x, double y); public String toString() {return m_name; } private static int m_nextOridnal = 0; private final int m_ordinal = m_nextOridnal++; private static final Operation[] VALUES = { PLUS, MINUS, TIMES, DEVIDE }; } |
這種方法的本質和Java 1.5中的Enum是一致的。就是定義一個抽象函數(abstract function),然後每一個Enum值提供一個具體的實現方法。在Java 1.5以前,有的人可能會用一種看起來似乎簡單的方法來完成類似的任務。比如:
public class Operation { private final String m_name; Operation(String name) {m_name = name;} public static final Operation PLUS = new Operation("Plus"); public static final Operation MINUS = new Operation("Minus"); public static final Operation TIMES = new Operation("Times"); public static final Operation DEVIDE = new Operation("Devide"); public double eval (double x, double y){ if(this == Operation.PLUS){ return x + y; } else if(this == Operation.MINUS){ return x - y; } else if(this == Operation.TIMES){ return x * y; } else if(this == Operation.DEVIDE){ return x / y; } return -1; } public String toString() {return m_name; } private static int m_nextOridnal = 0; private final int m_ordinal = m_nextOridnal++; private static final Operation[] VALUES = { PLUS, MINUS, TIMES, DEVIDE }; } |
這種實現的方法和前面提到的方法的區別之處在於它消除了抽象類和抽象函數,使用了條件判斷語句來給加減乘除四個枚舉值以不同的方法。從效果上看是完全可以的,但這不是面向對象編程所提倡的。所以這種思想沒有被引入到Java 1.5中來。
方便的Switch功能
在Java 1.5以前,Switch語句只能和int, short, char以及byte這些數據類型聯合使用。現在,在Java 1.5的枚舉中,你也可以方便的使用switch 語句了。比如:
public class EnumTest { public enum Grade {A,B,C,D,E,F,Incomplete } private Grade m_grade; public EnumTest(Grade grade) { this.m_grade = grade; testing(); } private void testing(){ switch(this.m_grade){ case A: System.out.println(Grade.A.toString()); break; case B: System.out.println(Grade.B.toString()); break; case C: System.out.println(Grade.C.toString()); break; case D: System.out.println(Grade.D.toString()); break; case E: System.out.println(Grade.E.toString()); break; case F: System.out.println(Grade.F.toString()); break; case Incomplete: System.out.println(Grade.Incomplete.toString()); break; } } public static void main(String[] args){ new EnumTest(Grade.A); } } |
(注:A, B, C, D, E, F, Incomplete是美國學校裏普遍採用的學習成績等級)
看了上面這個例子,大家肯定不禁要問:“是Java 1.5的Switch功能增強了嗎?”其實事情並不是這樣,我們看到的只是一個假象。在編譯器編譯完程序後,一切又回到從前了。Switch還是在整數上進行轉跳。下面就是反編譯後的程序片斷:
…… //從略 private void testing() { static class _cls1 { static final int $SwitchMap$EnumTest1$Grade[]; static { $SwitchMap$EnumTest1$Grade = new int[Grade.values().length]; try { $SwitchMap$EnumTest1$Grade[Grade.A.ordinal()] = 1; } catch(NoSuchFieldError nosuchfielderror) { } try { $SwitchMap$EnumTest1$Grade[Grade.B.ordinal()] = 2; } catch(NoSuchFieldError nosuchfielderror1) { } try { $SwitchMap$EnumTest1$Grade[Grade.C.ordinal()] = 3; } catch(NoSuchFieldError nosuchfielderror2) { } try { $SwitchMap$EnumTest1$Grade[Grade.D.ordinal()] = 4; } catch(NoSuchFieldError nosuchfielderror3) { } try { $SwitchMap$EnumTest1$Grade[Grade.E.ordinal()] = 5; } catch(NoSuchFieldError nosuchfielderror4) { } try { $SwitchMap$EnumTest1$Grade[Grade.F.ordinal()] = 6; } catch(NoSuchFieldError nosuchfielderror5) { } try { $SwitchMap$EnumTest1$Grade[Grade.Incomplete.ordinal()] = 7; } catch(NoSuchFieldError nosuchfielderror6) { } } } switch(_cls1..SwitchMap.EnumTest1.Grade[m_grade.ordinal()]) { case 1: // '\001' System.out.println(Grade.A.toString()); break; case 2: // '\002' System.out.println(Grade.B.toString()); break; case 3: // '\003' System.out.println(Grade.C.toString()); break; case 4: // '\004' System.out.println(Grade.D.toString()); break; case 5: // '\005' System.out.println(Grade.E.toString()); break; case 6: // '\006' System.out.println(Grade.F.toString()); break; case 7: // '\007' System.out.println(Grade.Incomplete.toString()); break; } } |
Java 1.5中枚舉的本質
從上面我們列舉的例子一路看過來,到了這裏,讀者一定會問,爲什麼Java 1.5的枚舉和Java 1.5以前的“類型安全枚舉”是那麼的相似呢(拋開Java 1.5中的Generics不說)?其實這非常好理解。大家也許注意到了這樣一個細節,Java 1.5中Enum的源程序的第一作者是Josh Bloch。這位Java大師在2001年出版的那本Java經典編程手冊《Effective Java Programming Language Guide》中的第五章裏,就已經全面清晰地闡述了“類型安全枚舉”的核心思想和實現方法。Java 1.5中的枚舉不是一種嶄新的思想,而是原有思想的一個實現和完善。其進步之處就在於將這些思想體現在了Java的編譯器中,程序員看到的是一個簡單,直觀的枚舉服務,將原先需要手工完成或是藉助於第三方工具完成的任務直接的放在了編譯器中。
當然,Java 1.5中的枚舉實現也不是“完美”的。它不是在Java虛擬機層次實現的枚舉,而是在編譯器層次實現的,本質上是一種“Java程序自動生成器”。這樣的實現的好處是不言而喻的。因爲它對Java的虛擬機沒有任何新的要求,這樣極大的減輕了Java虛擬機的壓力,Java 1.5的虛擬機不必要做大的改動,在以有的基礎上改進和完善就可以了。同時這種做法還使得程序有比較好的向前兼容性。這一指導思想和Java Generics是完全一致的,在Java 編譯器層增加功能,而不是大的更動以有的Java虛擬機。所以我們可以將Java 1.5中新增的枚舉看作是一種的“語法糖衣(Syntax Sugar)” 當然,不在虛擬機層次實現枚舉在有些情況下會暴露一些問題,有時候不免會有一些不倫不類的地方,並且我們多多少少還是犧牲了一些功能。情形和我以前提到的Java Generics一樣 (請參看http://tech.ccidnet.com/pub/article/c1078_a170543_p1.html )。下面就讓我們討論一下幾個比較顯著的問題。
特殊的Enum類
從前面的例子中大家可以看出,所有的枚舉類型都是隱式的衍生於基類java.lang.Enum。從表面上看,這個Enum類和其他的Java類沒有什麼區別。如果看一下我們前面給出的Enum源程序,那麼這個問題是非常顯而易見的。既然是一個普通的Java類,那麼我們可以不可以像其它類那樣使用呢?比若說,我們擴展一下這個Enum類,生成一個子類:
public class MyEnum extends Enum { protected Enum(String name, int ordinal); protected Object clone( ); } |
從語法上講,這樣的定義是完全合法的。但是如果你試圖編譯這段程序,Java的編譯器就會給出你錯誤信息。也就是說Enum類可以內部隱式的被擴展,去不允許你直接顯式的去擴展。所以說這個Enum類似比較“特殊”的一個類,編譯器對它有“特殊政策”。從理論上講,這是一種不值得推薦的做法。明明是一個非Final的類,你卻不能去Extends它,這有悖於類最基本原則的。這是Java 1.5枚舉實現中的一個瑕紕,不免叫人感到有些遺憾。
無法擴展的Enum類型
在很多的時候,我們希望我們定義的枚舉有擴展能力,就像我們定義的其它類那樣。不管從邏輯的角度,還是從面向對象的程序設計原則來看,這個要求都是非常合理的。比如我們前面定義的Operation枚舉,在那裏我們定義了加減乘除四個基本的運算。如果有一天我們想擴展一下這個枚舉,加入取對數和乘方的能力,我們可能會很自然的想到這樣的方法:
public enum OperationExt extends Operation { LOG { double eval(double x, double y) { return Math.log(y) / Math.log(x);} }, POWER { double eval(double x, double y) { return Math.power(x,y);} }, } |
很遺憾,當你試圖編譯這樣的程序的時候,編譯器會給出錯誤信息,編譯不會通過。也就是說,我們失去了擴展一個枚舉類型的能力。而在Java 1.5以前,我們是可以手工來完成這樣的工作的,比如說我們將Operation基類的構造函數定義爲protected的,那麼我們就可以方便的擴展Operation類,其源程序爲:
abstract class OperationExt extends Opetation { private final String m_name; Operation(String name) {m_name = name;} public static final LOG = new Operation("Log"){ protected double eval(double x, double y){ return Math.log(y) / Math.log(x); } } public static final POWER = new Operation("Power"){ protected double eval(double x, double y){ return Math.log(x,y); } } protected double eval (double x, double y); public String toString() {return m_name; } private static int m_nextOridnal = 0; private final int m_ordinal = m_nextOridnal++; private static final Operation[] VALUES = { LOG, POWER; } } |
從這個意義上來說,Java 1.5的枚舉給了我們一些束縛,使我們不再像以前那樣可以隨意的操作我們自己定義的類,這可以算是倒退了一小步。不過從總體上來說,Java 1.5的枚舉是能滿足絕大部分程序員的要求的,它的簡明,易用的特點是很突出的,犧牲了一些東西還是值得的。如果你對枚舉有超出一般的特殊要求,那麼你還是可以回到Java 1.5以前的老路上來,手工完成你的枚舉類。同時你不用擔心太多,因爲無論是使用Java 1.5的枚舉服務,還是手工完成,其本質都是一樣的。爲了進一步方便程序員對枚舉的操作,Java 1.5中還提供了一些輔助類。比如大家今後可能會經常用到EnumMap和EnumSet類。這些類是對枚舉功能的一個補充和完善。(T117)