你是否了解 Java 中的枚举?枚举能被反射吗?

163 阅读4分钟

1. 枚举的概念

  Java中的枚举是一种特殊的数据类型,它允许程序员定义一个固定的一组常量,这些常量可以作为变量在程序中使用。

  在Java中,枚举被定义为一个特殊的类,其中每个枚举常量都是该类的一个实例。

2. 枚举的使用

  定义一个枚举需要使用enum关键字,枚举中的常量都用大写字母命名,每个常量用逗号分隔。例如:

public enum TestEnum {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}

枚举可以结合switch使用

public class Main {
    public static void main(String[] args) {

        //注意,enum 类型是不能 new 的
        TestEnum testEnum = TestEnum.MONDAY;

        switch(testEnum){
            case MONDAY:
                System.out.println("MONDAY");
                break;
            case TUESDAY:
                System.out.println("TUESDAY");
                break;
            case WEDNESDAY:
                System.out.println("WEDNESDAY");
                break; 
            case THURSDAY:
                System.out.println("THURSDAY");
                break;
            case FRIDAY:
                System.out.println("FRIDAY");
                break;
            case SATURDAY:
                System.out.println("SATURDAY");
                break;
            case SUNDAY:
                System.out.println("SUNDAY");
                break;
            default:
                System.out.println("非法输入");
                break;
        }
    }
}
结果:
MONDAY

  在定义枚举的时候,它会默认继承java.lang.Enum这个类,它提供的常用方法:

返回类型方法名描述
Stringname()返回枚举常量的名称。
intordinal()返回枚举常量在枚举类中的位置,位置从0开始计数。
intcompareTo(Enum e)按照枚举常量在枚举类中的位置进行比较。
TvalueOf(Class enumType, String name)根据枚举常量的名称返回对应的枚举常量。
T[]values()返回枚举类中所有的枚举常量
TvalueOf(String name)返回一个指定名称的枚举常量

案例:

public static void main(String[] args) {
    //获取所有的枚举常量
    TestEnum[] testEnum = TestEnum.values();
    //遍历所有的枚举常量,并打印下标
    for (int i = 0; i < testEnum.length; i++) {
        System.out.println(testEnum[i] + ": " + testEnum[i].ordinal());
    }
}
结果:
MONDAY: 0
TUESDAY: 1
WEDNESDAY: 2
THURSDAY: 3
FRIDAY: 4
SATURDAY: 5
SUNDAY: 6

  你会发现在Enum类中没有values()这个方法:

image.png

  这是为什么呢?values()是由编译器自动添加到每个枚举类型中的。这个方法返回的是一个包含枚举类中所有枚举常量的数组,数组的顺序按照常量在定义时的顺序排列。

2.1 枚举的构造方法

  在枚举中可以定义属性、方法等,就像普通的类那样,其中最重要的是,枚举的构造方法默认是私有的。

image.png

  普通类的构造方法调用时机是 new 的时候;而枚举比较特殊,枚举类的构造方法在枚举常量被创建时调用,而且只会被调用一次。枚举常量的创建是在枚举类被加载时自动进行的,因此可以保证枚举常量的唯一性。

  假设我们要定义一个枚举类型来表示颜色,其中每个枚举常量都有三个属性:红色、绿色和蓝色分量。可以这样定义:

public enum Color {
    RED(255, 0, 0),
    GREEN(0, 255, 0),
    BLUE(0, 0, 255);

    private final int red;
    private final int green;
    private final int blue;

    private Color(int red, int green, int blue) {
        this.red = red;
        this.green = green;
        this.blue = blue;
    }

    public int getRed() {
        return red;
    }

    public int getGreen() {
        return green;
    }

    public int getBlue() {
        return blue;
    }
}

  在这个例子中,每个枚举常量都有三个属性:redgreenblue,表示红色、绿色和蓝色分量。在枚举常量被创建时,括号中的参数会被传入构造方法,用于初始化枚举常量的属性。例如,RED枚举常量的构造方法被调用时,会将255, 0, 0作为参数传入,用于初始化redgreenblue属性。

public static void main(String[] args) {
    Color color = Color.BLUE;
    System.out.println(color.getRed());
    System.out.println(color.getGreen());
    System.out.println(color.getBlue());
}
结果:
0
0
255

3. 枚举与反射

  我们尝试反射枚举类型:

public class ReflectEnum {

    public enum Color {
        RED, GREEN, BLUE;
        private Color() {
            System.out.println("调用了私有构造方法");
        }
    }
    
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        Constructor<?> constructor = Color.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Color red = (Color) constructor.newInstance();
    }
}

运行结果:

image.png

  报错的信息表示没有这个构造方法,但是里面是有一个这样的构造方法的,为什么还报错了呢?

  还记得枚举类型默认继承的是java.lang.Enum这个类,而这个父类里面也有一个构造方法,并且只有这一个:

image.png

  也就是说先初始化父类构造方法再初始化子类构造方法,要注意的是枚举的子类构造方法默认掉用父类构造方法,所以改一下代码:

public class ReflectEnum {

    public enum Color {
        RED, GREEN, BLUE;
        private Color() {
            System.out.println("调用了私有构造方法");
        }
    }

    public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        //改动
        Constructor<?> constructor = 
     Color.class.getDeclaredConstructor(String.class,int.class);
     
        constructor.setAccessible(true);
        Color red = (Color) constructor.newInstance("RED",10);
    }
}

结果:

image.png

  现在报了这个错误,没错,就是不能反射地创建枚举对象!!!

  我们点进newInstance() 方法的源码,发现如下:

image.png