深入Java 1.5枚举类型的内部实现原理
Posted huzhigenlaohu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入Java 1.5枚举类型的内部实现原理相关的知识,希望对你有一定的参考价值。
Java是一种面向对象的高级编程语言。它的出众之处就在于它的简洁。一个程序员所要做的就是创建类(Create Class)以及定义接口(Define Interface),如此而已。当然,这种简洁和优美是有代价的,比如失去了Enum这种广泛使用的数据类型就是一个不小的损失。在Java 1.5以前,程序员们不得不通过一些变通的方法来间接的解决这一问题。比如说,被普遍使用的整数枚举替代法和类型安全类替代法(Type safe Enum)。在正式讨论Java 1.5的枚举类型之前,让我们先简单回顾一下这两种枚举替代方法。一.整数枚举替代法
比如说我们要定义一个春夏秋冬四季的枚举类型,如果使用整数来模拟,其样子大概为:
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); //错误调用 } …… |
问题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) { …… } } |
它的缺点是:
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 }); } } |
这些工具软件其实是一些“程序生成器”。你按照它的语法规则定义你的枚举(语法相对简单直观),然后运行这些工具软件,它会将你的定义转化为一个完整的Java类,就像我们上面所写的那个程序一样。看到这里,读者也许会想:“为什么不能将这种工具软件的功能放到Java编译器中呢?那样我们不是就可以简单方便的定义枚举了吗?而具体的Java类程序由编译器来生成,我们不必再手工完成那么冗长的程序行,也不必使用什么第三方工具来生成这样的程序行了吗?”如果你有这样的想法,那么就要恭喜你了。因为你和Java的设计开发人员想到一块儿去了。好,现在就让我们来看看Java 1.5中新增的枚举的功能。
Java 1.5的枚举类型
在新的Java 1.5中,如果定义我们上面提到的春夏秋冬四季的枚举,那么语句非常简单,如下所示
public enum Season { SPRING, SUMMER, FALL, WINTER } |
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 }); } } |
/* * @(#)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(); } } |
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 }); } } |
依附于具体变量之上的方法
下面这段小程序也是一个比较有代表性的例子。即在列出的加,减,乘,除四个枚举值中,每一个值都有其相应的类定义段(就是所谓的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); } |
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 }); } } |
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 }; } |
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 }; } |
方便的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); } } |
看了上面这个例子,大家肯定不禁要问:“是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中的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( ); } |
无法扩展的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);} }, } |
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枚举类型的内部实现原理的主要内容,如果未能解决你的问题,请参考以下文章