Java-Enum枚举类
前言
介绍
enum 的全称为 enumeration, 是 JDK 1.5 中引入的新特性,存放在 java.lang 包中。
使用关键字enum来定义枚举类,枚举类是一个特殊的类,大部分功能和普通类是一样的。
区别为:枚举类继承了java.lang.Enum类,而不是默认的Object类。
而java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable接口。
非抽象的枚举类默认会使用final修饰,因此不能派生子类
一、枚举的实现原理
枚举类型使用的最常用类型就是枚举常量,下面通过一个简单的Demo来说明枚举的原理。
使用示例:
// 定义
public enum Color {
BLACK, WHITE
}
// 使用
public class Main {
public static void main(String[] args) {
System.out.println(Color.BLACK);
}
}
// 结果
// BLACK
从简单的示例,不能看出枚举的特点和枚举的具体实现。
下面我们通过 jad工具来反编译Color类, 通过jad -sjava Color.class反编译出一份java文件:
// final修饰,无法被继承
public final class Color extends Enum {
// 为了避免 返回的数组修改,而引起内部values值的改变,返回的是原数组的副本
public static Color[] values() {
return (Color[]) $VALUES.clone();
}
// 按名字获取枚举实例
public static Color valueOf(String name) {
return (Color) Enum.valueOf(em / Color, name);
}
// 私有的构造函数
private Color(String name, int ordinal) {
super(name, ordinal);
}
// enum第一行的声明的变量,都对应一个枚举实例对象
public static final Color BLACK;
public static final Color WHITE;
//
private static final Color $VALUES[];
// 静态域初始化,说明在类加载的cinit阶段就会被实例化,jvm能够保证类加载过程的线程安全
static {
BLACK = new Color("BLACK", 0);
WHITE = new Color("WHITE", 1);
$VALUES = (new Color[]{
BLACK, WHITE
});
}
}
从反编译的类中,可以看出, 我们使用enum关键字编写的类,在编译阶段编译器会自动帮我们生成一份真正在jvm中运行的代码。
Color 类继承自 Enum类:
public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable
Enum类接受一个继承自Enum的泛型.(在反编译java文件中没有体现泛型是因为,泛型在编译阶段就会被类型类型擦除(参考泛型详解,链接:泛型详解),替换为具体的实现)。
从反编译的Color类中可以看出,在enum关键字的类中,第一行 (准确的说是第一个分号前) 定义的变量,都会生成一个 Color实例,且它是在静态域中进行初始化的, 而静态域在类加载阶段的cinit中进行初始化,所以枚举对象是线程安全的,由JVM来保证.
生成的枚举类有 Color $VALUES[];成员变量,外部可以通过values()方法获取当前枚举类的所有实例对象。
二、使用反射查看枚举类
通过上述查看编译后的枚举类后,我们可以清晰看到枚举类编译后的类内容信息,但是笔者仍旧抱有怀疑的态度,于是就用反射做了验证。
源码:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) {
Class clazz = Color.class;
Field[] declaredFields = clazz.getDeclaredFields();
for(Field field : declaredFields){
System.out.println(field.toString());
}
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method.toString());
}
Constructor[] constructors = clazz.getConstructors();
System.out.println(constructors.length);
}
enum Color {
BLACK, WHITE
}
}
运行结果:
public static final Main$Color Main$Color.BLACK
public static final Main$Color Main$Color.WHITE
private static final Main$Color[] Main$Color.$VALUES
public static Main$Color[] Main$Color.values()
public static Main$Color Main$Color.valueOf(java.lang.String)
0
可以得知,编译过后的Color类,含三个常量,两个方法,没有可见的构造方法。
三、 枚举的高级使用
先说一个案例,你需要让每一个星期几对应到一个整数,比如星期天对应0。由于枚举类在定义的时候会自动为每个变量添加一个顺序,从0开始。
假如你希望0代表星期天,1代表周一,并且你在定义枚举类的时候,顺序也是这个顺序,那你可以不用定义新的变量,就像这样:
public enum Weekday {
SUN,MON,TUS,WED,THU,FRI,SAT
}
这个时候,星期天对应的ordinal值就是0,周一对应的就是1,满足你的要求。但是,如果你这么写,那就有问题了:
public enum Weekday {
MON,TUS,WED,THU,FRI,SAT,SUN
}
我把SUN放到了最后,但是我还是希0代表SUN,1代表MON怎么办呢?默认的ordinal是指望不上了,因为它只会傻傻的给第一个变量0,给第二个1、、、
所以,我们需要自己定义变量!
看代码:
public enum Weekday {
MON(1),TUS(2),WED(3),THU(4),FRI(5),SAT(6),SUN(0);
private int value;
private Weekday(int value){
this.value = value;
}
}
我们对上面的代码做了一些改变:
首先,我们在每个枚举变量的后面加上了一个括号,里面是我们希望它代表的数字。
然后,我们定义了一个int变量,然后通过构造函数初始化这个变量。
你应该也清楚了,括号里的数字,其实就是我们定义的那个int变量。这句叫做自定义变量。
请注意:这里有三点需要注意:
一定要把枚举变量的定义放在第一行,并且以分号结尾。
构造函数必须私有化。事实上,private是多余的,你完全没有必要写,因为它默认并强制是private,如果你要写,也只能写private,写public是不能通过编译的。
自定义变量与默认的ordinal属性并不冲突,ordinal还是按照它的规则给每个枚举变量按顺序赋值。
好了,你已经掌握了上面的知识,你想,既然能自定义一个变量,能不能自定义两个呢?
当然可以:
public enum Weekday {
MON(1,"mon"),TUS(2,"tus"),WED(3,"wed"),THU(4,"thu"),FRI(5,"fri"),SAT(6,"sat"),SUN(0,"sun");
private int value;
private String label;
private Weekday(int value,String label){
this.value = value;
this.label = label;
}
}
每一个枚举类型极其定义的枚举变量在JVM中都是唯一的
这句话的意思是枚举类型它拥有的实例在编写的时候,就已经确定下,不能通过其他手段进行创建,且枚举变量在jvm有且只有一个对应的实例.
留言:文章是博主本人遇到问题,通过各种渠道收集资料解决后,并记录在word文档里,最后才整理发博,因此有些相似的内容是借鉴网友的,若有雷同,请谅解!