vcjmhg 的个人博客

自律者自由

  menu

从源码角度看枚举 置顶!

概述

关于枚举类型,我们学过 C 语言的小伙伴应该都不陌生。所谓枚举类型,是一种特殊的数据结构,它的取值范围是有限的,所有取值结果都可以枚举出来,比如说一年四季(春夏秋冬)。对于确定范围的变量取值,我们通过枚举类型来表现较之用类表示更加简洁、方便、安全。

下边我们借助一些例子来介绍枚举类型的使用以及其实现原理。

基础

枚举类型的定义和使用都是比较简单的,比如要表示一年的四个季节(春夏秋冬)我们可以定下如下类 Wether:

1public enum Wether{
2    SPRING,SUMMER,AUTUMN,WINTER
3}

Wether 中分别定义了春(Spring)、夏(SUMMER)、秋(AUTUMN),冬(WINTER)这四个值。枚举类型使用 enum 这个关键字来进行定义。当然枚举类型可以像类一样单独写到一个文件中,也可以写到一个类内部,仅供该类使用。

使用时也非常简单可以直接通过类型命调用

1public class Test {
2    public static void main(String[] args) {
3        System.out.println("我是春天:"+Wether.SPRING);
4        System.out.println("我是夏天:"+Wether.SUMMER);
5        System.out.println("我是秋天:"+Wether.AUTUMN);
6        System.out.println("我是冬天:"+Wether.WINTER);
7    }
8}
9

输出结果如下

image.png

原理

看似简单的枚举类 Wether 其背后有隐藏着什么故事,下边我们拭目以待。

首先我们通过 jad 来对文件进行反编译我们的到如下代码

 1// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
 2// Jad home page: http://kpdus.tripod.com/jad.html
 3// Decompiler options: packimports(3) fieldsfirst ansi space 
 4// Source File Name:   Wether.java
 5
 6public final class Wether extends Enum
 7{
 8
 9	public static final Wether SPRING;
10	public static final Wether SUMMER;
11	public static final Wether AUTUMN;
12	public static final Wether WINTER;
13	private static final Wether $VALUES[];
14
15	public static Wether[] values()
16	{
17		return (Wether[])$VALUES.clone();
18	}
19
20	public static Wether valueOf(String name)
21	{
22		return (
23        )Enum.valueOf(testEnum/Wether, name);
24	}
25
26	private Wether(String s, int i)
27	{
28		super(s, i);
29	}
30
31	static 
32	{
33		SPRING = new Wether("SPRING", 0);
34		SUMMER = new Wether("SUMMER", 1);
35		AUTUMN = new Wether("AUTUMN", 2);
36		WINTER = new Wether("WINTER", 3);
37		$VALUES = (new Wether[] {
38			SPRING, SUMMER, AUTUMN, WINTER
39		});
40	}
41}

我们会发现 Java 编译器在实现枚举类型的时候,其实是按照类类型来进行处理的,也就是说:枚举类型其本质上仍然是类,是一个继承自 Enmu 的类。但是由于编译器帮我们默默的做了一些事情,使得我们使用起来更加方便也更加高效。

详细来说针对枚举类型编译器在背后为我们做了一下几件事情。

  • 定义一个继承自 Enmu 的一个类命名为 Wether
  • 为每个枚举实例对应创建一个类对象,这些类对象是用 public static final 修饰的。同时生成一个数组,用于保存全部的类对象
  • 生成一个静态代码块,用于初始化类对象和类对象数组
  • 生成一个构造函数,构造函数包含自定义参数和两个默认参数(下文会讲解这两个默认参数)
  • 生成一个静态的 values() 方法,用于返回所有的类对象
  • 生成一个静态的 valueOf() 方法,根据 name 参数返回对应的类实例

官方的对编译器对 Enum 的处理做了如下说明:文档地址

image.png

为了便于大家阅读我将我对上边的文档尝试进行了翻译

image.png

当让只看官方文档会让人云里雾里的,下边我们结合 Enum 的源码来进行一些分析。

 1package java.lang;
 2
 3import java.io.Serializable;
 4import java.io.IOException;
 5import java.io.InvalidObjectException;
 6import java.io.ObjectInputStream;
 7import java.io.ObjectStreamException;
 8
 9
