文章目录
- 通过反编译深入理解Java枚举类型
- 1.枚举类底层原理
- 1.1简单枚举类
- 反编译-javap
- 反编译-CRF
- 1.2自定义枚举类
- 1.3枚举类的其他特性
- 2.switch对枚举支持原理
通过反编译深入理解Java枚举类型
1.枚举类底层原理
1.1简单枚举类
public enum Season {
/**
* 枚举类测试
*/
SPRING, SUMMER, FALL, WINTER
}
反编译-javap
先用javap反编译一下(注意不带-c参数),看看编译器生成的枚举类结构:
Compiled from "Season.java"
public final class com.irappelt.test.model.po.Season extends java.lang.Enum<com.irappelt.test.model.po.Season> {
public static final com.irappelt.mymusic.model.po.Season SPRING;
public static final com.irappelt.mymusic.model.po.Season SUMMER;
public static final com.irappelt.mymusic.model.po.Season FALL;
public static final com.irappelt.mymusic.model.po.Season WINTER;
public static com.irappelt.mymusic.model.po.Season[] values();
public static com.irappelt.mymusic.model.po.Season valueOf(java.lang.String);
static {};
}
java.lang.Enum
可以看到枚举类内部继承Enum类,这里贴上部分源码
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
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;
}
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;
}
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);
}
// 省略部分方法...
}
注意:该抽象类不能被其他类显式继承,只能通过编译器给枚举类继承
总结
- 通过enum声明的枚举类最终会被编译器处理成final class,说明枚举类也是一个类(class),且不能被继承
- 枚举常量会被编译成static final修饰的字段
- 枚举类内部会被编译器生成静态的values()、valueOf()方法
反编译-CRF
这里再通过CRF反编译枚举类内部的具体实现:
/*
* Decompiled with CFR 0.151.
*/
public final class Season
extends Enum<Season> {
public static final /* enum */ Season SPRING = new Season("SPRING", 0);
public static final /* enum */ Season SUMMER = new Season("SUMMER", 1);
public static final /* enum */ Season FALL = new Season("FALL", 2);
public static final /* enum */ Season WINTER = new Season("WINTER", 3);
private static final /* synthetic */ Season[] $VALUES;
public static Season[] values() {
return (Season[])$VALUES.clone();
}
public static Season valueOf(String string) {
return Enum.valueOf(Season.class, string);
}
private Season(String string, int n) {
super(string, n);
}
static {
$VALUES = new Season[]{SPRING, SUMMER, FALL, WINTER};
}
}
总结
- 我们声明的枚举类会被编译器继承java.lang.Enum类,我们常用的name()方法、ordinar()方法都是来自此类
- 我们声明的枚举类含一个私有的构造方法,内部调用了父类的构造方法
- 枚举类中的每个枚举常量对应一个枚举类实例,且枚举常量名对应父类的name属性,枚举字段声明的顺序对应了父类的ordinal属性
- 枚举类会被编译器生成一个枚举类类型的$VALUES数组,存储着枚举实例(枚举常量),而values()方法则是用来返回这个数组的浅拷贝,除此之外,valueOf()方法用的很少,他内部调用父类的valueOf()方法,作用是在传入枚举常量名时,他会返回对应的枚举实例
1.2自定义枚举类
public enum MyEnum {
/**
* 自定义枚举类
*/
TRAIN(1, "火车"),
PLANE(2, "飞机"),
CAR(3, "汽车")
;
private Integer type;
private String desc;
MyEnum(Integer type, String desc) {
this.type = type;
this.desc = desc;
}
public static String getDescByType(Integer type) {
for (MyEnum myEnum: values()) {
if (myEnum.getType().equals(type)) {
return myEnum.getDesc();
}
}
return "";
}
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
反编译-CRF
/*
* Decompiled with CFR 0.151.
*/
public final class MyEnum
extends Enum<MyEnum> {
public static final /* enum */ MyEnum TRAIN = new MyEnum("TRAIN", 0, 1, "火车");
public static final /* enum */ MyEnum PLANE = new MyEnum("PLANE", 1, 2, "飞机");
public static final /* enum */ MyEnum CAR = new MyEnum("CAR", 2, 3, "汽车");
private Integer type;
private String desc;
private static final /* synthetic */ MyEnum[] $VALUES;
public static MyEnum[] values() {
return (MyEnum[])$VALUES.clone();
}
public static MyEnum valueOf(String string) {
return Enum.valueOf(MyEnum.class, string);
}
private MyEnum(String string, int n, Integer n2, String string2) {
super(string, n);
this.type = n2;
this.desc = string2;
}
/**
* 自定义方法
*/
public static String getDescByType(Integer n) {
for (MyEnum myEnum : MyEnum.values()) {
if (!myEnum.getType().equals(n)) continue;
return myEnum.getDesc();
}
return "";
}
public Integer getType() {
return this.type;
}
public void setType(Integer n) {
this.type = n;
}
public String getDesc() {
return this.desc;
}
public void setDesc(String string) {
this.desc = string;
}
static {
$VALUES = new MyEnum[]{TRAIN, PLANE, CAR};
}
}
总结
- 自定义的构造器会被编译器增加两个参数,也就是父类构造器的两个参数,分别是枚举常量名、枚举常量声明的顺序,内部用这两个参数调用父类构造器
1.3枚举类的其他特性
- 由于枚举类本身还是一个类,所以可以实现接口,但是不能继承,因为编译器已经将其继承了java.lang.Enum类
- 枚举类和其他类一样可以实现自定义方法
2.switch对枚举支持原理
测试类
class Test {
public static void main(String[] args) {
Season season = Season.FALL;
switch (season) {
case SPRING:
System.out.println("Spring!");
break;
case FALL:
System.out.println("Fall!");
break;
default:
break;
}
}
}
反编译-Jad
通过CFR反编译出的内容并不能很好的说明问题,所以这里借助Jad来进行反编译,效果好的一批:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Season.java
package com.irappelt.test.model.po;
import java.io.PrintStream;
// Referenced classes of package com.irappelt.test.model.po:
// Season
class Test
{
Test()
{
}
public static void main(String args[])
{
Season season = Season.FALL;
static class _cls1
{
static final int $SwitchMap$com$irappelt$test$model$po$Season[];
static
{
$SwitchMap$com$irappelt$test$model$po$Season = new int[Season.values().length];
try
{
$SwitchMap$com$irappelt$test$model$po$Season[Season.SPRING.ordinal()] = 1;
}
catch(NoSuchFieldError nosuchfielderror) { }
try
{
$SwitchMap$com$irappelt$test$model$po$Season[Season.FALL.ordinal()] = 2;
}
catch(NoSuchFieldError nosuchfielderror1) { }
}
}
switch(_cls1..SwitchMap.com.irappelt.test.model.po.Season[season.ordinal()])
{
case 1: // '\001'
System.out.println("Spring!");
break;
case 2: // '\002'
System.out.println("Fall!");
break;
}
}
}
分析
- 编译器对switch语句进行处理的时候,如果是枚举类型,则会维护一个switchMap数组,数组的索引是case语句中涉及到的枚举常量的ordinal属性,数组的值为int类型,且从1递增,到时候比较时就比较数组对应索引的值
- 其实switch只支持int类型,只是编译器对byte、short、int、char、String、enum进行了转换处理映射处理
- 题外话:switch对String的支持是将String转换成hashcode处理,并且为了避免hash冲突,还进行了equals处理