谈谈枚举


如果一个类的对象个数是有限的而且是不变的,我们通常将这样的类设计成枚举类。


1. 枚举类的定义

枚举类有如下特点:

  • 枚举类默认是使用final关键字修饰的,所以枚举类不能被继承;
  • 枚举类的构造函数默认是使用private修饰的;
  • 定义枚举类时所有实例必须在第一行全部列出;
  • 枚举类也可以实现接口;
  • 枚举类可以包含抽象方法。
//默认final修饰,不能被继承
public enum EnumDemo implements Runnable { //枚举的字段必须加注释
//男性
MALE("male"){
@Override
//这边每个枚举类都单独实现了接口方法,也可以统一实现
//在枚举类的定义中实现一个就好了
public void run() {
System.out.println("l like run...");
} @Override
public void tellSex() {
System.out.println("l am a man");
}
},
//女性
Female("female"){
@Override
public void run() {
System.out.println("l hate running...");
} @Override
public void tellSex() {
System.out.println("l am a girl");
}
}; private String sex; public String getSex(){
return sex;
} /**
* 构造函数默认是priva的
* @param sex
*/
EnumDemo(String sex){
this.sex = sex;
} /**
* 抽象方法,需要枚举类实例实现这个方法
*/
public abstract void tellSex();
}

2. 枚举类的底层实现

假如有如下的一个枚举类定义

public enum T {
SPRING,SUMMER,AUTUMN,WINTER;
}

经过Java编译器编译后,如果我们反编译class文件可以看到如下代码:

public final class T extends Enum
{
private T(String s, int i)
{
super(s, i);
}
public static T[] values()
{
T at[];
int i;
T at1[];
System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
return at1;
} public static T valueOf(String s)
{
return (T)Enum.valueOf(demo/T, s);
} public static final T SPRING;
public static final T SUMMER;
public static final T AUTUMN;
public static final T WINTER;
private static final T ENUM$VALUES[];
static
{
SPRING = new T("SPRING", 0);
SUMMER = new T("SUMMER", 1);
AUTUMN = new T("AUTUMN", 2);
WINTER = new T("WINTER", 3);
ENUM$VALUES = (new T[] {
SPRING, SUMMER, AUTUMN, WINTER
});
}
}

可以看到经过编译后,枚举类也是一个普通的Java类。这个类继承了Enum这个类,并且使用final修饰,所以枚举类都是不能被继承的。枚举类中定义的枚举值都是这个枚举类的静态成员变量,而且在初始化代码块中会一次性初始化,放在value数组中。我们调用枚举类的values()方法能一次性拿到所有的枚举值。关于枚举类,有几个重要的方法需要说下。从上面的代码可以看出我们定义的枚举类都会继承Enum这个类。

public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
//枚举的名字,我们可以通valueof(name)来获取枚举值
//这个就是指代我们定义的枚举实例的名字,比如上面的“SPRING”和“SUMMER”等
private final String name; public final String name() {
return name;
}
//枚举的大小,枚举值比较大小默认的就是比较这个值的大小
//这个值的大小是根据我们定义枚举值的顺序来的,比如一个
//枚举值我们第一个定义那么这个枚举的ordinal就是0,比如上面定义的“SPRING”的ordinal值就是0
// 上面定义的“SUMMER”的值就是1,还有一点需要说明的就是:当我们在switch中使用枚举类型时,
//编译后就是匹配的这个ordinal值
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 super.hashCode();
}
//禁止克隆
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
//比较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;
}
//通过name来获得枚举
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");
}
}

3. 枚举类的序列化实现

以前的所有的单例模式都有一个比较大的问题,就是一旦实现了Serializable接口之后,就不再是单例了,因为,每次调用 readObject()方法返回的都是一个新创建出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。但是,为了保证枚举类型像Java规范中所说的那样,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定,即在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。

    //Quarter是一个枚举类
Quarter[] values = Quarter.values();
Quarter one = values[0]; ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\enum.txt"));
objectOutputStream.writeObject(one);
objectOutputStream.close(); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\enum.txt"));
Object object = objectInputStream.readObject();
//返回true
System.out.println(object==one);

枚举类在序列化的时候只会将name属相序列化。在上面代码中ObjectInputStream类的readObject方法进行反序列化时会先判断被反序列化的类的类型,如果是枚举类就获取这个枚举类的类型再调用valueof方法。

	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);
}

通过上面的代码可以看出,valueof方法根据获得的类还是枚举类初始化时缓存起来的类。所以系统中还是只会存在一个枚举类。

4. 用枚举实现单列

普通的单列实现方式有一个比较大的问题就是如果将单列类序列化后再进行反序列化那么同一个jvm中将存在两个单列类。通过上面的分析枚举类的反序列化不会出现这个问题,所以通过枚举类来实现单例模式是一个很好的选择。

public enum  EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}

还有一种方式防止单例反序列化生成多个对象的方法是给单列添加readResolve方法。具体原理请查阅序列化和反序列化相关知识。

 private Object readResolve() {
return singleton;
}

5. 枚举实例的创建过程是线程安全的

