一、前言

本篇博客是对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关键字用以定义枚举类,这是一个和classinterface关键字地位相当的关键字。也就是说,枚举类和我们之前使用的类差不太多,且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:枚举类也就这么回事的更多相关文章

  1. Java枚举类在生产环境中的使用方式

    前言   Java枚举在项目中使用非常普遍,许多人在做项目时,一定会遇到要维护某些业务场景状态的时候,往往会定义一个常量类,然后添加业务场景相关的状态常量.但实际上,生产环境的项目中业务状态的定义大部 ...

  2. Java 枚举类

    如果要定义一个枚举类: public enum Size { SAMLL, MEDIUM, LARGE, EXTRA, EXTRA_LARGE}; 实际上,这个声明定义的类型是一个类,它刚好有4个实例 ...

  3. java 枚举类 enum 总结

    枚举定义: enum是计算机编程语言中的一种数据类型.枚举类型:在实际问题中,有些变量的取值被限定在一个有限的范围内.例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等.如果把这些量 ...

  4. java 枚举类小结 Enum

    好久没有接触枚举类了,差不多都忘了,今天抽出个时间总结一下吧.说实话,枚举类确实能够给我们带来很大的方便. 说明:枚举类它约定了一个范围,可以理解成只可以生成固定的几个对象让外界去调用,故枚举类中的构 ...

  5. java枚举类

    enum关键字用于定义枚举类,若枚举只有一个成员, 则可以作为一种单例模式的实现方式.   枚举类对象的属性不应允许被改动, 所以应该使用 private final 修饰. 枚举类的使用 priva ...

  6. 【JAVA】浅谈java枚举类

    一.什么情况下使用枚举类? 有的时候一个类的对象是有限且固定的,这种情况下我们使用枚举类就比较方便? 二.为什么不用静态常量来替代枚举类呢? public static final int SEASO ...

  7. Java枚举类enum

    枚举类enum是JDK1.5引入的,之前都是用public static final int enum_value来代替枚举类的.枚举类enum是一种特殊的类,它默认继承了类java.lang.Enu ...

  8. java枚举类(enum) 基础知识讲解

    枚举类是在java 5后新增的,可以用于封装常量,并且还可以为常量的使用提供一些方法. 定义枚举类的语法: public enum EnumName{ 成员1(A,B...),成员2(A,B...), ...

  9. Java枚举类使用

    用法一:常量 在JDK1.5 之前,我们定义常量都是: public static fianl.... .现在好了,有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法. p ...

随机推荐

  1. Ceph日常运维管理和排错 -- <7>

    Ceph日常运维管理 集群监控管理 集群整体运行状态 [root@cephnode01 ~]# ceph -s cluster: id: 8230a918-a0de-4784-9ab8-cd2a2b8 ...

  2. sg函数的变形 - 可以将一堆石子分开

    Nim is a two-player mathematic game of strategy in which players take turns removing objects from di ...

  3. spring Cloud-eureka的保护模式

    eureka的首页出现以下警告 EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. REN ...

  4. Qt Installer Framework翻译(5-2)

    创建在线安装程序 联机安装程序获取二进制安装文件中的内容以及存储库描述(Updates.xml).请创建一个存储库,并将其上传到Web服务器.然后在用于创建安装程序的config.xml文件中指定存储 ...

  5. CSS基础应用总结

    目录 CSS 样式笔记 文字水平居中和垂直居中 如何设置a标签不带下划线 控件右对齐 div上下居中 控件左右居中 控件展示在同一行 设置文字超出部分...显示 CSS 样式笔记 文字水平居中和垂直居 ...

  6. 阿里fastjson解析

    解析案例 String object="{total=1, rows=[{_Account=3646808, UserID=131514, Mt4Name=SewwoaIQQS, Serve ...

  7. Python环境搭建(win)——Python官方解释器

    Python官方解释器搭建方法: 本文以当前最新的3.8.1为例 1.在电脑上打开Python的官网https://www.python.org/ 2.找到Download下的All releases ...

  8. Sublime Text 3 部分安装过程记录

    概览: Sublime Text 3下载网址 Package Control的安装 Install Package报错(There are no packages availabel for inst ...

  9. from .cv2 import * ImportError: DLL load failed: 找不到指定的模块。 >>>

    from .cv2 import * ImportError: DLL load failed: 找不到指定的模块. >>> 昨天看项目的时候遇到这个问题,折腾到深夜,网上的各种方法 ...

  10. ios---CoreLocation框架实现定位功能

    CoreLocation框架实现定位功能(iOS8.0之后) // // ViewController.m // 定位 // // Created by admin on 2017/9/20. // ...