博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
枚举 学习笔记
阅读量:7065 次
发布时间:2019-06-28

本文共 9467 字,大约阅读时间需要 31 分钟。

枚举类型是 JDK 5 之后引进的一种非常重要的引用类型,可以用来定义一系列枚举常量。

在没有引入 enum 关键字之前,要表示可枚举的变量,只能使用 public static final 的方式。

public staic final int SPRING = 1;public staic final int SUMMER = 2;public staic final int AUTUMN = 3;public staic final int WINTER = 4;复制代码

这种实现方式有几个弊端。首先,类型不安全。试想一下,有一个方法期待接受一个季节作为参数,那么只能将参数类型声明为 int,但是传入的值可能是 99。显然只能在运行时进行参数合理性的判断,无法在编译期间完成检查。其次,指意性不强,含义不明确。我们使用枚举,很多场合会用到该枚举的字串符表达,而上述的实现中只能得到一个数字,不能直观地表达该枚举常量的含义。当然也可用 String 常量,但是又会带来性能问题,因为比较要依赖字符串的比较操作。

使用 enum 来表示枚举可以更好地保证程序的类型安全和可读性。

enum 是类型安全的。除了预先定义的枚举常量,不能将其它的值赋给枚举变量。这和用 int 或 String 实现的枚举很不一样。

enum 有自己的名称空间,且可读性强。在创建 enum 时,编译器会自动添加一些有用的特性。每个 enum 实例都有一个名字 (name) 和一个序号 (ordinal),可以通过 toString() 方法获取 enum 实例的字符串表示。还以通过 values() 方法获得一个由 enum 常量按顺序构成的数组。

enum 还有一个特别实用的特性,可以在 switch 语句中使用,这也是 enum 最常用的使用方式了。

反编译枚举类型源码

public enum Season {  SPRING, SUMMER, AUTUMN, WINTER;}复制代码

用 javap 反编译一下生成的 class 文件:

public final class Season extends java.lang.Enum
{ public static final Season SPRING; public static final Season SUMMER; public static final Season AUTUMN; public static final Season WINTER; public static Season[] values(); public static Season valueOf(java.lang.String); static {};}复制代码

可以看到,实际上在经过编译器编译后生成了一个 Season 类,该类继承自 Enum 类,且是 final 的。从这一点来看,Java 中的枚举类型似乎就是一个语法糖。

每一个枚举常量都对应类中的一个 public static final 的实例,这些实例的初始化应该是在 static {} 语句块中进行的。因为枚举常量都是 final 的,因而一旦创建之后就不能进行更改了。 此外,Season 类还实现了 values() 和 valueOf() 这两个静态方法。

再用 jad 进行反编译,我们可以大致看到 Season 类内部的实现细节:

public final class Season extends Enum{    public static Season[] values()    {        return (Season[])$VALUES.clone();    }    public static Season valueOf(String s)    {        return (Season)Enum.valueOf(Season, s);    }    private Season(String s, int i)    {        super(s, i);    }    public static final Season SPRING;    public static final Season SUMMER;    public static final Season AUTUMN;    public static final Season WINTER;    private static final Season $VALUES[];    static     {        SPRING = new Season("SPRING", 0);        SUMMER = new Season("SUMMER", 1);        AUTUMN = new Season("AUTUMN", 2);        WINTER = new Season("WINTER", 3);        $VALUES = (new Season[] {            SPRING, SUMMER, AUTUMN, WINTER        });    }}复制代码

除了对应的四个枚举常量外,还有一个私有的数组,数组中的元素就是枚举常量。编译器自动生成了一个 private 的构造方法,这个构造方法中直接调用父类的构造方法,传入了一个字符串和一个整型变量。从初始化语句中可以看到,字符串的值就是声明枚举常量时使用的名称,而整型变量分别是它们的顺序(从0开始)。枚举类的实现使用了一种多例模式,只有有限的对象可以创建,无法显示调用构造方法创建对象。

values() 方法返回枚举常量数组的一个浅拷贝,可以通过这个数组访问所有的枚举常量;而 valueOf() 则直接调用父类的静态方法 Enum.valueOf(),根据传入的名称字符串获得对应的枚举对象。

