打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
「每日知识点」深入理解 Java 枚举类型

原创程序之心丁仪

枚举是软件开发中使用率非常高的类型。这里针对枚举进行一次深入的讨论,希望对您有所帮助。

枚举的使用

Java 中的枚举是一个比较特殊的类型,既具有 class 的特性,又具有自己特殊的特性。定义枚举类型使用 enum 关键字,枚举值一般使用大写字母,如下所示。使用枚举类型的 name() 方法可以获取字符串的名称,使用 ordinal() 方法可以获取枚举值的下标,这里不做赘述。

枚举同样可以拥有构造器和变量,但枚举类型的构造器要求必须是 private 类型。这是为了确保枚举的值一定由自己定义,拒绝外界传入。与 class 不同的是,枚举类型的构造器不定义访问权限时,默认为 private。

枚举类型自动拥有 values 和 valueOf 方法,values 用于获取枚举所有的值,valueOf 用于根据名称反查枚举值。在后面的实现原理中,我们将看到这两个方法的实现。

枚举的实现原理

枚举类型编译后生成一个 class 并且继承 Enum 类型(敲黑板,划重点,一定要记住)。反编译可以看到 SexTwo 的字节码,对字节码进行还原可以得到如下的 class。

SexTwo 编译后变成了一个普通的 class 类型。这里需要注意的是,SexTwo 拥有修饰符 final,因此不能有子类。同时继承了 Enum 类型,因此开发中 SexTwo 也将不能继承任何父类。

SexTwo 生成的构造器是 private 类型,有两个 public static final SexTwo 类型的变量 MALE 和 FEMALE。编译时编译器自动根据枚举值的顺序确定下标,并在创建枚举值时使用。枚举类型定义的值都是 public static final 类型,在静态初始化中进行初始化。这里可以看到 SexTwo 在静态初始化中为变量 MALE、FEMALE 进行赋值,使用的参数就是我们给定的参数 man 和 woman。

自动生成的 values 和 valueOf

Java 编译枚举类型时,自动加上两个静态方法 values 和 valueOf。如果我们定义了签名完全相同的方法会编译报错 “已在枚举中定义了方法 values”。

枚举类型使用私有静态变量 $VALUES 存储所有的值,在静态初始化中赋值,类型为数组,值为所有枚举值,用于 values 和 valueOf 方法。

values 方法的作用是返回所有枚举值,实现很简单,就是 clone 一下 $VALUES 的值。valueOf 方法的作用是根据枚举值的名称返回枚举值,实现方法是调用 Enum.valueOf 方法,后文会在 Enum 类型中介绍这个方法。

Enum 类型

Enum 类型是 Java 中所有枚举类型的父类,并且是抽象类。需要注意的是我们不能直接继承 Enum 类,只有编译器生成的枚举最终的 class 可以继承,直接继承会导致编译器报错 “类无法直接扩展 java.lang.Enum”。

Enum 类型提供了我们常用的 name() 和 ordinal() 方法,其中的 name 和 ordinal 变量都是 final 类型。

Enum 拥有一个构造器,参数为 name 和 ordinal。经过前面的分析可以知道,这个构造器我们是没有办法直接调用的,只有编译器编译的枚举类型可以调用。

Enum 定义了一个 valueOf 方法,用于枚举类型调用。可以看到 SexTwo 中的 valueOf 方法就是调用 Enum.valueOf(SexTwo,s) 实现的。

Enum 中的大部分方法都是 final 类型的,因此枚举类型是没有办法覆盖这些方法的,唯一能够覆盖的就是 toString 方法。

枚举 与 Class

前面我们已经看到,values 方法是编译器自动加到枚举类型上的,而 Enum 类型并没有提供 values 方法,也就是说我们把枚举类型向上转型为 Enum 类型后没法调用 values 方法。或者我们可能需要传递 Class 对象进行反射操作,这种情况下如何操作枚举呢?

Class 对象的 isEnum() 方法可以用来判断是否是枚举类型,如果是枚举类型该方法返回 true。Class 对象的 getEnumConstants() 方法可以用来获取所有枚举值。

枚举使用抽象方法

前面提到了枚举类型编译之后是以 class 方式运行的,因此枚举类型也可以定义抽象方法。有同学可能要问了,前面说是 final class 类型,怎么可以定义抽象方法呢,抽象方法需要类型是 abstract class ?我们来试一下。

如下,SexThree 定义了一个抽象方法 getAlias()。枚举值 MALE 实现了这个抽象方法,返回 'man'。在枚举中定义抽象方法,可以为多个枚举值定义不同的实现,枚举值自身即可处理数据。

对 SexThree 进行编译和反编译,可以看到,这里生成的类型是抽象类,仍然继承了 Enum。MALE 在编译后生成了 SexThree$1.class,继承 SexThree 并实现了抽象方法。虽然这里枚举类型是抽象类,但是 java 编译器限制了我们不能继承这个抽象类,继承情况下编译时会报错“枚举类型不可继承”。

枚举实现接口

枚举最终编译成 class,因此也可以实现接口。如下的 SexFour 就实现了 Runnable 接口,定义了 run 方法。因为枚举不能继承其他类型,这个特性使得我们可以通过实现多个方法来定义枚举的某些特征。

对 SexFour 进行编译和反编译,可以看到,这里又生成了 final class 类型,并且继承了 Enum,实现了 Runnable 接口。

EnumMap

EnumMap 是一个使用枚举值做 key 的 Map 实现。如下代码即可创建 EnumMap 的实例,创建时需要指定 key 类型的 class,或者传入一个 map 进行初始化。EnumMap 使用数组存储数据,效率高于 HashMap。

EnumMap 使用枚举值做 key,因此选择了枚举值的下标作为值的下标,把值存储在一个数组中。这样显著提高了数据存取效率,但是容量不能发生变化,适合于数据量比较固定又需要较高效率的场景。

EnumSet

EnumSet 是一个枚举集合,是一个抽象类,它有两个继承类:JumboEnumSet和 RegularEnumSet。EnumSet 使用 bit 位存储数据,效率高于 HashSet。

从 JumboEnumSet 的 add 方法可以一窥 EnumSet 的实现原理。首先把枚举值的下标值无符号右移 6 位,也就是按照 64 位进行分组,找到当前分组的数值。然后,按照枚举值的下标值进行左移,找到添加的位置,把该位置置为 1。同样地,EnumSet 的容量也不能发生变化,枚举类型的定义决定了 EnumSet 的固定容量值。

关于枚举就讨论到这里,欢迎留言讨论。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
enum使用详解
深入理解Java枚举类型(enum)
Java 语言中 Enum 类型的使用介绍
Java枚举(enum)详解:Java声明枚举类型、枚举(enum)类、EnumMap 与 EnumSet
掌握Java枚举这几个知识点,日常开发就够啦
说说Java中的枚举(一)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服