一、枚举类型——values()方法的神秘之处

所有的枚举类型都是由编译器通过继承 Enum 类来创建的。然而,如果仔细查看Enum类的代码,你会发现里面并没有 values() 方法,我们却已经能直接使用它了,是哪里有其他“隐藏”的方法吗?我们可以编写一个小的反射程序来一探究竟:

Reflection.java

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Set;
import java.util.TreeSet;

enum Explore {HERE, THERE}

public class Reflection {
    public static Set<String> analyze(Class<?> enumClass) {
        System.out.println("_____ Analyzing " + enumClass + " _____");
        System.out.println("Interfaces:");
        for (Type t : enumClass.getGenericInterfaces()) {
            System.out.println(t);
        }
        System.out.println("Base: " + enumClass.getSuperclass());
        System.out.println("Methods: ");
        Set<String> methods = new TreeSet<>();
        for (Method m : enumClass.getMethods()) {
            methods.add(m.getName());
        }
        System.out.println(methods);
        return methods;
    }

    public static void main(String[] args) {
        Set<String> exploreMethods =
                analyze(Explore.class);
        Set<String> enumMethods = analyze(Enum.class);
        System.out.println("Explore.containsAll(Enum)? " + exploreMethods.containsAll(enumMethods));
        System.out.print("Explore.removeAll(Enum): ");
        exploreMethods.removeAll(enumMethods);
        System.out.println(exploreMethods);
        // Explore.class 的位置
        OSExecute.command("javap -cp out/production/myTest/enums/TEST0515 Explore");
    }
}

OSExecute.java

import java.io.*;

public class OSExecute {
    public static void command(String command) {
        boolean err = false;
        try {
            Process process = new ProcessBuilder(command.split(" ")).start();
            try (
                    BufferedReader results = new BufferedReader(new InputStreamReader(process.getInputStream()));
                    BufferedReader errors = new BufferedReader(new InputStreamReader(process.getErrorStream()))
            ) {
                results.lines().forEach(System.out::println);
                err = errors.lines().peek(System.err::println).count() > 0;
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        if (err) {
            // 直接抛出异常可能导致以上内容打印不出来
            // throw new OSExecuteException("Errors executing " + command);
            System.err.println("Errors executing " + command);
        }
    }
}

运行结果如下:

image.png

答案揭晓,values() 方法是由编译器添加的一个静态方法。注意在创建枚举的过程中,value0f() 方法同样也被添加到了 Explore 枚举中。这有点让人糊涂,Enum 类中同样也有一个 value0f() 方法,但是该方法有2个参数,而新加入的方法则只有 1 个。然而,这里的Set方法只关心方法名,并不关心方法签名,所以在调用 Explore.removeAll(Enum) 后,只剩下了 [values]。

打印结果显示 Explore 枚举被编译器限定为 final 类,所以你无法继承一个枚举类。此外还有一个static的初始化子句,你稍后会看到它可以被重定义。

由于类型擦除(相关介绍参见基础卷第20章)的缘故,反编译器得不到 Enum 类的完整信息,因此只能将 Explore 类的基类作为一个原始的 Enum 类来显示,而不是实际上的 Enum。

由于 values() 方法是由编译器在枚举类的定义中插入的一个静态方法,因此如果你将枚举类型向上转型为 Enum,则 values() 方法将不可用。然而要注意的是,Class 中有个 getEnunConstants() 方法,所以即使 Enum 的接口中没有 values() 方法,仍然可以通过 Class 对象来得到 enum 的实例:

UpcastEnum.java

enum Search {HITHER, YON}

public class UpcastEnum {
    public static void main(String[] args) {
        Search[] vals = Search.values();
        Enum e = Search.HITHER; // Upcast
        // e.values(); // No values() in Enum
        for (Enum en : e.getClass().getEnumConstants()) {
            System.out.println(en);
        }
    }
}

运行结果如下:

HITHER
YON

由于 getEnumconstants() 是Class类中的一个方法,因此对一个没有枚举的类也可以调用该方法。

NonEnum.java

public class NonEnum {
    public static void main(String[] args) {
        Class<Integer> intClass = Integer.class;
        try {
            for (Object en : intClass.getEnumConstants()) {
                System.out.println(en);
            }
        } catch (Exception e) {
            System.out.println("Expected: " + e);
        }
    }
}

运行结果如下:

Expected: java.lang.NullPointerException

该方法会返回null,因此如果你尝试引用该结果,就会抛出异常。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只小熊猫呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值