Java:枚举类也就这么回事
一、前言
本篇博客是对JDK1.5的新特性枚举的一波小小的总结,主要是昨天在看一部分面试题的时候,遇到了枚举类型的题目,发现自己有许多细节还需要加强,做起来都模棱两可,是时候总结一波了。
二、源自一道面试题
不多bb,直接开门见山,我遇到这样一道也许很简单的题目:
enum AccountType
{
SAVING, FIXED, CURRENT;
private AccountType()
{
System.out.println(“It is a account type”);
}
}
class EnumOne
{
public static void main(String[]args)
{
System.out.println(AccountType.FIXED);
}
}
问打印的结果是啥?正确答案如下:
It is a account type
It is a account type
It is a account type
FIXED
至于结果为啥是这个,且看我慢慢总结。
三、枚举的由来
存在即合理。
我贼喜欢这句圣经,每次我一解释不了它为什么出现的时候,就不自觉地用上这句话。
枚举一定有他存在的价值,在一些时候,我们需要定义一个类,这个类中的对象是有限且固定的,比如我们一年有四个季节,春夏秋冬。
在枚举被支持之前,我们该如何定义这个Season类呢?可能会像下面这样:
public class Season {
//private修饰构造器,无法随意创建对象
private Season(){}
//final修饰提供的对象在类外不能改变
public static final Season SPRING = new Season();
public static final Season SUMMER = new Season();
public static final Season AUTUMN = new Season();
public static final Season WINTER = new Season();
}
在定义上,这个Season类可以完成我们的预期,它们各自代表一个实例,且不能被改变,外部也不能随便创建实例。
但,通过自定义类实现枚举的效果有个显著的问题:代码量非常大。
于是,JDK1.5,枚举类应运而生。
四、枚举的定义形式
enum
关键字用以定义枚举类,这是一个和class
,interface
关键字地位相当的关键字。也就是说,枚举类和我们之前使用的类差不太多,且enum和class修饰的类如果同名,会出错。
有一部分规则,类需要遵循的,枚举类也遵循:
- 枚举类也可以定义成员变量、构造器、普通和抽象方法等。
- 一个Java源文件最多只能定义一个public的枚举类,且类名与文件名相同。
- 枚举类可以实现一个或多个接口。
也有一部分规则,枚举类显得与众不同:
- 枚举类的实例必须在枚举类的第一行显式列出,以逗号分隔,列出的实例系统默认添加
public static final
修饰。 - 枚举类的构造器默认私有,且只能是私有,可以重载。
- 枚举类默认final修饰,无法被继承。
- 枚举类都继承了java.lang.Enum类,所以无法继承其他的类。
- 一般情况下,枚举常量需要用
枚举类.枚举常量
的方式调用。
知道这些之后,我们可以用enum关键字重新定义枚举类:
public enum Season{
//定义四个实例
SPRING,SUMMER,AUTUMN,WINTER;
}
需要注意的是,在JDK1.5枚举类加入之后,switch-case语句进行了扩展,其控制表达式可以是任意枚举类型,且可以直接使用枚举值的名称,无需添加枚举类作为限定。
五、Enum类里有啥?
Enum类是所有enum关键字修饰的枚举类的顶级父类,里头定义的方法默认情况下,是通用的,我们来瞅它一瞅:
public abstract class Enum<E extends Enum<E>> extends Object implements Comparable<E>, Serializable
我们可以发现,Enum其实是一个继承自Object类的抽象类(Object类果然是顶级父类,不可撼动),并实现了两个接口:
- Comparable:支持枚举对象的比较。
- Serializable:支持枚举对象的序列化。
1、唯一的构造器
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
官方文档这样说的:程序员不能去调用这个构造器,它用于编译器响应enum类型声明发出的代码,关于这一点,我们后面体会会更加深刻一些。
2、重要的方法们
关于Object类中的方法,这边就不赘述了,主要提一提特殊的方法。
public final String name()
返回这个枚举常量的名称。官方建议:大多数情况,最好使用toString()方法,因为可以返回一个友好的名字。而name()方法以final修饰,无法被重写。
public String toString()
源码上看,toString()方法和name()方法是相同的,但是建议:如果有更友好的常量名称显示,可以重写toString()方法。
public final int ordinal()
返回此枚举常量的序号(其在enum声明中的位置,其中初始常量的序号为零)。
大多数程序员将不需要这种方法。它被用于复杂的基于枚举的数据结构中,如EnumSet和EnumMap。
public final int compareTo(E o)
这个方法用于指定枚举对象比较顺序,同一个枚举实例只能与相同类型的枚举实例进行比较。
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
//getDeclaringClass()方法返回该枚举常量对应Enum类的类对象
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
//该枚举常量顺序在o常量之前,返回负整数
return self.ordinal - other.ordinal;
}
public static <T extends Enum> T valueOf(Class enumType,
String name)
该静态方法返回指定枚举类中指定名称的枚举常量。
3、凭空出现的values()方法
为什么会想到总结这个方法呢?其实也是有一定心路历程的,官方文档特别强调了一句话:
Note that when using an enumeration type as the type of a set or as the type of the keys in a map, specialized and efficient set and map implementations are available.
一般Note开头的玩意儿,还是比较重要的。大致意思如下:
当使用枚举类型作为集合的类型或映射中的键的类型时,可以使用专门化且有效的集合和映射实现。
看完非常不理解,于是开始查找资料,发现有一种用法:
Arrays.asList(AccountType.values())
很明显调用了这个枚举类的values()方法,但是刚才对枚举类的方法一通分析,也没看到有values()方法啊。但是编译器确实提示,有,确实有!
这是怎么回事呢?JDK文档是这么说的:
The compiler automatically adds some special methods when it creates an enum. For example, they have a static values method that returns an array containing all of the values of the enum in the order they are declared.
编译器会在创建一个枚举类的时候,自动在里面加入一些特殊的方法,例如静态的values()方法,它将返回一个数组,按照枚举常量声明的顺序存放它们。
这样一来,枚举类就可以和集合等玩意儿很好地配合在一起了,具体咋配合,以后遇到了就知道了。
关于这一点,待会反编译之后会更加印象深刻。
六、反编译枚举类
注:由于学识尚浅,这部分内容总结起来虚虚的,但是总归查找了许多的资料,如有说的不对的地方,还望评论区批评指正。
那么,回到我们文章开头提到的那到面试题,我们根据结果来推测程序运行之后发生的情况:
- 其中的构造器被调用了三次,说明定义的枚举常量确实是三个活生生的实例,也就是说,每次创建实例就会调用一次构造器。
- 然后,
System.out.println(AccountType.FIXED);
将会调用toString()方法,由于子类没有重写,那么将会返回name值,也就是"FIXED"
。
至此,我们的猜测结束,其实确实也大差不差了,大致就是这个过程。在一番查阅资料之后,我又尝试着去反编译这个枚举类文件:
我们先用javap -p AccountType.class
命令试着反编译之后查看所有类和成员。
为了看看static中发生的情况,我试着用更加详细的指令,javap -c -l AccountType.class
,试图获取本地变量信息表和行号,虽然我大概率还是看不太懂的。
我们以其中一个为例,参看虚拟机字节码指令表,大致过程如下:
static {};
Code:
0: new #4 //创建一个对象,将其引用值压入栈
3: dup //复制栈顶数值并将复制值压入栈顶
4: ldc #10 //将String常量值SAVING从常量池推送至栈顶
6: iconst_0 //将int型0推送至栈顶
7: invokespecial #11 //调用超类构造器
10: putstatic #12 //为指定的静态域赋值
以下为由个人理解简化的编译结构:
public final class AccountType extends java.lang.Enum<AccountType> {
//静态枚举常量
public static final AccountType SAVING;
public static final AccountType FIXED;
public static final AccountType CURRENT;
//存储静态枚举常量的私有静态域
private static final AccountType[] $VALUES;
//编译器新加入的静态方法
public static AccountType[] values();
//调用实例方法获取指定名称的枚举常量
public static AccountType valueOf(java.lang.String);
static {
//创建对象,传入枚举常量名和顺序
SAVING = new AccountType("SAVING",0);
FIXED = new AccountType("FIXED",1);
CURRENT = new AccountType("CURRENT",2);
//给静态域赋值
$VALUES = new AccountType[]{
SAVING,FIXED,CURRENT
}
};
}
Enum类的构造器,在感应到enum关键字修饰的类之后,将会被调用,传入枚举常量的字符串字面量值(name)和索引(ordinal),创建的实例存在私有静态域&VALUES
中。
而且编译器确实会添加静态的values()方法,用以返回存放枚举常量的数组。
七、枚举类实现单例
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
这部分等到以后总结单例模式再侃,先在文末贴个地址。
八、参考资料
通过javap命令分析java汇编指令
Java中的枚举与values()方法
Java:枚举类也就这么回事的更多相关文章
- Java枚举类在生产环境中的使用方式
前言 Java枚举在项目中使用非常普遍,许多人在做项目时,一定会遇到要维护某些业务场景状态的时候,往往会定义一个常量类,然后添加业务场景相关的状态常量.但实际上,生产环境的项目中业务状态的定义大部 ...
- Java 枚举类
如果要定义一个枚举类: public enum Size { SAMLL, MEDIUM, LARGE, EXTRA, EXTRA_LARGE}; 实际上,这个声明定义的类型是一个类,它刚好有4个实例 ...
- java 枚举类 enum 总结
枚举定义: enum是计算机编程语言中的一种数据类型.枚举类型:在实际问题中,有些变量的取值被限定在一个有限的范围内.例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等.如果把这些量 ...
- java 枚举类小结 Enum
好久没有接触枚举类了,差不多都忘了,今天抽出个时间总结一下吧.说实话,枚举类确实能够给我们带来很大的方便. 说明:枚举类它约定了一个范围,可以理解成只可以生成固定的几个对象让外界去调用,故枚举类中的构 ...
- java枚举类
enum关键字用于定义枚举类,若枚举只有一个成员, 则可以作为一种单例模式的实现方式. 枚举类对象的属性不应允许被改动, 所以应该使用 private final 修饰. 枚举类的使用 priva ...
- 【JAVA】浅谈java枚举类
一.什么情况下使用枚举类? 有的时候一个类的对象是有限且固定的,这种情况下我们使用枚举类就比较方便? 二.为什么不用静态常量来替代枚举类呢? public static final int SEASO ...
- Java枚举类enum
枚举类enum是JDK1.5引入的,之前都是用public static final int enum_value来代替枚举类的.枚举类enum是一种特殊的类,它默认继承了类java.lang.Enu ...
- java枚举类(enum) 基础知识讲解
枚举类是在java 5后新增的,可以用于封装常量,并且还可以为常量的使用提供一些方法. 定义枚举类的语法: public enum EnumName{ 成员1(A,B...),成员2(A,B...), ...
- Java枚举类使用
用法一:常量 在JDK1.5 之前,我们定义常量都是: public static fianl.... .现在好了,有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法. p ...
随机推荐
- Ceph日常运维管理和排错 -- <7>
Ceph日常运维管理 集群监控管理 集群整体运行状态 [root@cephnode01 ~]# ceph -s cluster: id: 8230a918-a0de-4784-9ab8-cd2a2b8 ...
- sg函数的变形 - 可以将一堆石子分开
Nim is a two-player mathematic game of strategy in which players take turns removing objects from di ...
- spring Cloud-eureka的保护模式
eureka的首页出现以下警告 EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. REN ...
- Qt Installer Framework翻译(5-2)
创建在线安装程序 联机安装程序获取二进制安装文件中的内容以及存储库描述(Updates.xml).请创建一个存储库,并将其上传到Web服务器.然后在用于创建安装程序的config.xml文件中指定存储 ...
- CSS基础应用总结
目录 CSS 样式笔记 文字水平居中和垂直居中 如何设置a标签不带下划线 控件右对齐 div上下居中 控件左右居中 控件展示在同一行 设置文字超出部分...显示 CSS 样式笔记 文字水平居中和垂直居 ...
- 阿里fastjson解析
解析案例 String object="{total=1, rows=[{_Account=3646808, UserID=131514, Mt4Name=SewwoaIQQS, Serve ...
- Python环境搭建(win)——Python官方解释器
Python官方解释器搭建方法: 本文以当前最新的3.8.1为例 1.在电脑上打开Python的官网https://www.python.org/ 2.找到Download下的All releases ...
- Sublime Text 3 部分安装过程记录
概览: Sublime Text 3下载网址 Package Control的安装 Install Package报错(There are no packages availabel for inst ...
- from .cv2 import * ImportError: DLL load failed: 找不到指定的模块。 >>>
from .cv2 import * ImportError: DLL load failed: 找不到指定的模块. >>> 昨天看项目的时候遇到这个问题,折腾到深夜,网上的各种方法 ...
- ios---CoreLocation框架实现定位功能
CoreLocation框架实现定位功能(iOS8.0之后) // // ViewController.m // 定位 // // Created by admin on 2017/9/20. // ...