Java枚举类之Enum源码分析
一、概述
Enum
的全写是Enumeration
,这个词的翻译是列举、逐条陈述、细目。在程序语言中,枚举类型是一种特殊的数据类型(常用的数据类型比如字符串、整型),这种数据类型的变量值限定在固定的范围,比如季节只有春夏秋冬,月份是12个。
枚举类型是JDK 5
之后引进的一种非常重要的引用类型,可以用来定义一系列枚举常量。在没有引入enum
关键字之前,要表示可枚举的变量,只能使用以下的方式。
public class SeasonDef {
public static final int SPRING = 1;
public static final int SUMMER = 2;
public static final int AUTUMN = 3;
public static final int WINTER = 4;
public static String getSeason(int type) {
String season = "";
switch (type) {
case SPRING:
season = "春天";
break;
case SUMMER:
season = "夏天";
break;
case AUTUMN:
season = "秋天";
break;
case WINTER:
season = "冬天";
break;
}
return season;
}
}
这种实现方式有几个弊端。
- 类型不安全。试想一下,有一个方法期待接受一个季节作为参数,那么只能将参数类型声明为
int
,但是传入的值可能是99
。显然只能在运行时进行参数合理性的判断,无法在编译期间完成检查。 - 指意性不强,含义不明确。我们使用枚举,很多场合会用到该枚举的字串符表达,而上述的实现中只能得到一个数字,不能直观地表达该枚举常量的含义。当然也可用
String
常量,但是又会带来性能问题,因为比较要依赖字符串的比较操作。
使用enum
来表示枚举可以更好地保证程序的类型安全和可读性。
enum
是类型安全的。除了预先定义的枚举常量,不能将其它的值赋给枚举变量。这和用int
或String
实现的枚举很不一样。enum
有自己的名称空间,且可读性强。在创建enum
时,编译器会自动添加一些有用的特性。每个enum
实例都有一个名字(name
)和一个序号(ordinal
),可以通过toString()
方法获取enum
实例的字符串表示。还以通过values()
方法获得一个由enum
常量按顺序构成的数组。
enum
还有一个特别实用的特性,可以在switch
语句中使用,这也是enum
最常用的使用方式了。
二、案例
2.1 例1:获取枚举 - values方法
import lombok.Getter;
import java.util.Arrays;
@Getter
public enum SeasonEnum {
SPRING(1, "春天"),
SUMMER(2, "夏天"),
AUTUMN(3, "秋天"),
WINTER(4, "冬天"),
;
Integer code;
String name;
SeasonEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
public static SeasonEnum getCode(Integer code) {
for (SeasonEnum season : SeasonEnum.values()) {
if(season.getCode().equals(code)) {
return season;
}
}
return null;
}
//java8写法
public static SeasonEnum getSeasonEnum(Integer code) {
return Arrays.stream(SeasonEnum.values())
.filter(t -> t.getCode().equals(code)).findFirst().orElse(null);
}
}
2.2 例2:获取枚举 - EnumMap类
@Getter
public enum SeasonEnum {
SPRING(1, "春天"),
SUMMER(2, "夏天"),
AUTUMN(3, "秋天"),
WINTER(4, "冬天"),
;
String code;
String name;
SeasonEnum(String code, String name) {
this.code = code;
this.name = name;
}
// 将数据缓存到map中
private static final Map<String, String> map = new HashMap<>();
static {
for (SeasonEnum season : SeasonEnum.values()) {
map.put(season.getCode(), season.getName());
}
}
// 根据name查询value值
public static String getNameByCode(String code) {
return map.get(code);
}
}
2.3 例3:枚举实现抽象方法
与常规抽象类一样,enum
类允许我们为其定义抽象方法,然后使每个枚举实例都实现该方法,以便产生不同的行为方式,注意abstract
关键字对于枚举类来说并不是必须的。
枚举也可以定义多个抽象方法
/*
* 枚举类里定义抽象方法时不能使用abstract关键字将枚举类定义成抽象类(由于系统自动会为它添加abstract关键字),
* 但由于枚举类须要显式建立枚举值,而不是做为父类,因此定义每一个枚举值时必须为抽象方法提供实现,不然将出现编译错误。
*/
public enum Operation {
PLUS {
public double eval(double a, double b){
return a + b;
}
},
MINUS {
public double eval(double a, double b){
return a - b;
}
},
MULTI {
public double eval(double a, double b){
return a * b;
}
},
DIVIDE {
public double eval(double a, double b){
return a / b;
}
};
//为枚举类定义一个抽象方法
//这个抽象方法由不一样的枚举值提供实现
public abstract double eval(double a, double b);
public static void main(String[] args){
for(Operation o : Operation.values()){
System.out.println(o.eval(1, 2));
}
}
}
三、源码分析
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
//枚举常量的名称
//大多数程序员应该使用toString方法而不是访问此字段。
private final String name;
//返回此枚举常量的名称,与其枚举声明中声明的完全相同
public final String name() {
return name;
}
//此枚举常量的序数(它在枚举声明中的位置,其中初始常量的序数为零)
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;
}
//果指定的对象等于此枚举常量,则返回true。
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();
}
//将此枚举与指定的枚举序号进行比较
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;
}
//返回与此枚举常量的枚举类型相对应的Class对象
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == java.lang.Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
/**
* 返回具有指定名称的指定枚举类型的枚举常量。
* 该名称必须与用于声明此类型的枚举常量的标识符完全一致。
* 请注意,对于特定枚举类型T,
* 有两个隐式声明方法可以直接使用:
* public static T valueOf(String) 根据名称获取单个枚举类型
* public static T[] values() 获取所有枚举类型数组
*/
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);
}
//枚举类不能有 finalize 方法
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");
}
}
四、底层原理
用javac
命令将SeasonEnum
这个枚举类编译生成SeasonEnum.class
可执行文件,紧接着使用javap
命令将SeasonEnum.class
文件反汇编。
public final class com.test.SeasonEnum extends java.lang.Enum<com.test.SeasonEnum> {
public static com.test.SeasonEnum[] values();
Code:
0: getstatic #1 // Field $VALUES:[Lcom/test/SeasonEnum;
3: invokevirtual #2 // Method "[Lcom/test/SeasonEnum;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[Lcom/test/SeasonEnum;"
9: areturn
public static com.test.SeasonEnum valueOf(java.lang.String);
Code:
0: ldc #4 // class com/test/SeasonEnum
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:
(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class com/test/SeasonEnum
9: areturn
static {};
Code:
0: iconst_0
1: anewarray #4 // class com/test/SeasonEnum
4: putstatic #1 // Field $VALUES:[Lcom/test/SeasonEnum;
7: return
}
- 类:
SeasonEnum
在底层其实还是用class
修饰的,说明枚举类本质上还是一个Class
,而且SeasonEnum
被jvm
隐式的用final
关键字修饰起来了,那么这个类不能被任何类继承,这也在一定程度上保证了类的安全。 values
方法:默认生成了一个values
方法,它返回了当前类型枚举的数组,这也解释了ordinal
的来源。valueOf
方法:在具体的每个自定义枚举类里面生成了一个根据枚举名返回枚举实例的方法,这其实是对java.lang.Enum
中valueOf
方法的具体重写,它只限于当前枚举类。- 静态代码块:
SeasonEnum
中所有的enum
实例在这里初始化完成。
五、拓展
5.1 枚举是如何保证线程安全的
当一个Java
类第一次被真正使用到的时候静态资源被初始化、Java
类的加载和初始化过程都是线程安全的。所以创建一个enum
类型是线程安全的。
5.2 为什么用枚举实现的单例是最好的方式
- 枚举写法简单
- 枚举自己处理序列化
- 枚举实例创建是
thread-safe
(线程安全的)
5.3 EnumMap与EnumSet
为了更好地支持枚举类型,java.util
中添加了两个新类:EnumMap
和EnumSet
。使用它们可以更高效地操作枚举类型。具体请参考Java容器之EnumMap源码分析和Java容器之EnumSet源码分析。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器