10public abstract class Enum<E extends Enum<E>>
11        implements Comparable<E>, Serializable {
12
13    private final String name;
14
15  
16    public final String name() {
17        return name;
18    }
19
20  
21    private final int ;
22
23    public final int ordinal() {
24        return ordinal;
25    }
26
27  
28    protected Enum(String name, int ordinal) {
29        this.name = name;
30        this.ordinal = ordinal;
31    }
32
33   
34    public String toString() {
35        return name;
36    }
37
38   
39    public final boolean equals(Object other) {
40        return this==other;
41    }
42
43  
44    public final int hashCode() {
45        return super.hashCode();
46    }
47
48    protected final Object clone() throws CloneNotSupportedException {
49        throw new CloneNotSupportedException();
50    }
51
52  
53    public final int compareTo(E o) {
54        Enum<?> other = (Enum<?>)o;
55        Enum<E> self = this;
56        if (self.getClass() != other.getClass() && // optimization
57            self.getDeclaringClass() != other.getDeclaringClass())
58            throw new ClassCastException();
59        return self.ordinal - other.ordinal;
60    }
61
62   
63    @SuppressWarnings("unchecked")
64    public final Class<E> getDeclaringClass() {
65        Class<?> clazz = getClass();
66        Class<?> zuper = clazz.getSuperclass();
67        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
68    }
69
70   
71    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
72                                                String name) {
73        T result = enumType.enumConstantDirectory().get(name);
74        if (result != null)
75            return result;
76        if (name == null)
77            throw new NullPointerException("Name is null");
78        throw new IllegalArgumentException(
79            "No enum constant " + enumType.getCanonicalName() + "." + name);
80    }
81
82   
83    protected final void finalize() { }
84
85    private void readObject(ObjectInputStream in) throws IOException,
86        ClassNotFoundException {
87        throw new InvalidObjectException("can't deserialize enum");
88    }
89
90    private void readObjectNoData() throws ObjectStreamException {
91        throw new InvalidObjectException("can't deserialize enum");
92    }
93}
94

Enum 作为 Java 中所有唯一基类,它在一定程度上定义了枚举类型的公共特征,从上边的源码中我们可以看到以下比较重要的几点:

  1. Enum 类有两个成员变量,name 和 ordinal 两个成员变量,其中 name 用于枚举常量的名字,如 SPRING,SUMMER,AUTUMN 等,oridinal 指的是默认编的序号,一般是从 0 开始。

  2. 返回 name 变量的方法有两个:一个是 name(),一个是 toString(),它们都是直接返回 name 变量(也就是说实现是一样的),但是它们所代表的含义确是不同的。

    关于两者的区别官方的 API 说明如下:

    image.png
    从官方 API 中我们可以看到,name() 方法会返回**枚举类型在该枚举类型声明的时所定义的枚举类型的精确名称。**官方不太赞同大多数编程者使用该方法获取枚举名称,而应该使用 toString() 类型,因为它返回的名称会更加友好。该方法在设计的时候主要是在一些特殊情况下使用(结果的正确性取决于给定的精确名称),而且该方法可能会在发布的过程中发生变化。

    image.png
    name() 方法类似都是返回枚举常量的名称。官方指出,该方法可被重写(尽管大部分情况下不是必须的,也不建议重写)。当一个对编程者更加友好的字符串名称被发现时,应该重写该方法替代原有的枚举类型名称。

    简而言之,给出 name 方法主要为了便于编程者在某些条件下重写名称的,我们在大部分情况下应该使用 toString()

  3. Enum 类不允许克隆,clone() 方法直接抛出异常。(保证枚举永远是单例的)

  4. Enum 类实现了 Comparable 接口,因此我们可以直接比较枚举常量的 ordinal 的值。

  5. Enum 类不允许反序列化,为了保证枚举永远是单例的。

使用场景

学为所用,讲了那么多主要还是为了能用好枚举,因此我简单总结了一个枚举类型常用的一些场景:

  1. 当变量是有穷对象的集合时我们可以尝试使用枚举类型,比如使用到季节(Wether)的时候。
  2. 在某些场景下,需要变量是单例的(单例设计模式),我们也可以考虑使用枚举类型,因为枚举类型是天生单例的。

标题:从源码角度看枚举
作者:vcjmhg
地址:https://vcjmhg.top/essence_of_enumeration