网站首页 > java教程 正文
今天来记录下对于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, InvocationTargetException, 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在单例场景中的用处,会对其有一个新的认识。
猜你喜欢
- 2024-10-06 关于Java中枚举Enum的深入剖析(java中枚举类型的使用)
- 2024-10-06 常用类及枚举类(一)(枚举类和普通类的区别)
- 2024-10-06 JAVA ---- 枚举/注解/反射(java中枚举的作用)
- 2024-10-06 Java enum——不一样的枚举对象(java 枚举使用)
- 2024-10-06 Java的enum的用法详解(java enum使用)
- 2024-10-06 学习廖雪峰的JAVA教程---java核心类(枚举类)
- 2024-10-06 Java枚举(JAVA枚举类型怎么用)
- 2024-10-06 java高级特性之泛型和枚举(java 泛型实现)
- 2024-10-06 java里枚举非常实用的3个方法,程序员一般都不注意
- 2024-10-06 给大伙讲一讲枚举的使用以及原理(枚举是干嘛的)
你 发表评论:
欢迎- 最近发表
-
- 你真的会用 Java 中的线程池吗?多个企业级线程池工具类封装实践
- 线程池的实现原理、优点与风险、以及四种线程池实现
- Java线程池ThreadPoolExecutor实现原理剖析
- 深入分析线程池的实现原理(线程池是干嘛的)
- 一文搞懂JAVA线程池工作原理(java线程池的工作流程)
- Java线程池的工作原理(java线程池的实现原理)
- 5分钟读懂C#中TcpClient、TcpListener和Socket三个类的角色
- JVM对象的创建过程(jvm运行过程中创建的对象一般存放在方法区)
- 对象组成与Java内存模型JMM分析(java对象在内存中存储的结构)
- JVM对象内存分配详细过程(栈上分配->TLAB->老年代->Eden区)
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)