文章目录
枚举类定义
//枚举类型,使用关键字enum
enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
在定义枚举类型时我们使用的关键字是enum,与class关键字类似,只不过前者是定义枚举类型,后者是定义类类型。
注意:值一般是大写的字母,多个值之间以逗号分隔。同时我们应该知道的是枚举类型可以像类(class)类型一样,定义为一个单独的文件,当然也可以定义在其他类内部,更重要的是枚举常量在类型安全性和便捷性都很有保证,如果出现类型问题编译器也会提示我们改进,但务必记住枚举表示的类型其取值是必须有限的,也就是说每个值都是可以枚举出来的。
枚举实现原理
实际上在使用关键字enum创建枚举类型并编译后,编译器会为我们生成一个相关的类,这个类继承了Java API中的java.lang.Enum类,也就是说通过关键字enum创建枚举类型在编译后事实上也是一个类类型而且该类继承自java.lang.Enum类。
查看上面枚举类Day编译后的class文件:
//反编译Day.class
final class Day extends Enum
{
//编译器为我们添加的静态的values()方法
public static Day[] values()
{
return (Day[])$VALUES.clone();
}
//编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf方法
public static Day valueOf(String s)
{
return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s);
}
//私有构造函数
private Day(String s, int i)
{
super(s, i);
}
//前面定义的7种枚举实例
public static final Day MONDAY;
public static final Day TUESDAY;
public static final Day WEDNESDAY;
public static final Day THURSDAY;
public static final Day FRIDAY;
public static final Day SATURDAY;
public static final Day SUNDAY;
private static final Day $VALUES[];
static
{
//实例化枚举实例
MONDAY = new Day("MONDAY", 0);
TUESDAY = new Day("TUESDAY", 1);
WEDNESDAY = new Day("WEDNESDAY", 2);
THURSDAY = new Day("THURSDAY", 3);
FRIDAY = new Day("FRIDAY", 4);
SATURDAY = new Day("SATURDAY", 5);
SUNDAY = new Day("SUNDAY", 6);
$VALUES = (new Day[] {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
});
}
}
从反编译的代码可以看出编译器确实帮助我们生成了一个Day类(注意该类是final类型的,将无法被继承)而且该类继承自java.lang.Enum类,该类是一个抽象类(稍后我们会分析该类中的主要方法),除此之外,编译器还帮助我们生成了7个Day类型的实例对象分别对应枚举中定义的7个日期,这也充分说明了我们前面使用关键字enum定义的Day类型中的每种日期枚举常量也是实实在在的Day实例对象,只不过代表的内容不一样而已。
注意编译器还为我们生成了两个静态方法,分别是values()和 valueOf(),稍后会分析它们的用法,到此我们也就明白了,使用关键字enum定义的枚举类型,在编译期后,也将转换成为一个实实在在的类,而在该类中,会存在每个在枚举类型中定义好变量的对应实例对象,如上述的MONDAY枚举类型对应public static final Day MONDAY;,同时编译器会为该类创建两个方法,分别是values()和valueOf()。
常见方法
Enum是所有 Java 语言枚举类型的公共基本类(注意Enum是抽象类),以下是它的常见方法:
- ordinal()方法,该方法获取的是枚举变量在枚举类中声明的顺序,下标从0开始,如日期中的MONDAY在第一个位置,那么MONDAY的ordinal值就是0,如果MONDAY的声明位置发生变化,那么ordinal方法获取到的值也随之变化,注意在大多数情况下我们都不应该首先使用该方法,毕竟它总是变幻莫测的。
- compareTo(E o)方法则是比较枚举的大小,注意其内部实现是根据每个枚举的ordinal值大小进行比较的。
- name()方法与toString()几乎是等同的,都是输出变量的字符串形式。
- 至于valueOf(Class enumType, String name)方法则是根据枚举类的Class对象和枚举名称获取枚举常量,注意该方法是静态的
public class EnumDemo {
public static void main(String[] args){
//创建枚举数组
Day[] days=new Day[]{Day.MONDAY, Day.TUESDAY, Day.WEDNESDAY,
Day.THURSDAY, Day.FRIDAY, Day.SATURDAY, Day.SUNDAY};
for (int i = 0; i <days.length ; i++) {
System.out.println("day["+i+"].ordinal():"+days[i].ordinal());
}
System.out.println("-------------------------------------");
//通过compareTo方法比较,实际上其内部是通过ordinal()值比较的
System.out.println("days[0].compareTo(days[1]):"+days[0].compareTo(days[1]));
System.out.println("days[0].compareTo(days[1]):"+days[0].compareTo(days[2]));
//获取该枚举对象的Class对象引用,当然也可以通过getClass方法
Class<?> clazz = days[0].getDeclaringClass();
System.out.println("clazz:"+clazz);
System.out.println("-------------------------------------");
//name()
System.out.println("days[0].name():"+days[0].name());
System.out.println("days[1].name():"+days[1].name());
System.out.println("days[2].name():"+days[2].name());
System.out.println("days[3].name():"+days[3].name());
System.out.println("-------------------------------------");
System.out.println("days[0].toString():"+days[0].toString());
System.out.println("days[1].toString():"+days[1].toString());
System.out.println("days[2].toString():"+days[2].toString());
System.out.println("days[3].toString():"+days[3].toString());
System.out.println("-------------------------------------");
Day d=Enum.valueOf(Day.class,days[0].name());
Day d2=Day.valueOf(Day.class,days[0].name());
System.out.println("d:"+d);
System.out.println("d2:"+d2);
}
/**
执行结果:
day[0].ordinal():0
day[1].ordinal():1
day[2].ordinal():2
day[3].ordinal():3
day[4].ordinal():4
day[5].ordinal():5
day[6].ordinal():6
-------------------------------------
days[0].compareTo(days[1]):-1
days[0].compareTo(days[1]):-2
clazz:class com.zejian.enumdemo.Day
-------------------------------------
days[0].name():MONDAY
days[1].name():TUESDAY
days[2].name():WEDNESDAY
days[3].name():THURSDAY
-------------------------------------
days[0].toString():MONDAY
days[1].toString():TUESDAY
days[2].toString():WEDNESDAY
days[3].toString():THURSDAY
-------------------------------------
d:MONDAY
d2:MONDAY
*/
}
enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
Enum类的主要源码
//实现了Comparable
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
private final String name; //枚举字符串名称
public final String name() {
return name;
}
private final int ordinal;//枚举顺序值
public final int ordinal() {
return ordinal;
}
//枚举的构造方法,只能由编译器调用
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
public String toString() {
return name;
}
public final boolean equals(Object other) {
return this==other;
}
//比较的是ordinal值
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;//根据ordinal值比较大小
}
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
//获取class对象引用,getClass()是Object的方法
Class<?> clazz = getClass();
//获取父类Class对象引用
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
//enumType.enumConstantDirectory()获取到的是一个map集合,key值就是name值,value则是枚举变量值
//enumConstantDirectory是class对象内部的方法,根据class对象获取一个map集合的值
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
//.....省略其他没用的方法
}
通过Enum源码,可以知道,Enum实现了Comparable接口,这也是可以使用compareTo比较的原因,当然Enum构造函数也是存在的,该函数只能由编译器调用,毕竟我们只能使用enum关键字定义枚举,其他事情就放心交给编译器吧。
编译器生成的Values方法与ValueOf方法
values()方法和valueOf(String name)方法是编译器生成的static方法,因此从前面的分析中,在Enum类中并没出现values()方法,但valueOf()方法还是有出现的,只不过编译器生成的valueOf()方法需传递一个name参数,而Enum自带的静态方法valueOf()则需要传递两个方法,从前面反编译后的代码可以看出,编译器生成的valueOf方法最终还是调用了Enum类的valueOf方法,下面通过代码来演示这两个方法的作用:
Day[] days2 = Day.values();
System.out.println("day2:"+Arrays.toString(days2));
Day day = Day.valueOf("MONDAY");
System.out.println("day:"+day);
/**
输出结果:
day2:[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
day:MONDAY
*/
从结果可知道,values()方法的作用就是获取枚举类中的所有变量,并作为数组返回,而valueOf(String name)方法与Enum类中的valueOf方法的作用类似根据名称获取枚举变量,只不过编译器生成的valueOf方法更简洁些只需传递一个参数。这里我们还必须注意到,由于values()方法是由编译器插入到枚举类中的static方法,所以如果我们将枚举实例向上转型为Enum,那么values()方法将无法被调用,因为Enum类中并没有values()方法,valueOf()方法也是同样的道理,注意是一个参数的。
//正常使用
Day[] ds=Day.values();
//向上转型Enum
Enum e = Day.MONDAY;
//无法调用,没有此方法
//e.values();
枚举的进阶用法
在前面的分析中,我们都是基于简单枚举类型的定义,也就是在定义枚举时只定义了枚举实例类型,并没定义方法或者成员变量,实际上使用关键字enum定义的枚举类,除了不能使用继承(因为编译器会自动为我们继承Enum抽象类而Java只支持单继承,因此枚举类是无法手动实现继承的),可以把enum类当成常规类,也就是说我们可以向enum类中添加方法和变量,甚至是mian方法
向enum类添加方法与自定义构造函数
如果打算自定义自己的方法,那么必须在enum实例序列的最后添加一个分号。
重新定义一个日期枚举类,带有desc成员变量描述该日期的对于中文描述,同时定义一个getDesc方法,返回中文描述内容,自定义私有构造函数,在声明枚举实例时传入对应的中文描述,代码如下:
/**
* Created by zejian on 2017/5/8.
* Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
*/
public enum Day2 {
MONDAY("星期一"),
TUESDAY("星期二"),
WEDNESDAY("星期三"),
THURSDAY("星期四"),
FRIDAY("星期五"),
SATURDAY("星期六"),
SUNDAY("星期日");//记住要用分号结束
private String desc;//中文描述
/**
* 私有构造,防止被外部调用
* @param desc
*/
private Day2(String desc){
this.desc=desc;
}
/**
* 定义方法,返回描述,跟常规类的定义没区别
* @return
*/
public String getDesc(){
return desc;
}
public static void main(String[] args){
for (Day2 day:Day2.values()) {
System.out.println("name:"+day.name()+
",desc:"+day.getDesc());
}
}
/**
输出结果:
name:MONDAY,desc:星期一
name:TUESDAY,desc:星期二
name:WEDNESDAY,desc:星期三
name:THURSDAY,desc:星期四
name:FRIDAY,desc:星期五
name:SATURDAY,desc:星期六
name:SUNDAY,desc:星期日
*/
}
从上述代码可知,在enum类中确实可以像定义常规类一样声明变量或者成员方法。但是我们必须注意到,如果打算在enum类中定义方法,务必在声明完枚举实例后使用分号分开,倘若在枚举实例前定义任何方法,编译器都将会报错,无法编译通过,同时即使自定义了构造函数且enum的定义结束,我们也永远无法手动调用构造函数创建枚举实例,毕竟这事只能由编译器执行。
也即是对于自定义成员变量,要定义对应的构造函数赋值,然后定义枚举类的时候传入相应的值,由编译器自动执行构造函数进行赋值,而不能手动进行赋值。
同时要想获取自定义成员变量值,
enum类中定义抽象方法
与常规抽象类一样,enum类允许我们为其定义抽象方法,然后使每个枚举实例都实现该方法,以便产生不同的行为方式,注意abstract关键字对于枚举类来说并不是必须的如下:
public enum EnumDemo3 {
FIRST{
@Override
public String getInfo() {
return "FIRST TIME";
}
},
SECOND{
@Override
public String getInfo() {
return "SECOND TIME";
}
}
;
/**
* 定义抽象方法
* @return
*/
public abstract String getInfo();
//测试
public static void main(String[] args){
System.out.println("F:"+EnumDemo3.FIRST.getInfo());
System.out.println("S:"+EnumDemo3.SECOND.getInfo());
/**
输出结果:
F:FIRST TIME
S:SECOND TIME
*/
}
}
通过这种方式就可以轻而易举地定义每个枚举实例的不同行为方式。我们可能注意到,enum类的实例似乎表现出了多态的特性,可惜的是枚举类型的实例终究不能作为类型传递使用,就像下面的使用方式,编译器是不可能答应的:
//无法通过编译,毕竟EnumDemo3.FIRST是个实例对象 public void text(EnumDemo3.FIRST instance){ }
enum类与接口
由于Java单继承的原因,enum类并不能再继承其它类,但并不妨碍它实现接口,因此enum类同样是可以实现多接口的,如下:
interface food{
void eat();
}
interface sport{
void run();
}
public enum EnumDemo2 implements food ,sport{
FOOD,
SPORT,
; //分号分隔
@Override
public void eat() {
System.out.println("eat.....");
}
@Override
public void run() {
System.out.println("run.....");
}
}
枚举与单例模式
Java单例模式实现方式:
http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html
单例模式可以说是最常使用的设计模式了,它的作用是确保某个类只有一个实例,自行实例化并向整个系统提供这个实例。在实际应用中,线程池、缓存、日志对象、对话框对象常被设计成单例,总之,选择单例模式就是为了避免不一致状态,下面我们将会简单说明单例模式的几种主要编写方式,从而对比出使用枚举实现单例模式的优点。首先看看饿汉式的单例模式:
public class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry() {
}
public static SingletonHungry getInstance() {
return instance;
}
}
枚举类实现单例模式:
实现方式一:枚举类本身就是单例对象,内部可实现单例对象具体方法逻辑
Java虚拟机会保证枚举对象的唯一性,因此每一个枚举类型和定义的枚举变量在JVM****中都是唯一的。
public enum Singleton {
INSTANCE;
public void businessMethod() {
System.out.println("我是一个单例!");
}
}
大家可以看到,我们定义了一个枚举类型Singleton,在其中定义了一个枚举变量instance,同时还提供了业务方法businessMethod。
实现方式二:枚举类作为单例对象内部类
如果需要将一个已有的类改造为单例类,也可以使用枚举的方式来实现。
public class Singleton {
private Singleton(){
}
public static enum SingletonEnum {
SINGLETON;
private Singleton instance = null;
private SingletonEnum(){
instance = new Singleton();
}
public Singleton getInstance(){
return instance;
}
}
}
在代码中,我们首先将Singleton类的构造函数设置为private私有的,然后在Singleton类中定义一个静态的枚举类型SingletonEnum。
在SingletonEnum中定义了枚举类型的实例对象Singleton,再按照单例模式的要求在其中定义一个Singleton类型的对象instance,其初始值为null;我们需要将SingletonEnum的构造函数改为私有的,在私有构造函数中创建一个Singleton的实例对象;最后在getInstance()方法中返回该对象。
在实现过程中,Java虚拟机会保证枚举类型不能被反射并且构造函数只被执行一次。
正如我们前面所讲的Singleton类本身就是一个普通类,它里面还包含了其他业务方法。在这里我们只需要在其中增加一个内部枚举类型来存储和创建它的唯一实例即可,这和前面的静态内部类的实现有点相似,但是枚举实现可以很好地解决反射和反序列化会破坏单例模式的问题,提供了一种更加安全和可靠的单例模式实现机制。
参考文献
https://blog.csdn.net/javazejian/article/details/71333103
https://blog.csdn.net/qq_27093465/article/details/52180865