文章目录

  • 通过反编译深入理解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处理