Java 利用枚举实现单例模式

来源:互联网 发布:新淘宝联盟没有购物车 编辑:程序博客网 时间:2024/05/21 19:53

引言

单例模式比较常见的实现方法有懒汉模式,DCL模式公有静态成员等,从Java 1.5版本起,单元素枚举实现单例模式成为最佳的方法。


Java枚举

基本用法

枚举的用法比较多,本文主要旨在介绍利用枚举实现单例模式的原理,所以这里也主要介绍一些相关的基础内容。 
首先,枚举类似类,一个枚举可以拥有成员变量,成员方法,构造方法。先来看枚举最基本的用法:

enum Type{    A,B,C,D;}
  • 1
  • 2
  • 3

创建enum时,编译器会自动为我们生成一个继承自java.lang.Enum的类,我们上面的enum可以简单看作:

class Type extends Enum{    public static final Type A;    public static final Type B;    ...}
  • 1
  • 2
  • 3
  • 4
  • 5

对于上面的例子,我们可以把Type看作一个类,而把A,B,C,D看作类的Type的实例。 
当然,这个构建实例的过程不是我们做的,一个enum的构造方法限制是private的,也就是不允许我们调用。

“类”方法和“实例”方法

上面说到,我们可以把Type看作一个类,而把A,B。。。看作Type的一个实例。同样,在enum中,我们可以定义类和实例的变量以及方法。看下面的代码:

enum Type{    A,B,C,D;    static int value;    public static int getValue() {        return value;    }    String type;    public String getType() {        return type;    }}在原有的基础上,添加了类方法和实例方法。我们把Type看做一个类,那么enum中静态的域和方法,都可以视作类方法。和我们调用普通的静态方法一样,这里调用类方法也是通过  Type.getValue()即可调用,访问类属性也是通过Type.value即可访问。下面的是实例方法,也就是每个实例才能调用的方法。那么实例是什么呢?没错,就是A,B,C,D。所以我们调用实例方法,也就通过 Type.A.getType()来调用就可以了。最后,对于某个实例而言,还可以实现自己的实例方法。再看下下面的代码:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
enum Type{A{    public String getType() {        return "I will not tell you";    }},B,C,D;static int value;public static int getValue() {    return value;}String type;public String getType() {    return type; }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

这里,A实例后面的{…}就是属于A的实例方法,可以通过覆盖原本的方法,实现属于自己的定制。 
除此之外,我们还可以添加抽象方法在enum中,强制ABCD都实现各自的处理逻辑:

enum Type{    A{        public String getType() {            return "A";        }    },B {        @Override        public String getType() {            return "B";        }    },C {        @Override        public String getType() {            return "C";        }    },D {        @Override        public String getType() {            return "D";        }    };    public abstract String getType();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

枚举单例

有了上面的基础,我们可以来看一下枚举单例的实现方法:

class Resource{}public enum SomeThing {    INSTANCE;    private Resource instance;    SomeThing() {        instance = new Resource();    }    public Resource getInstance() {        return instance;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

上面的类Resource是我们要应用单例模式的资源,具体可以表现为网络连接,数据库连接,线程池等等。 
获取资源的方式很简单,只要 SomeThing.INSTANCE.getInstance() 即可获得所要实例。下面我们来看看单例是如何被保证的: 
首先,在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。 
也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。 
可以看到,枚举实现单例还是比较简单的,除此之外我们再来看一下Enum这个类的声明:

public abstract class Enum<E extends Enum<E>>        implements Comparable<E>, Serializable
  • 1
  • 2

可以看到,枚举也提供了序列化机制。某些情况,比如我们要通过网络传输一个数据库连接的句柄,会提供很多帮助。 
最后借用 《Effective Java》一书中的话,

单元素的枚举类型已经成为实现Singleton的最佳方法。

高手文章:

    • 简介
    • 使用单元素的枚举实现单例模式

简介

通常情况下,我们写单例模式的时候无非就是三个步骤:构造器私有化,声明私有静态变量,提供静态获取实例的方法。简单说就是以下这种方式:

class SingletonA {    private static SingletonA instence = new SingletonA();    private SingletonA() {    }    public static SingletonA getInstance() {        return instence;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这是最基本的单例模式的写法,考虑到线程安全的问题,会用synchronized 关键字修饰getInstance()方法,另外还有饿汉式、懒汉式、静态内部类、双重校验锁的写法。 
但是这种写法存在缺陷,可以利用反射的方式来实例化多个不同的实例,如下所示:

private static void testReflex() {        try {            SingletonA s1 = SingletonA.getInstance();            Class<SingletonA> cls = SingletonA.class;            Constructor<SingletonA> constructor = cls                    .getDeclaredConstructor(new Class[] {});            constructor.setAccessible(true);            SingletonA s2 = constructor.newInstance(new Object[] {});            System.out.println(s1 == s2);//false        } catch (Exception e) {            e.printStackTrace();        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这种情况下,就会出现多个不同的实例,从而导致一些乱七八糟的结果。 
还有一种情况就是在序列化和反序列换的时候也会出现多个不同的实例,如下:

class SingletonB implements Serializable {    private static SingletonB instence = new SingletonB();    private SingletonB() {    }    public static SingletonB getInstance() {        return instence;    }}private static void testSingletonB() {        File file = new File("singleton");        ObjectOutputStream oos = null;        ObjectInputStream ois = null;        try {            oos = new ObjectOutputStream(new FileOutputStream(file));            SingletonB SingletonB1 = SingletonB.getInstance();            oos.writeObject(SingletonB1);            oos.close();            ois = new ObjectInputStream(new FileInputStream(file));            SingletonB SingletonB2 = (SingletonB) ois.readObject();            System.out.println(SingletonB1 == SingletonB2);//false        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        } catch (ClassNotFoundException e) {            e.printStackTrace();        } finally {            if (oos != null) {                try {                    oos.close();                } catch (IOException e) {                    e.printStackTrace();                }            }            if (ois != null) {                try {                    ois.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

这种情况也会出现有多个实例的问题,这个问题可以在类中添加readResolve()方法来避免,即:

class SingletonB implements Serializable {    private static SingletonB instence = new SingletonB();    private SingletonB() {    }    public static SingletonB getInstance() {        return instence;    }    // 不添加该方法则会出现 反序列化时出现多个实例的问题    public Object readResolve() {        return instence;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这样在反序列化的时候就不会出现多个实例。

使用单元素的枚举实现单例模式

一个最简单的POJO类,如下:

enum SingletonC implements Serializable {    INSTANCE;    private String field;    public String getField() {        return field;    }    public void setField(String field) {        this.field = field;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

测试方法:

    private static void testEnum() {        File file = new File("singletonEnum");        ObjectOutputStream oos = null;        ObjectInputStream ois = null;        try {            oos = new ObjectOutputStream(new FileOutputStream(file));            SingletonC singleton = SingletonC.INSTANCE;            oos.writeObject(SingletonC.INSTANCE);            oos.close();            ois = new ObjectInputStream(new FileInputStream(file));            SingletonC singleton2 = (SingletonC) ois.readObject();            System.out.println(singleton == singleton2);//true        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        } catch (ClassNotFoundException e) {            e.printStackTrace();        } finally {            if (oos != null) {                try {                    oos.close();                } catch (IOException e) {                    e.printStackTrace();                }            }            if (ois != null) {                try {                    ois.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

这种实现单例模式的方式是在 1.5之后才出现的,

这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使是在面对复杂序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。 —-《Effective Java 中文版 第二版》


原创粉丝点击