您的位置 首页 java

深入剖析Java Enum类型

今天来记录下对于 Java Enum 类型的理解,往往初学者都会对 enum 这个概念有点疑惑,在声明Enum的时候并没有像声明类那样,今天这篇内容我们会从底层来看下 enum 的实现,从而更深刻地去掌握 enum

先从一个简单的例子开始,声明一个 enum 对象

 public enum EnumType{
}  

而熟知的用法就是在用 enum 来声明一些常量:

 public enum EnumType{
   Apple ,
  Banana,
  Pear;
}  

然后可以直接调用所需要使用的常量,如:EnumType.Apple;再稍微复杂一点会类似这样:

 public enum InstanceType {
    INSTANCE("instance",1),
    INSTANCE1("instance",2);
     private  String name;
    private int id;
    public String getName(){
        return name;
    }
    public int getId(){
        return id;
    }
    private Singleton4(String name ,int id){
        this.name = name;
        this.id = id;
    }
    public Singleton4 getEnumFromId(int id){
        for (Singleton4 singleton4 : values()){
            if(singleton4.getId() == id){
                return singleton4;
            }
        }
        return null;
    }
}  

像上面这个例子,对于 enum 对象中的每个常量都赋予了一些属性,其实跟类的使用没有太多区别;

下面来看一个特殊的例子,用 enum 来构造单例模式:

 public enum Singleton {
    INSTANCE;
}  

前面在 我们也提到过,这次《Effective Java》的作者推荐的单例写法,简单轻巧。

下面就来分析下为什么这种写法会被推荐,它有什么好处?究竟 enum 在这里起了什么作用?

首先枚举的单例写法确实特别简单,Java规范规定,每个单例对象在 JVM 中是唯一的;其实INSTANCE就是一个静态常量字段,可以通过 jad 工具来反编译看到具体的Singleton的源码:

 public final class Singleton  extends  Enum
    public  static  Singleton[] values()
    {
        return (Singleton[])$VALUES.clone();
    }
    public static Singleton valueOf(String name)
    {
        return (Singleton)Enum.valueOf(cs/designpattern/singleton/bean/Singleton, name);
    }
    private Singleton(String s, int i)
    {
        super(s, i);
    }
    public static final Singleton INSTANCE;
    private static final Singleton $VALUES[];
    static 
    {
        INSTANCE = new Singleton("INSTANCE", 0);
        $VALUES = (new Singleton[] {
            INSTANCE
        });
    }
}  

上面的代码是通过jad反编译工具生成的代码,可以注意到INSTANCE被其实是被声明成了一个static final类型的对象,并且在static代码块中进行了初始化,同时调用的构造函数是private类型的, 所以这里的单例对象INSTANCE在类加载的时候就会进行初始化,也是饿汉模式。

同样是饿汉模式,enum构造的饿汉模式另外一个好处就是可以防止反射的时候构造出第二个不同的对象,如果我们是按照以下这种模式构造单例:

 public class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton(){
    }
    public Singleton  getInstance (){
        return singleton;
    }
}  

这种构造方式下,如果进行反射构造对象也能生成一个不同的对象,可以来对比下直接获取的单例对象和反射获取的单例对象是否相同:

 public static  void  main(String[] args) throws ClassNotFoundException, InvocationTarget Exception , InstantiationException, IllegalAccessException, NoSuchMethodException {
        //直接获取单例对象,输出
        System.out.println(Singleton.getInstance());
        //反射获取单例对象,输出
        Class clazz = Class.forName("cs.designpattern.singleton.bean.Singleton");
         Constructor  constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton1 singleton = (Singleton) constructor.newInstance(null);
        System.out.println(singleton);
  }  

输出:

 cs.designpattern.singleton.bean.Singleton1@14ae5a5
cs.designpattern.singleton.bean.Singleton1@7f31245a  

可以从输出结果看到,这是两个不同的对象,所以这种构造方式,如果用反射构造对象,就会打破单例模式的唯一性。

然后如果换成Enum来构造的单例,再来尝试下:

         public enum Singleton {
          INSTANCE;
        }


        //直接获取Enum单例对象,输出
        System.out.println(Singleton.INSTANCE);


        //反射获取单例对象,输出
        Class clazz = Class.forName("cs.designpattern.singleton.bean.Singleton");
        Constructor[] constructors = clazz.getDeclaredConstructors();
        Constructor constructor = constructors[0];
        constructor.setAccessible(true);
        Singleton singleton = (Singleton) constructor.newInstance("INSTANCE",0,"instance",1);
        System.out.println(singleton);  

输出:

 INSTANCE
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
  at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
  at cs.designpattern.singleton.bean.Main.testSingle4(Main.java:32)
  at cs.designpattern.singleton.bean.Main.main(Main.java:8)  

可以看到,当反射调用enum 构造方法 的时候,抛出异常 Cannot reflectively create enum objects ,定位到具体的Constructor源码是这样的:

 if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");  

由于代码中判断了是否是对enum类型做反射,所以enum就是依靠这种检测方式来阻止了反射时去创建单例对象,因此相比上面的饿汉模式,enum饿汉的方式是可以确保能够创建一个唯一的单例对象的。

同理,另外两种懒汉模式也是无法防止反射去打破单例的唯一性的特性的,所以之前介绍的4种常用模式中,只有enum构造的单例模式既能做到真正的唯一单例的对象,还能保证 多线程 安全,而且最关键的是写法还非常简单。

那么看到这里,是否除了enum构造的单例写法,其他的写法都没有用了呢?

当然,用反射来打破单例对象的唯一性,并不是说其他的写法就不能用了,其实我理解的是设计模式更多的是代码上的一种约束,是 程序员 构建系统的时候考虑扩展性、程序性能时采用的一种方案,也是程序员互相交流的一种沟通方式,如果在一个团队中,大家约定单例模式用的双重检测的懒汉模式,而且不用反射调用单例对象,那么在大家都遵守这种规约的情况下,其实用哪种写法都是可以的。

关于网上经常讨论的 单例模式 写法上导致的性能差异,如何分析?

经常会看到关于饿汉模式和懒汉模式的讨论,饿汉模式可能会浪费资源,懒汉模式会节省资源,关于这个也是各有利弊。

饿汉模式的好处在于可以做到系统启动时初始化,因为可能单例对象是初始化资源、配置、 数据库连接池 等比较耗时的操作,这样只要启动一次,以后可以处处运行,也是提高应用运行时性能的方式;

而如果用懒汉模式,等到用的时候,再去初始化单例对象,如果这时候初始化单例的耗时极短,倒是没啥影响,一旦耗时过长,有可能就会引起应用发生timeout等影响,所以看似理论上比较在理的判断,实际没有那么精确的,最保险的方式还是要做上线前的benchmark、系统压测来通过数据决定。

今天主要深入剖析了Java enum 类型,其实本质上它也是一个类,而编写代码时只是语法层面的规则,理解了enum在单例场景中的用处,会对其有一个新的认识。

文章来源:智云一二三科技

文章标题:深入剖析Java Enum类型

文章地址:https://www.zhihuclub.com/186613.shtml

关于作者: 智云科技

热门文章

网站地图