目录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
至于结果为啥是这个,且看我慢慢总结。数据结构
存在即合理。this
我贼喜欢这句圣经,每次我一解释不了它为何出现的时候,就不自觉地用上这句话。.net
枚举必定有他存在的价值,在一些时候,咱们须要定义一个类,这个类中的对象是有限且固定的,好比咱们一年有四个季节,春夏秋冬。code
在枚举被支持以前,咱们该如何定义这个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类能够完成咱们的预期,它们各自表明一个实例,且不能被改变,外部也不能随便建立实例。blog
但,经过自定义类实现枚举的效果有个显著的问题:代码量很是大。
因而,JDK1.5,枚举类应运而生。
enum
关键字用以定义枚举类,这是一个和class
,interface
关键字地位至关的关键字。也就是说,枚举类和咱们以前使用的类差不太多,且enum和class修饰的类若是同名,会出错。
有一部分规则,类须要遵循的,枚举类也遵循:
也有一部分规则,枚举类显得不同凡响:
public static final
修饰。枚举类.枚举常量
的方式调用。知道这些以后,咱们能够用enum关键字从新定义枚举类:
public enum Season{ //定义四个实例 SPRING,SUMMER,AUTUMN,WINTER; }
须要注意的是,在JDK1.5枚举类加入以后,switch-case语句进行了扩展,其控制表达式能够是任意枚举类型,且能够直接使用枚举值的名称,无需添加枚举类做为限定。
Enum类是全部enum关键字修饰的枚举类的顶级父类,里头定义的方法默认状况下,是通用的,咱们来瞅它一瞅:
public abstract class Enum<E extends Enum<E>> extends Object implements Comparable<E>, Serializable
咱们能够发现,Enum实际上是一个继承自Object类的抽象类(Object类果真是顶级父类,不可撼动),并实现了两个接口:
protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; }
官方文档这样说的:程序员不能去调用这个构造器,它用于编译器响应enum类型声明发出的代码,关于这一点,咱们后面体会会更加深入一些。
关于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)
该静态方法返回指定枚举类中指定名称的枚举常量。
为何会想到总结这个方法呢?其实也是有必定心路历程的,官方文档特别强调了一句话:
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; } }
这部分等到之后总结单例模式再侃,先在文末贴个地址。