枚举作为一个常规的语言概念,一直到Java5才诞生不得不说有点奇怪,以至于到现在为止很多程序员仍然更喜欢用static final的形式去命名常量而不使用,一般情况下,Java程序员用这种方式去实现枚举:

class EnumByClass{
public static final int RED=0;
public static final int GREEN=1;
public static final int BLUE=2;
}

这种方式实现的枚举也叫int枚举模式,尽管很常用,但是由int实现的枚举很难保证安全性,即当调用不在枚举范围内的数值时,需要额外的维护。另外 ,也不利于查看log和测试。

此时,我们需要开始使用Java的枚举类型,例如上面的int枚举模式类如果用enum实现,那么代码如下:

enum Color{
RED,GREEN,BLUE;
}

上述是将枚举作为常量集合的简单用法,实际上,枚举更多的还是用于switch,也是在这时才能发现枚举相对于int枚举模式的好处,这里面举一个用enum实现switch的例子:

enum Color{
RED,GREEN,BLUE;
}
public class Hello {
public static void main(String[] args){
Color color=Color.RED;
int counter=10;
while (counter-->0){
switch (color){
case RED:
System.out.println("Red");
color=Color.BLUE;
break;
case BLUE:
System.out.println("Blue");
color=Color.GREEN;
break;
case GREEN:
System.out.println("Green");
color=Color.RED;
break;
}
}
}
}

如果我们用int枚举模式的话,诚然可以用一些类似++,——的语法糖,但是也要更多的考虑到安全性的问题。

如果仅此而已,那么枚举也没什么单独拿出来写博客的价值。

我个人对enum感兴趣主要是因为之前在介绍Singleton时有一个非常经验的枚举实现的单例,代码如下:

enum SingletonDemo{
INSTANCE;
public void otherMethods(){
System.out.println("Something");
}
}

简简单单的一点代码就实现了一个线程安全的单例,与其说是写法鬼斧神工,不如说是恰如其分地应用了enum的性质。

在用enum实现Singleton时我曾介绍过三个特性,自由序列化,线程安全,保证单例。这里我们就要探讨一下why的问题。

首先,我们都知道enum是由class实现的,换言之,enum可以实现很多class的内容,包括可以有member和member function,这也是我们可以用enum作为一个类来实现单例的基础。另外,由于enum是通过继承了Enum类实现的,enum结构不能够作为子类继承其他类,但是可以用来实现接口。此外,enum类也不能够被继承,在反编译中,我们会发现该类是final的。

其次,enum有且仅有private的构造器,防止外部的额外构造,这恰好和单例模式吻合,也为保证单例性做了一个铺垫。这里展开说下这个private构造器,如果我们不去手写构造器,则会有一个默认的空参构造器,我们也可以通过给枚举变量参量来实现类的初始化。这里举一个例子。

enum Color{
RED(1),GREEN(2),BLUE(3);
private int code;
Color(int code){
this.code=code;
}
public int getCode(){
return code;
}
}

需要注意的是,private修饰符对于构造器是可以省略的,但这不代表构造器的权限是默认权限。

目前我们对enum的结构和特性有了初步的了解,接下来探究一下原理层次的特性。

想要了解enum是如何工作的,就要对其进行反编译。

反编译后就会发现,使用枚举其实和使用静态类内部加载方法原理类似。枚举会被编译成如下形式:

public final class T extends Enum{

...

}

其中,Enum是Java提供给编译器的一个用于继承的类。枚举量的实现其实是public static final T 类型的未初始化变量,之后,会在静态代码中对枚举量进行初始化。所以,如果用枚举去实现一个单例,这样的加载时间其实有点类似于饿汉模式,并没有起到lazy-loading的作用。

对于序列化和反序列化,因为每一个枚举类型和枚举变量在JVM中都是唯一的,即Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被编译器禁用的,因此也不存在实现序列化接口后调用readObject会破坏单例的问题。

对于线程安全方面,类似于普通的饿汉模式,通过在第一次调用时的静态初始化创建的对象是线程安全的。

因此,选择枚举作为Singleton的实现方式,相对于其他方式尤其是类似的饿汉模式主要有以下优点:

1. 代码简单

2. 自由序列化

至于lazy-loading,考虑到一般情况不存在调用单例类又不需要实例化单例的情况,所以即便不能做到很好的lazy-loading,也并不是大问题。换言之,除了枚举这种方案,饿汉模式也在单例设计中广泛的被应用。例如,Hibernate默认的单例,获取sessionFactory用的HibernateUtil类建立方式如下:

public class HibernateUtil {
private static final SessionFactory ourSessionFactory; static {
try {
Configuration configuration = new Configuration();
configuration.configure(); ourSessionFactory = configuration.buildSessionFactory();
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
} public static Session getSession() throws HibernateException {
return ourSessionFactory.openSession();
}
}

这是一个典型的饿汉模式,考虑到这个单例只有一个方法即getSession,显然这种模式本身就是最优的且简洁的。这里面由于SessionFactory的创建并不是用系统默认的方式,如果想要用enum去实现反而麻烦且无必要。不过至少说明这样做也许需要一个解决自由序列化的问题。

Java枚举enum以及应用:枚举实现单例模式的更多相关文章