从第二部分的代码我们可以看出,枚举实例的创建是在静态代码块中创建的。静态代码块会在类的初始化过程中执行,而类的初始化过程是线程安全的,所以枚举实例的创建过程是线程安全的。

参考

【Java基础】关于枚举类你可能不知道的事的更多相关文章

  1. Java 基础 enum枚举类 的创建/使用/接口继承 ,以及手动创建枚举类的对象为:public static final

    笔记: import java.lang.*; /**一:枚举类 : enum Season implements info { s1(),s2(),s3(),s4() }; //s1--s4 放在S ...

  2. 【Java基础】枚举类与注解

    枚举类与注解 枚举类的使用 当需要定义一组常量时,强烈建议使用枚举类. 枚举类的理解:类的对象只有有限个,确定的. 若枚举只有一个对象, 则可以作为一种单例模式的实现方式. 枚举类的属性: 枚举类对象 ...

  3. java基础41 枚举(类)

    1.概述 枚举:一些方法在运行时,它需要数据不能是任意的,而必须是一定范围内的值,可以使用枚举解决 2.枚举的格式 enum 类名{ 枚举值 } 例子 package com.dhb.enumerat ...

  4. 【转】Java基础笔记 – 枚举类型的使用介绍和静态导入--不错

    原文网址:http://www.itzhai.com/java-based-notes-introduction-and-use-of-an-enumeration-type-static-impor ...

  5. 黑马程序员:Java基础总结----枚举

    黑马程序员:Java基础总结 枚举   ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 枚举 为什么要有枚举 问题:要定义星期几或性别的变量,该怎么定义?假设用1-7分别 ...

  6. Java基础之枚举

    Java基础之枚举 作为1.5才增加的特性,枚举的使用并不是很多. 枚举其实就是一个比较特殊的类,就如同注解其实也是个特殊的接口一样(注解反编译之后没有了@符号).枚举使用enum关键字声明,通过反编 ...

  7. JAVA中的枚举类

    某些情况下一个类的对象是有限而且固定的,例如性别就只有两个类(考虑大众情况).因此这种实例有限而且固定的类,java里面叫枚举类.枚举类的关键字是enum,一些基本的命名规则和文件命名等细节和一般的类 ...

  8. Java基础-DButils工具类(QueryRunner)详解

    Java基础-DButils工具类(QueryRunner)详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 如果只使用JDBC进行开发,我们会发现冗余代码过多,为了简化JDBC ...

  9. Java基础之File类的使用

    Java基础之File类的使用 1.File类的构造方法和常用方法 2.对File中listFile(FileNameFilter name)学习 3.与File文件类相关的实现 File类的构造方法 ...

随机推荐

  1. JDBC、Tomcat为什么要破坏双亲委派模型?

    问题一:双亲委派模型是什么 如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的 ...

  2. codeforce978C-Almost Arithmetic Progression+暴力,枚举前两个数字的情况

    传送门:http://codeforces.com/contest/978/problem/D 题意:求变为等差数列,最小要改动的数字个数: 思路:暴力,这道题只用枚举前面两个数字的情况就ok,反思自 ...

  3. ccpc网赛 hdu6703 array(权值线段树

    http://acm.hdu.edu.cn/showproblem.php?pid=6703 大意:给一个n个元素的数组,其中所有元素都是不重复的[1,n]. 两种操作: 将pos位置元素+1e7 查 ...

  4. Codeforces Round #379 (Div. 2) E. Anton and Tree 缩点 树的直径

    传送门 题意: 这道题说的是在一颗有两种颜色的树上,每操作一个节点,可以改变这个节点颜色和相邻同色节点的颜色.问最少操作次数,使得树上颜色相同. 思路: 先缩点,把相同的颜色的相邻节点缩在一起.再求出 ...

  5. Go语言os标准库常用方法

    1. os.Getwd()函数 原型:func Getwd()(pwd string, err error) 作用:获取当前文件路径 返回:当前文件路径的字符串和一个err信息 示例: package ...

  6. 调度系统Airflow的第一个DAG

    Airflow的第一个DAG 考虑了很久,要不要记录airflow相关的东西, 应该怎么记录. 官方文档已经有比较详细的介绍了,还有各种博客,我需要有一份自己的笔记吗? 答案就从本文开始了. 本文将从 ...

  7. JS-DOM ~ 02. 隐藏二维码、锁定、获取输入框焦点、for循环为文本赋值、筛选条件、全选和反选、属性的方法操作、节点的层次结构、nodeType

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. Tomcat性能调优参数简介

    近期,我们的一个项目进入了试运营的阶段,在系统部署至阿里云之后,我们发现整个系统跑起来还是比较慢的,而且,由于代码的各种不规范,以及一期进度十分赶的原因,缺少文档和完整的测试,整个的上线过程一波三折. ...

  9. 常用logback.xml配置详解

    选择logback的理由 ==logback==与==log4j==的简单对比一下: 1.首先,对于同样的代码路径,==logback==使用起来更快. 2.==logback==原生实现了log4j ...

  10. 【Offer】[50-2] 【字符流中第一个只出现一次的字符】

    题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 请实现一个函数用来找出字符流中第一个只出现一次的字符.例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次 ...