谈谈枚举


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


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. 【JVM从小白学成大佬】开篇

    JVM的重要性毋庸置疑,可以毫不夸张的说Java虚拟机是整个Java平台的基石. JVM方面的知识,也一直是BAT等大厂面试考核的重点.特别是JVM调优,故障排查性能调优,你知道该从哪些方面入手吗? ...

  2. MyBatis的flushCache和useCache的使用注意

    之前在利用MyBatis做开发的时候,遇到了一个问题,使用select配置的时候发现前后两次的结果是一样的,并且使用statementType="CALLABLE"配置,然后在配置 ...

  3. JavaScript 数据结构与算法之美 - 十大经典排序算法汇总(图文并茂)

    1. 前言 算法为王. 想学好前端,先练好内功,内功不行,就算招式练的再花哨,终究成不了高手:只有内功深厚者,前端之路才会走得更远. 笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 ...

  4. JAVA 泛型中的通配符 T,E,K,V,?

    前言 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说所操作的数据 ...

  5. JS函数提升和变量提升

    1.1什么是函数提升和变量的提升? JS引擎在运行整个JS代码的过程中,分为俩步. 第一步是读取和解析JS代码,第二部是执行. 在引擎解析JS代码的时候,当解析器遇见变量声明(var 变量名)和函数声 ...

  6. TypeScript进阶开发——ThreeJs基础实例,从入坑到入门

    前言 我们前面使用的是自己编写的ts,以及自己手动引入的jquery,由于第三方库采用的是直接引入js,没有d.ts声明文件,开发起来很累,所以一般情况下我们使用npm引入第三方的库,本文记录使用np ...

  7. TypeError: 'in <string>' requires string as left operand, not int

    报错 Traceback (most recent call last): File "D:/PyCharm 5.0.3/WorkSpace/2.NLP/9.DL在NLP中的应用/4. Ve ...

  8. 动态数组& allocator

    问题来源 在编写程序的时候,对数组."二维数组"的分配的删除掌握的不是很清楚,不能正确的进行定义初始化. 以及在使用vector的时候,如何正确的定义及初始化 注意!!! 尽量使用 ...

  9. DB2 根据id查表

    SELECT * FROM SYSCAT.TABLES WHERE TBSPACEID = 2 AND TABLEID = 50 SELECT * FROM SYSCAT.COLUMNS WHERE  ...

  10. ZOJ4027 Sequence Swapping DP

    link:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=4027 题意: 有一个括号序列,每个括号对应一个值,现在可以使得相 ...