  1. Android中是否推荐使用枚举Enum

    一.Enum的产生 Java1.5中引入了枚举的语法,包括Enum,EnumSet,EnumMap等.其中Enum就是我们在C或C++中见过的枚举类型,但是Java中的枚举又比C或C++中的枚举更成熟 ...

  2. Java中enum的学习总结

    一.通常的定义常量的方法 public class Sex{ public final static int MALE = 1; public final static int FEMALE=2; } ...

  3. 关于Java中枚举Enum的深入剖析

    在编程语言中我们,都会接触到枚举类型,通常我们进行有穷的列举来实现一些限定.Java也不例外.Java中的枚举类型为Enum,本文将对枚举进行一些比较深入的剖析. 什么是Enum Enum是自Java ...

  4. JAVA中枚举Enum详解

    1.关键字:enum.枚举可以定义成单独的文件,也可以定义在其他类内部. 枚举在类内部的示例: public class EnumInner { public static void main(Str ...

  5. Java中Enum枚举的使用

    三种不同的用法 注意项: 1.在switch中使用枚举能使代码的可读性更强.   2.如果要自定义方法,那么必须在enum实例序列的最后添加分号.而且Java要求必须先定义enum实例.   3.所有 ...

  6. JAVA—枚举(Enum)学习总结

    1.枚举(Enumeration) 枚举(The Enumeration)接口定义了一种从数据结构中取回连续元素的方式.这种传统接口已被迭代器取代,虽然Enumeration 还未被遗弃,但在现代代码 ...

  7. Java 枚举(enum) 详解7种常见的用法

    Java 枚举(enum) 详解7种常见的用法 来源 https://blog.csdn.net/qq_27093465/article/details/52180865 JDK1.5引入了新的类型— ...

  8. java枚举(enum)

    1. 创建枚举类型要使用 enum 关键字,隐含了所创建的类型都是 java.lang.Enum (抽象类) 类的子类. enum AccountType { SAVING, FIXED, CURRE ...

  9. Java 枚举(enum) 详解4种常见的用法

    JDK1.5引入了新的类型——枚举.在 Java 中它虽然算个“小”功能,却给我的开发带来了“大”方便. 大师兄我又加上自己的理解,来帮助各位理解一下. 用法一:常量 在JDK1.5 之前,我们定义常 ...

随机推荐

  1. clisp, scheme 和 clojure 初学习

    clisp, scheme和clojure 初学习 1 clojure "clojure绝对会成为你的编程工具箱里的终极武器" "其他语言可能只是工具,但 Clojure ...

  2. (NO.00001)iOS游戏SpeedBoy Lite成形记(六)

    为了能让玩家可以在比赛结束时清楚看到每位选手的成绩,我们需要在GameScene场景的track对象中添加一些新的元素. 在SpriteBuilder中打开GameScene.ccb,创建1个标签对象 ...

  3. 程序压力测试、性能测试AB、Webbench、Tsung

             负载生成器是一些生成用于测试的流量的程序.它们可以向你展示服务器在高负载的情况下的性能,以及让你能够找出服务器可能存在的问题.为了得到更加客观和准确的数值,应该从远程访问.局域网访问 ...

  4. 018-继承-OC笔记

    学习目标 1.[掌握]Xcode开发文档 2.[掌握]static关键字 3.[掌握]self关键字 4.[掌握]继承 5.[掌握]NSObject 6.[掌握]访问修饰符 7.[掌握]私有实例变量和 ...

  5. LayoutInflater和inflate的用法,有图有真相

    1.概述 有时候在我们的Activity中用到别的layout,并且要对其组件进行操作,比如: A.acyivity是获取网络数据的,对应布局文件为A.xml,然后需要把这个数据设置到B.xml的组件 ...

  6. Android Studio 1.2.2设置显示行号

    Android Studio设置显示行号的方法与Eclipse有少许差别,直接在代码中右键,弹出右键菜单是没有显示行号功能的. 在Android Studio中设置方法有二: 1.临时显示行号 在单个 ...

  7. Android Hal 分析

    本文是基于android4.0.3.对应其他低版本的代码,可能有所差异,但基本大同小异. Android的HAL是为了保护一些硬件提供商的知识产权而提出的,是为了避开linux的GPL束缚.思路是把控 ...

  8. Android NDK开发三:java和C\C++交互

    转自:http://www.cnblogs.com/shangdahao/archive/2013/05/02/3053971.html 1.定义native方法并加载动态链接库: public cl ...

  9. 增量会话对象——DeltaSession

    在集群环境中为了使集群中各个节点的会话状态都同步,同步操作是集群重点解决的问题,一般来说有两种同步策略,其一是每次同步都把整个会话对象传给集群中其他节点,其他节点更新整个会话对象:其二是对会话中增量修 ...

  10. Socket层实现系列 — listen()的实现

    本文主要分析listen()的内核实现,包括它的系统调用.Socket层实现.半连接队列,以及监听哈希表. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int l ...