Java的枚举与设计模式的单例模式

昨天学习了一下单例模式(其实就是随便看看),里面提到了几种实现单例的方法,其中一种就是用枚举来实现。看完之后,我才发现自己对枚举原来是一知半解。

什么是枚举

所谓枚举,就是能把所有可能的情况都列举出来。最经典的一个例子就是交通灯了。正常的交通信号灯只有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类之外编写其子类
    
}

会出现下面的错误:


TrafficLigh类的构造函数private化,而子类构造函数却要调用它

解决上面这个问题的办法就是内部类。
所以总之一句话,要不要把实现了枚举效果的类(这里是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类的(根据反编译的结果就可以看到)

  1. toString() 获得枚举常量对应的字符串
TrafficLight light = TrafficLight.Red;
System.out.println(light.toString());//输出结果为“Red”
System.out.println(light);//输出结果为“Red”,和上面一条语句是同一个意思
  1. valueOf() 由字符串来获得对应的枚举常量
System.out.println(TrafficLight.Red == TrafficLight.valueOf(TrafficLight.class,"Red"));//显示结果为“true”
  1. 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》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容