昨天学习了一下单例模式(其实就是随便看看),里面提到了几种实现单例的方法,其中一种就是用枚举来实现。看完之后,我才发现自己对枚举原来是一知半解。
什么是枚举
所谓枚举,就是能把所有可能的情况都列举出来。最经典的一个例子就是交通灯了。正常的交通信号灯只有3种可能的情况:红灯、绿灯、黄灯。如果我们希望在程序中定义交通灯这样一种类型,那就应该满足,属于这样一种类型的数据只能是上述三者之一。使用枚举就可以实现这样的需求。但枚举不是一有Java就有的,是jdk1.5引入。所以,在没有枚举之前,用类也是可以实现上面那种需求的。所以,我们先来看看怎么用类来实现。
用类实现枚举
public class EnumTest{
public static void main(String[] args){
TrafficLight light = TrafficLight.Red;//定义一个枚举变量,并用一个枚举常量为其赋值
//TrafficLight light = new TrafficLight();//Error,构造方法被private化
System.out.println(light);//打印结果是地址
}
}
class TrafficLight{
public static final TrafficLight Red = new TrafficLight();
public static final TrafficLight Green = new TrafficLight();
public static final TrafficLight Yellow = new TrafficLight();
private TrafficLight() {}
}
上面的代码介绍了如何用类来实现枚举,以及实现后如何使用这一枚举类型。
在我们自定义的TrafficLight类里,有三个属性Red、Green、Yellow,均是TrafficLight对象的引用,并都在class load 时分配空间。另外,我还用fianl、static来修饰了这3个属性。用final是为了使这3个引用管理的对象不能发生改变,即把这3个引用修饰为常引用。至于为何要用static修饰,在解释这个问题之前,我们先解释为何要把TrafficLight的构造函数私有化。原因就是:我们不希望能去实例化TrafficLight的对象,希望每一个TrafficLight的引用都只是上面说的3个常引用之一。因此,我们要把构造函数private化。这样一做,就意味着不可能在TrafficLight类之外的地方去new一个该类的对象出来,那又如何通过对象去用属性呢?因此要把那三个属性修饰为静态属性。至于这三个属性的权限修饰就无所谓了,据具体情况而定吧。
最后,还有一点需要解释的,就是TrafficLight要不要修饰为最终类的问题。首先修不修饰,你都没办法在TrafficLight类外编写一个子类。比如下面这样:
public class EnumTest{
public static void main(String[] args){
TrafficLight light = TrafficLight.Red;//定义一个枚举变量,并用一个枚举常量为其赋值
//TrafficLight light = new TrafficLight();//Error,构造方法被private化
System.out.println(light);//打印结果是地址
}
}
class TrafficLight{
public static final TrafficLight Red = new TrafficLight();
public static final TrafficLight Green = new TrafficLight();
public static final TrafficLight Yellow = new TrafficLight();
private TrafficLight() {}
}
class TrafficLightSon extends TrafficLight{ //在TrafficLight类之外编写其子类
}
会出现下面的错误:
解决上面这个问题的办法就是内部类。
所以总之一句话,要不要把实现了枚举效果的类(这里是TrafficLight)修饰为final,完全看具体情况咯。
不修饰为final,没办法直接在类外面编写其子类,要通过内部类实现。
修饰为final,就是无论如何都不可能有子类了。
下面我们看看用枚举来实现的TrafficLight。
枚举的使用
public class EnumTest{
public static void main(String[] args){
TrafficLight light = TrafficLight.Red;//定义一个枚举变量,并用一个枚举常量为其赋值
//TrafficLight light = new TrafficLight();//Error,不能实例化enum类对象
System.out.println(light);//打印结果是“Red”
}
}
enum TrafficLight{Red,Green,Yellow;} //用花括号括起来枚举常量表,两个枚举常量之间用“,”隔开
是不是简单了许多,一行代码就完事了。
根据上面的例子,我们总结一下如何定义一个简单的枚举类型,以及相关的简单使用
1.定义枚举类型的格式:
[修饰符] enum 枚举类型名 {
枚举常量1,枚举常量2,......;
}
花括号内的第一行是要求我们定义、列举出来所有的枚举常量,相邻枚举常量之间以 ,
分隔,至于最后一个枚举常量后面的;
是能省则省,只要下面没有第二条语句就可省略。
在定义枚举常量之前不能有其他语句
2.枚举类型的使用:
TrafficLight light = TrafficeLight.Red;//定义一个TrafficLight类的枚举变量light,并赋值为枚举常量Red
这里强调一点,枚举类都是不能实例化,只能是用枚举常量给变量赋值。
正所谓懒是第一创造力。枚举,其实就是一个语法糖(Syntactic sugar),帮程序员省略很多要做的事,同时也提高了可读性。见到上面形式的代码,我们一看就知道是枚举。而如果见到的是上文用类实现的那种枚举,我们可能没能一下子反应过来,那是枚举。当然我说枚举是语法糖,是有证据的,下面来看看对上面代码编译成的字节码文件反编译的结果:
当我们解除掉语法糖这层衣服之后,看到的东西实际上和我们上面第一种实现交通灯的方法差不了多少。区别在于,这里面多了一些东西。下面我们就介绍这些东西。
枚举的更多使用:
我们定义的枚举类是继承于java.lang.Enum类的(根据反编译的结果就可以看到)
- toString() 获得枚举常量对应的字符串
TrafficLight light = TrafficLight.Red;
System.out.println(light.toString());//输出结果为“Red”
System.out.println(light);//输出结果为“Red”,和上面一条语句是同一个意思
- valueOf() 由字符串来获得对应的枚举常量
System.out.println(TrafficLight.Red == TrafficLight.valueOf(TrafficLight.class,"Red"));//显示结果为“true”
- values() 返回一个所有枚举常量按顺序构成的数组
for(TrafficLight e:TrafficLight.values())
System.out.print(e+" "); //输出结果为 “Red Green Yellow ”,顺序就是我们定义枚举常量时的顺序
4.ordinal() 返回枚举变量对应的编号,编号从0开始
System.out.println(TrafficLight.Yellow.ordinal()); //输出结果为2
5.equals() 这个不用多说,就是判断两个枚举量是否指向同一个东西,其实就是“==”
TrafficLight light = TrafficLight.Red;
System.out.println(TrafficLight.Green.equals(light));//输出结果为false
说其实和“==”一样的实锤:
//java.lang.Enum中的源码
/**
* 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;
}
定义一个更丰满的枚举
public class EnumTest{
public static void main(String[] args){
for(TrafficLight light:TrafficLight.values()){
System.out.println(light);
System.out.println("ordinal:"+light.ordinal());
light.show();
System.out.println();
}
}
}
enum TrafficLight{
Red("红灯"){
@Override
void show(){
System.out.println("这是个红灯");
}
},
Green("绿灯"){
@Override
void show(){
System.out.println("这是个绿灯");
}
},
Yellow("黄灯");
String name;
private TrafficLight(String name){ //这里的权限修饰符只能是private,就算你省略不写。
this.name = name;
}
@Override
public String toString(){
return name;
}
void show(){
System.out.println("这是个交通灯");
}
}
枚举,其实说到底还是一个类,一个特殊的类。特殊之处在于它能让程序员写很少的代码就实现了类似交通灯这样的需求。一定要写的东西,那就是可以不写。但枚举并没有限制说你就只能写那几个枚举常量,不能有其他的属性,不能有其他的方法。所以,我们完全可以编写额外的属性、方法。但由于其特殊性,是有几点要注意:
1.构造方法必须是private,就算你省略访问权限修饰符,也不是包权限,而还是private
2.当构造方法有参数时,要注意对枚举常量定义的影响
//没有显式构造方法,即此时是默认无参构造方法
public enum TrafficLight{
Red,Green,Yellow;// 可以理解为是 Red(),Green(),Yellow(); 一定要写的圆括号,就是可以不写
}
//给出了显式构造方法,而且不是无参的
public enum TrafficLight {
//String name; //还记得吗?枚举常量定义之前不能有其他语句
Red("红"),Green("绿"),Yellow("黄");
String name;
private TrafficLight(String name) { //注意 访问权限
this.name = name;
}
}
3.继承枚举
只要你不把枚举类型定义为final,就可以有子类。但是由于构造方法私有化,所以要通过内部类来继承
//截取自上面的完整代码
Red("红灯"){
@Override
void show(){
System.out.println("这是个红灯");
}
},
怎么理解这段代码呢?
emmmm,下面这个解释会非常地绕。
代码的意思是:通过TrafficLight(String name)
这一构造方法new出来一个继承了TrafficLight类的匿名子类的对象,然后交给名字叫Red
的常引用管理,而这么一个匿名子类是重载了show()
方法。
其实,正常的话,是没有必要去编写这么一个丰满的枚举。一般来说写成下面这样就OK了。
enum TrafficLight {Red,Green,Yellow} //最简单的写法,其实就把它当作是一个枚举常量s的集合来写
之前,我也是这么觉得的。但自从我看了单例模式,我知道,其实有时候枚举是要写得丰满的。
单例模式
这里先不展开讲单例模式,等我学完设计模式,总结的时候再展开介绍吧。
这里就只是先讲讲怎么用枚举实现单例
public enum Singleton {
INSTANCE;
}
emmm,就是这么简单,既线程安全又保证序列化时绝对单例,但不是lazy loading
最后
单元素的枚举类型已经成为实现Singleton的最佳方式 —— 《Effective Java》