Enum 类是不能被继承的,如果我们按照上面反编译的结果自己写一个这样的实现,是不能编译成功的。Java 编译器限制了我们显式的继承 java.Lang.Enum 类, 报错 The type may not subclass Enum explicitly。

Enum 类源码

上面反编译出的 Season 类继承自 Enum 类,且调用了父类的构造函数和静态方法 valueOf,我们来通过 Enum 源码看一下其内部的实现。

public abstract class Enum
> implements Comparable
, Serializable {}复制代码

从类的声明来看,Enum 是个抽象类,且用到了泛型,类型参数的值必须要是 Enum 的子类。Enum 类还实现了 Comparable 和 Serializable 接口。

Enum 类有两个私有的成员,name 和 ordinal,在 protected 的构造方法中初始化,分别是表示枚举常量名称的字符串、枚举常量在枚举定义中序号的整型变量。这两个常量当然也会被其子类继承,前面看到 Season 类的构造方法中就是直接调用父类的构造方法设置这两个成员的。name 和 ordinal 都是 final 修饰的,一旦初始化后就不能进行修改了。

private final String name;public final String name() {    return name;}private final int ordinal; //从0开始public final int ordinal() {    return ordinal;}protected Enum(String name, int ordinal) {    this.name = name;    this.ordinal = ordinal;}复制代码

toString() 方法默认返回枚举常量的名称,该方法在子类中可以进行重写。

public String toString() {    return name;}复制代码

前面我们看到,编译后生成的枚举类中使用了多例模式,其对象只有有限个,且一旦创建后就不能修改。因为每一个枚举常量总是单例的,因而可以使用 == 直接进行比较。equals() 方法就是直接使用 == 比较两个枚举类型的变量的。

//直接使用 `==` 比较,不可在子类重写public final boolean equals(Object other) {    return this==other;}// 返回该枚举常量的哈希码。和equals一致,该方法不可以被重写。  public final int hashCode() {    return super.hashCode();}  //类型要相同,根据它们在枚举声明中的先后顺序来返回大小(前面的小,后面的大)。//子类不可以重写该方法 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;}复制代码

valueOf() 方法根据传入的字符串返回对应名称的枚举常量。调用 Class 对象的 enumConstantDirectory() (package-private)方法会创建一个名称和枚举常量的 Map,然后以名称为键进行查找。这里名称必须和枚举声明时的名称完全一致。

//返回带指定名称的指定枚举类型的枚举常量。名称必须与在此类型中声明枚举常量所用的标识符完全匹配。public static 
> T valueOf(Class
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);}// 得到枚举常量所属枚举类型的Class对象 public final Class
getDeclaringClass() { Class clazz = getClass(); Class zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? clazz : zuper; }复制代码

枚举对象不能序列化和反序列化,也不允许克隆:

protected final Object clone() throws CloneNotSupportedException {    throw new CloneNotSupportedException();}/** * enum classes cannot have finalize methods. */protected final void finalize() { }/** * prevent default deserialization */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");}复制代码

使用枚举

Java 中的枚举相较于其它编程语言要强大许多,这里给出几个枚举的简单用法。关于枚举的使用,在「Java 编程思想」中有十分详细的说明。

作为常量

enum Color {  RED, GREEN, BLUE}class Test {  public void main(String[] args) {    Color color = Color.RED;    switch(color) {      case RED:        System.out.println("red");        break;      case GREEN:        System.out.println("green");        break;      case GREEN:        System.out.println("blue");        break;      default:        System.out.println("unknow");    }  }}复制代码

向枚举中添加成员和方法

