Java-Enum枚举类


文章目录

  • 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有且只有一个对应的实例.