【Java基础】关于枚举类你可能不知道的事
谈谈枚举
如果一个类的对象个数是有限的而且是不变的,我们通常将这样的类设计成枚举类。
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基础】关于枚举类你可能不知道的事的更多相关文章
- Java 基础 enum枚举类 的创建/使用/接口继承 ,以及手动创建枚举类的对象为:public static final
笔记: import java.lang.*; /**一:枚举类 : enum Season implements info { s1(),s2(),s3(),s4() }; //s1--s4 放在S ...
- 【Java基础】枚举类与注解
枚举类与注解 枚举类的使用 当需要定义一组常量时,强烈建议使用枚举类. 枚举类的理解:类的对象只有有限个,确定的. 若枚举只有一个对象, 则可以作为一种单例模式的实现方式. 枚举类的属性: 枚举类对象 ...
- java基础41 枚举(类)
1.概述 枚举:一些方法在运行时,它需要数据不能是任意的,而必须是一定范围内的值,可以使用枚举解决 2.枚举的格式 enum 类名{ 枚举值 } 例子 package com.dhb.enumerat ...
- 【转】Java基础笔记 – 枚举类型的使用介绍和静态导入--不错
原文网址:http://www.itzhai.com/java-based-notes-introduction-and-use-of-an-enumeration-type-static-impor ...
- 黑马程序员:Java基础总结----枚举
黑马程序员:Java基础总结 枚举 ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 枚举 为什么要有枚举 问题:要定义星期几或性别的变量,该怎么定义?假设用1-7分别 ...
- Java基础之枚举
Java基础之枚举 作为1.5才增加的特性,枚举的使用并不是很多. 枚举其实就是一个比较特殊的类,就如同注解其实也是个特殊的接口一样(注解反编译之后没有了@符号).枚举使用enum关键字声明,通过反编 ...
- JAVA中的枚举类
某些情况下一个类的对象是有限而且固定的,例如性别就只有两个类(考虑大众情况).因此这种实例有限而且固定的类,java里面叫枚举类.枚举类的关键字是enum,一些基本的命名规则和文件命名等细节和一般的类 ...
- Java基础-DButils工具类(QueryRunner)详解
Java基础-DButils工具类(QueryRunner)详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 如果只使用JDBC进行开发,我们会发现冗余代码过多,为了简化JDBC ...
- Java基础之File类的使用
Java基础之File类的使用 1.File类的构造方法和常用方法 2.对File中listFile(FileNameFilter name)学习 3.与File文件类相关的实现 File类的构造方法 ...
随机推荐
- There’s More Than One Way To Do It!
There’s More Than One Way To Do It!
- Mybatis批处理(批量查询,更新,插入)
mybatis批量查询 注意这里的 in 和 <trim prefix="(" suffix=")"> 以及 in ( )的三种方式的(例1(推 ...
- ionic 页面动画 ngAnimate
git 地址: https://github.com/Augus/ngAnimate/ 使用方法: 下载后, 1.引入ngAnimate.js(依赖jquery); 2.引入 ng-animate.c ...
- 【sybase】You can’t run SELECT INTO in this database的解决办法
进入master 数据库,使用sp_dboption test ,'select into',true命令(其中test为要设置可用的数据库)
- Delphi - Indy TIdFTPServer封装类
在Delphi 7开发下有强大的Indy控件,版本为9,要实现一个FTP服务器,参考自带的例子,发现还要写很多函数,而且不支持中文显示文件列表等等. 于是,自己改进封装了下,形成一个TFTPServe ...
- 从入门到入土的JS 随笔day01
js 的全称是javascript ,JavaScript一种直译式脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型. 它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用 ...
- P3225 [HNOI2012]矿场搭建 割点 tarjan 双联通分量
https://www.luogu.org/problemnew/show/P3225 题意 煤矿工地可以看成是由隧道连接挖煤点组成的无向图.为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条 ...
- Codeforces Round #506 (Div. 3) 1029 D. Concatenated Multiples
题意: 给定n个数字,和一个模数k,从中选出两个数,直接拼接,问拼接成的数字是k的倍数的组合有多少个. 思路: 对于a,b两个数,假定len = length of (b),那么a,b满足条件就是a ...
- CodeForces 1058 F Putting Boxes Together 树状数组,带权中位数
Putting Boxes Together 题意: 现在有n个物品,第i个物品他的位置在a[i],他的重量为w[i].每一个物品移动一步的代价为他的w[i].目前有2种操作: 1. x y 将第x的 ...
- lightoj 1028 - Trailing Zeroes (I)(素数筛)
We know what a base of a number is and what the properties are. For example, we use decimal number s ...