除了不能继承自 enum 外,我们基本可以将 enum 看作一个普通的类。可以向 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);        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 static void main(String[] args) {        if (args.length != 1) {            System.err.println("Usage: java Planet 
"); System.exit(-1); } double earthWeight = Double.parseDouble(args[0]); double mass = earthWeight/EARTH.surfaceGravity(); for (Planet p : Planet.values()) System.out.printf("Your weight on %s is %f%n", p, p.surfaceWeight(mass)); }}复制代码

反编译后发现构造方法如下:

private Planet(String s, int i, double d, double d1){    super(s, i);    mass = d;    radius = d1;}复制代码

为个别枚举常量重写方法

上一个例子中的为枚举新添加的方法是所有枚举实例共有的,实际上还可以为个别枚举实例单独重写方法:

public enum Gender {  Male {    @Override    public void behave() {      System.out.println("Male special behavior");    }  },  Female;    @Override  public void behave() {    System.out.println("Common behavior");  }    public static void main(String[] args) {    Gender.Male.behave(); //Male special behavior    Gender.Female.behave();  //Common behavior  }}复制代码

这里实际上产生了一个 Gender 的匿名子类 Gender$1,子类重写了父类 Gender 的 behave() 方法。在 Gender 的初始化语句中,生成枚举实例时:

public static final Male = new Gender$1("Male", 0);public static final Female = new Gender("Female", 1);复制代码

实现接口

枚举类默认继承自 Enum,由于不支持多继承,无法再继承其它类,但还可以实现接口。

interface Behavior {  void behave();}public enum Gender implements Behavior {  Male {    @Override    public void behave() {      System.out.println("Male special behavior");    }  },  Female;    @Override  public void behave() {    System.out.println("Common behavior");  }    public static void main(String[] args) {    Gender.Male.behave(); //Male special behavior    Gender.Female.behave();  //Common behavior    Behavior man = Gender.Male;    man.behave();  }}复制代码

用枚举实现单例

写一个线程安全的单例模式并不是一个简单的事情,但是借助枚举我们可以轻易地做到这一点。还记得我们前面分析枚举的源码时的发现吗? 每一个枚举常量在枚举类中的定义都是 public static final。static 类型保证了该实例会在类加载时被初始化(Java 的类加载机制,类第一次被使用时加载静态资源),且类加载和初始化是线程安全的。 final 则可以保证一旦完成初始化,该常量将不能被更改。 此外,由于父类 Enum 中明确了枚举对象不能被序列化和反序列化。 枚举完美地解决了单例模式中的序列化和反序列化问题,使用起来也极其简单:

public enum Singleton {  INSTANCE;  public void doSomething() {    //todo  }}复制代码

使用接口组织枚举

public interface Food {  enum Coffee implements Food{    BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO  }  enum Dessert implements Food{    FRUIT, CAKE, GELATO  }}复制代码

EnumSet 和 EnumMap

EnumSet 是一个特殊的 Set,其内部的元素必须是来自同一个 enum。EnumSet 内部使用 bit 向量实现,这种实现方式更紧凑高效,类似于传统基于 int 的位标志。相比于位标志,EnumSet 的可读性更强,且性能上也相差不大。详细可参考官方 API。

EnumMap 是一种特殊的 Map,要求其中的键 (key) 必须来自于同一个 enum。由于 enum 自身的实例数量是有限的,EnumMap 在内部可由数组实现,因此速度很快。除了只能使用 enum 作为键以外,其它的操作和一般的 Map 没有太大区别。详细可参考官方 API。

转载地址:http://lkill.baihongyu.com/

你可能感兴趣的文章
centos6.5上卸载和安装JDK7
查看>>
从文件加载至NSData
查看>>
Java连接访问Oracle--Connection.setSavepoint()方法使用
查看>>
LeetCode OJ:Maximal Square(最大矩形)
查看>>
抽象工厂 C++实现
查看>>
[KMP]字符串匹配算法
查看>>
[转] 随机数是骗人的,.Net、Java、C为我作证
查看>>
第一天
查看>>
VUE基础插值表达式
查看>>
如何在mysql客户端即mysql提示符下执行操作系统命令
查看>>
人月神话读后感
查看>>
Learning Agile software Development
查看>>
HDFS原理解析(整体架构,读写操作流程及源代码查看等)
查看>>
“精于算计”与“精于计算”我们应该更偏重哪方面?
查看>>
CAFFE安装(10):Mnist测试(可不做)
查看>>
7.2.7、数组指针的操作
查看>>
SetProp()、GetProp()、RemoveProp() API接口
查看>>
ES6 module模块
查看>>
content management system
查看>>
缓存穿透 缓存雪崩
查看>>