本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到个人仓库里查看html
https://github.com/h2pl/Java-Tutorial前端
喜欢的话麻烦点下Star、Fork、Watch三连哈,感谢你的支持。java
文章首发于个人我的博客:git
本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇,本文部份内容来源于网络,为了把本文主题讲得清晰透彻,也整合了不少我认为不错的技术博客内容,引用其中了一些比较好的博客文章,若有侵权,请联系做者。github
该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每一个Java知识点背后的实现原理,更完整地了解整个Java技术体系,造成本身的知识框架。为了更好地总结和检验你的学习成果,本系列文章也会提供每一个知识点对应的面试题以及参考答案。面试
@[toc] 若是对本系列文章有什么建议,或者是有什么疑问的话,也能够关注公众号【Java技术江湖】联系做者,欢迎你参与本系列博文的创做和修订。算法
<!-- more -->spring
枚举(enum)类型是Java 5新增的特性,它是一种新的类型,容许用常量来表示特定的数据片段,并且所有都以类型安全的形式来表示。segmentfault
在程序设计中,有时会用到由若干个有限数据元素组成的集合,如一周内的星期一到星期日七个数据元素组成的集合,由三种颜色红、黄、绿组成的集合,一个工做班组内十个职工组成的集合等等,程序中某个变量取值仅限于集合中的元素。此时,可将这些数据集合定义为枚举类型。
所以,枚举类型是某类数据可能取值的集合,如一周内星期可能取值的集合为: { Sun,Mon,Tue,Wed,Thu,Fri,Sat} 该集合可定义为描述星期的枚举类型,该枚举类型共有七个元素,于是用枚举类型定义的枚举变量只能取集合中的某一元素值。因为枚举类型是导出数据类型,所以,必须先定义枚举类型,而后再用枚举类型定义枚举型变量。
enum <枚举类型名> { <枚举元素表> }; 其中:关键词enum表示定义的是枚举类型,枚举类型名由标识符组成,而枚举元素表由枚举元素或枚举常量组成。例如: enum weekdays { Sun,Mon,Tue,Wed,Thu,Fri,Sat }; 定义了一个名为 weekdays的枚举类型,它包含七个元素:Sun、Mon、Tue、Wed、Thu、Fri、Sat。
在编译器编译程序时,给枚举类型中的每个元素指定一个整型常量值(也称为序号值)。若枚举类型定义中没有指定元素的整型常量值,则整型常量值从0开始依次递增,所以,weekdays枚举类型的七个元素Sun、Mon、Tue、Wed、Thu、Fri、Sat对应的整型常量值分别为0、一、二、三、四、五、6。 注意:在定义枚举类型时,也可指定元素对应的整型常量值。
例如,描述逻辑值集合{TRUE、FALSE}的枚举类型boolean可定义以下: enum boolean { TRUE=1 ,FALSE=0 }; 该定义规定:TRUE的值为1,而FALSE的值为0。 而描述颜色集合{red,blue,green,black,white,yellow}的枚举类型colors可定义以下: enum colors {red=5,blue=1,green,black,white,yellow}; 该定义规定red为5 ,blue为1,其后元素值从2 开始递增长1。green、black、white、yellow的值依次为二、三、四、5。
此时,整数5将用于表示二种颜色red与yellow。一般两个不一样元素取相同的整数值是没有意义的。枚举类型的定义只是定义了一个新的数据类型,只有用枚举类型定义枚举变量才能使用这种数据类型。
enum 与 class、interface 具备相同地位; 能够继承多个接口; 能够拥有构造器、成员方法、成员变量; 1.2 枚举类与普通类不一样之处
默认继承 java.lang.Enum 类,因此不能继承其余父类;其中 java.lang.Enum 类实现了 java.lang.Serializable 和 java.lang.Comparable 接口;
使用 enum 定义,默认使用 final 修饰,所以不能派生子类;
构造器默认使用 private 修饰,且只能使用 private 修饰;
枚举类全部实例必须在第一行给出,默认添加 public static final 修饰,不然没法产生实例;
这部份内容参考http://www.javashuo.com/article/p-poapgxnl-ka.html
public class 常量 { } enum Color { Red, Green, Blue, Yellow }
JDK1.6以前的switch语句只支持int,char,enum类型,使用枚举,能让咱们的代码可读性更强。
public static void showColor(Color color) { switch (color) { case Red: System.out.println(color); break; case Blue: System.out.println(color); break; case Yellow: System.out.println(color); break; case Green: System.out.println(color); break; } }
若是打算自定义本身的方法,那么必须在enum实例序列的最后添加一个分号。并且 Java 要求必须先定义 enum 实例。
enum Color { //每一个颜色都是枚举类的一个实例,而且构造方法要和枚举类的格式相符合。 //若是实例后面有其余内容,实例序列结束时要加分号。 Red("红色", 1), Green("绿色", 2), Blue("蓝色", 3), Yellow("黄色", 4); String name; int index; Color(String name, int index) { this.name = name; this.index = index; } public void showAllColors() { //values是Color实例的数组,在经过index和name能够获取对应的值。 for (Color color : Color.values()) { System.out.println(color.index + ":" + color.name); } } }
全部枚举类都继承自Enum类,因此能够重写该类的方法 下面给出一个toString()方法覆盖的例子。
@Override public String toString() { return this.index + ":" + this.name; }
全部的枚举都继承自java.lang.Enum类。因为Java 不支持多继承,因此枚举对象不能再继承其余类。
enum Color implements Print{ @Override public void print() { System.out.println(this.name); } }
搞个实现接口,来组织枚举,简单讲,就是分类吧。若是大量使用枚举的话,这么干,在写代码的时候,就很方便调用啦。
public class 用接口组织枚举 { public static void main(String[] args) { Food cf = chineseFood.dumpling; Food jf = Food.JapaneseFood.fishpiece; for (Food food : chineseFood.values()) { System.out.println(food); } for (Food food : Food.JapaneseFood.values()) { System.out.println(food); } } } interface Food { enum JapaneseFood implements Food { suse, fishpiece } } enum chineseFood implements Food { dumpling, tofu }
java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的 key是enum类型,而value则能够是任意类型。
EnumSet在JDK中没有找到实现类,这里写一个EnumMap的例子
public class 枚举类集合 { public static void main(String[] args) { EnumMap<Color, String> map = new EnumMap<Color, String>(Color.class); map.put(Color.Blue, "Blue"); map.put(Color.Yellow, "Yellow"); map.put(Color.Red, "Red"); System.out.println(map.get(Color.Red)); } }
枚举类型对象之间的值比较,是可使用==,直接来比较值,是否相等的,不是必须使用equals方法的哟。
由于枚举类Enum已经重写了equals方法
/** * Returns true if the specified object is equal to this * enum constant. * * @param other the object to be compared for equality with this object. * @return true if the specified object is equal to this * enum constant. */ public final boolean equals(Object other) { return this==other; }
这部分参考https://blog.csdn.net/mhmyqn/article/details/48087247
Java从JDK1.5开始支持枚举,也就是说,Java一开始是不支持枚举的,就像泛型同样,都是JDK1.5才加入的新特性。一般一个特性若是在一开始没有提供,在语言发展后期才添加,会遇到一个问题,就是向后兼容性的问题。
像Java在1.5中引入的不少特性,为了向后兼容,编译器会帮咱们写的源代码作不少事情,好比泛型为何会擦除类型,为何会生成桥接方法,foreach迭代,自动装箱/拆箱等,这有个术语叫“语法糖”,而编译器的特殊处理叫“解语法糖”。那么像枚举也是在JDK1.5中才引入的,又是怎么实现的呢?
Java在1.5中添加了java.lang.Enum抽象类,它是全部枚举类型基类。提供了一些基础属性和基础方法。同时,对把枚举用做Set和Map也提供了支持,即java.util.EnumSet和java.util.EnumMap。
接下来定义一个简单的枚举类
public enum Day { MONDAY { @Override void say() { System.out.println("MONDAY"); } } , TUESDAY { @Override void say() { System.out.println("TUESDAY"); } }, FRIDAY("work"){ @Override void say() { System.out.println("FRIDAY"); } }, SUNDAY("free"){ @Override void say() { System.out.println("SUNDAY"); } }; String work; //没有构造参数时,每一个实例能够看作常量。 //使用构造参数时,每一个实例都会变得不同,能够看作不一样的类型,因此编译后会生成实例个数对应的class。 private Day(String work) { this.work = work; } private Day() { } //枚举实例必须实现枚举类中的抽象方法 abstract void say (); }
反编译结果
D:\MyTech\out\production\MyTech\com\javase\枚举类>javap Day.class Compiled from "Day.java" public abstract class com.javase.枚举类.Day extends java.lang.Enum<com.javase.枚举类.Day> { public static final com.javase.枚举类.Day MONDAY; public static final com.javase.枚举类.Day TUESDAY; public static final com.javase.枚举类.Day FRIDAY; public static final com.javase.枚举类.Day SUNDAY; java.lang.String work; public static com.javase.枚举类.Day[] values(); public static com.javase.枚举类.Day valueOf(java.lang.String); abstract void say(); com.javase.枚举类.Day(java.lang.String, int, com.javase.枚举类.Day$1); com.javase.枚举类.Day(java.lang.String, int, java.lang.String, com.javase.枚举类.Day$1); static {}; }
能够看到,一个枚举在通过编译器编译事后,变成了一个抽象类,它继承了java.lang.Enum;而枚举中定义的枚举常量,变成了相应的public static final属性,并且其类型就抽象类的类型,名字就是枚举常量的名字.
同时咱们能够在Operator.class的相同路径下看到四个内部类的.class文件com/mikan/Day$1.class、com/mikan/Day$2.class、com/mikan/Day$3.class、com/mikan/Day$4.class,也就是说这四个命名字段分别使用了内部类来实现的;同时添加了两个方法values()和valueOf(String);咱们定义的构造方法原本只有一个参数,但却变成了三个参数;同时还生成了一个静态代码块。这些具体的内容接下来仔细看看。
下面分析一下字节码中的各部分,其中:
InnerClasses: static #23; //class com/javase/枚举类/Day$4 static #18; //class com/javase/枚举类/Day$3 static #14; //class com/javase/枚举类/Day$2 static #10; //class com/javase/枚举类/Day$1
从中能够看到它有4个内部类,这四个内部类的详细信息后面会分析。
static {}; descriptor: ()V flags: ACC_STATIC Code: stack=5, locals=0, args_size=0 0: new #10 // class com/javase/枚举类/Day$1 3: dup 4: ldc #11 // String MONDAY 6: iconst_0 7: invokespecial #12 // Method com/javase/枚举类/Day$1."<init>":(Ljava/lang/String;I)V 10: putstatic #13 // Field MONDAY:Lcom/javase/枚举类/Day; 13: new #14 // class com/javase/枚举类/Day$2 16: dup 17: ldc #15 // String TUESDAY 19: iconst_1 20: invokespecial #16 // Method com/javase/枚举类/Day$2."<init>":(Ljava/lang/String;I)V //后面相似,这里省略 }
其实编译器生成的这个静态代码块作了以下工做:分别设置生成的四个公共静态常量字段的值,同时编译器还生成了一个静态字段$VALUES,保存的是枚举类型定义的全部枚举常量 编译器添加的values方法:
public static com.javase.Day[] values(); flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: getstatic #2 // Field $VALUES:[Lcom/javase/Day; 3: invokevirtual #3 // Method "[Lcom/mikan/Day;".clone:()Ljava/lang/Object; 6: checkcast #4 // class "[Lcom/javase/Day;" 9: areturn 这个方法是一个公共的静态方法,因此咱们能够直接调用该方法(Day.values()),返回这个枚举值的数组,另外,这个方法的实现是,克隆在静态代码块中初始化的$VALUES字段的值,并把类型强转成Day[]类型返回。
造方法为何增长了两个参数?
有一个问题,构造方法咱们明明只定义了一个参数,为何生成的构造方法是三个参数呢?
从Enum类中咱们能够看到,为每一个枚举都定义了两个属性,name和ordinal,name表示咱们定义的枚举常量的名称,如FRIDAY、TUESDAY,而ordinal是一个顺序号,根据定义的顺序分别赋予一个整形值,从0开始。在枚举常量初始化时,会自动为初始化这两个字段,设置相应的值,因此才在构造方法中添加了两个参数。即: 另外三个枚举常量生成的内部类基本上差很少,这里就不重复说明了。
咱们能够从Enum类的代码中看到,定义的name和ordinal属性都是final的,并且大部分方法也都是final的,特别是clone、readObject、writeObject这三个方法,这三个方法和枚举经过静态代码块来进行初始化一块儿。
它保证了枚举类型的不可变性,不能经过克隆,不能经过序列化和反序列化来复制枚举,这能保证一个枚举常量只是一个实例,便是单例的,因此在effective java中推荐使用枚举来实现单例。
(1)定义一个无参枚举类
enum SeasonType { SPRING, SUMMER, AUTUMN, WINTER }
(2)实战中的使用
// 根据实际状况选择下面的用法便可 SeasonType springType = SeasonType.SPRING; // 输出 SPRING String springString = SeasonType.SPRING.toString(); // 输出 SPRING
(1)定义只有一个参数的枚举类
enum SeasonType { // 经过构造函数传递参数并建立实例 SPRING("spring"), SUMMER("summer"), AUTUMN("autumn"), WINTER("winter"); // 定义实例对应的参数 private String msg; // 必写:经过此构造器给枚举值建立实例 SeasonType(String msg) { this.msg = msg; } // 经过此方法能够获取到对应实例的参数值 public String getMsg() { return msg; } }
(2)实战中的使用
// 当咱们为某个实例类赋值的时候可以使用以下方式 String msg = SeasonType.SPRING.getMsg(); // 输出 spring
(1)定义有两个参数的枚举类
public enum Season { // 经过构造函数传递参数并建立实例 SPRING(1, "spring"), SUMMER(2, "summer"), AUTUMN(3, "autumn"), WINTER(4, "winter"); // 定义实例对应的参数 private Integer key; private String msg; // 必写:经过此构造器给枚举值建立实例 Season(Integer key, String msg) { this.key = key; this.msg = msg; } // 不少状况,咱们可能从前端拿到的值是枚举类的 key ,而后就能够经过如下静态方法获取到对应枚举值 public static Season valueofKey(Integer key) { for (Season season : Season.values()) { if (season.key.equals(key)) { return season; } } throw new IllegalArgumentException("No element matches " + key); } // 经过此方法能够获取到对应实例的 key 值 public Integer getKey() { return key; } // 经过此方法能够获取到对应实例的 msg 值 public String getMsg() { return msg; } }
(2)实战中的使用
// 输出 key 为 1 的枚举值实例 Season season = Season.valueofKey(1); // 输出 SPRING 实例对应的 key Integer key = Season.SPRING.getKey(); // 输出 SPRING 实例对应的 msg String msg = Season.SPRING.getMsg();
其实枚举类懂了其概念后,枚举就变得至关简单了,随手就能够写一个枚举类出来。因此如上几个实战小例子必定要先搞清楚概念,而后在练习几遍就 ok 了。
重要的概念,我在这里在赘述一遍,帮助老铁们快速掌握这块知识,首先记住,枚举类中的枚举值能够没有参数,也能够有多个参数,每个枚举值都是一个实例;
而且还有一点很重要,就是若是枚举值有 n 个参数,那么构造函数中的参数值确定有 n 个,由于声明的每个枚举值都会调用构造函数去建立实例,因此参数必定是一一对应的;既然明白了这一点,那么咱们只须要在枚举类中把这 n 个参数定义为 n 个成员变量,而后提供对应的 get() 方法,以后经过实例就能够随意的获取实例中的任意参数值了。
若是想让枚举类更加的好用,就能够模仿我在实战三中的写法那样,经过某一个参数值,好比 key 参数值,就能获取到其对应的枚举值,而后想要什么值,就 get 什么值就行了。
咱们使用 enum 定义的枚举类都是继承 java.lang.Enum 类的,那么就会继承其 API ,经常使用的 API 以下:
获取枚举名称
获取枚举的位置(下标,初始值为 0 )
经过 msg 获取其对应的枚举类型。(好比实战二中的枚举类或其它枚举类都行,只要使用得当均可以使用此方法)
获取枚举类中的全部枚举值(好比在实战三中就使用到了)
枚举本质上是经过普通的类来实现的,只是编译器为咱们进行了处理。每一个枚举类型都继承自java.lang.Enum,并自动添加了values和valueOf方法。
而每一个枚举常量是一个静态常量字段,使用内部类实现,该内部类继承了枚举类。全部枚举常量都经过静态代码块来进行初始化,即在类加载期间就初始化。
另外经过把clone、readObject、writeObject这三个方法定义为final的,同时实现是抛出相应的异常。这样保证了每一个枚举类型及枚举常量都是不可变的。能够利用枚举的这两个特性来实现线程安全的单例。
https://blog.csdn.net/qq_34988624/article/details/86592229 https://www.meiwen.com.cn/subject/slhvhqtx.html https://blog.csdn.net/qq_34988624/article/details/86592229 http://www.javashuo.com/article/p-rwfrbdcr-hy.html https://my.oschina.net/wuxinshui/blog/1511484 https://blog.csdn.net/hukailee/article/details/81107412
若是你们想要实时关注我更新的文章以及分享的干货的话,能够关注个人公众号【Java技术江湖】一位阿里 Java 工程师的技术小站,做者黄小斜,专一 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!
Java工程师必备学习资源: 一些Java工程师经常使用学习资源,关注公众号后,后台回复关键字 “Java” 便可免费无套路获取。
做者是 985 硕士,蚂蚁金服 JAVA 工程师,专一于 JAVA 后端技术栈:SpringBoot、MySQL、分布式、中间件、微服务,同时也懂点投资理财,偶尔讲点算法和计算机理论基础,坚持学习和写做,相信终身学习的力量!
程序员3T技术学习资源: 一些程序员学习技术的资源大礼包,关注公众号后,后台回复关键字 “资料” 便可免费无套路获取。