如何绕过Java的构造方法来创建实例
2014-05-26 10:01
417 查看
我所设计的绝大部分类,我都会关注它们的不可变性。要想获得不可变性需要这样做:
使用构造方法来初始化所有的属性。
这些属性没有setter方法。
然而,这样的设计使得测试更加复杂甚至无法测试。为了能进行测试,你还需要一个public的无参构造方法。
其它需要无参构造方法的情况包括:
序列化对象的反序列化。
子类中没有调用父类的构造函数。
其它
下面是它的一些解决方案。
实现一个public的无参构造方法
最简单的方式就是创建一个public的无参构造函数了,然后添加一个大的醒目的警告信息让开发人员别去使用这个方法。你可以想像得到,这种方法虽然简单,但是它无法强制约束什么,因为你得依赖开发人员的自觉性来遵循你的规则(或者更多地是他们得能在第一时间看到这段警告文档——这得赌一下了)。
这么做最大的限制就是你得能够修改类的代码。
实现一个包可见的无参构造方法
测试时一个常见的方法就是将类的private方法的可见性改成包可见的,这样的话测试类只要和它们在一个包下面就可以进行测试了。同样的方法也适用于我们这个例子中:实现一个包可见的无参构造方法。
这需要测试类和这个创建了构造函数的类在同一个包底下。和情况1类似,你也得去修改类的代码。
使用Unsafe来实现
JDK像一座埋藏的宝藏:它包含许多隐藏的闪亮的特性;sun.misc.Unsafe就是其中之一。当然了,正如它的名字和所在包所暗示的那样,它的使用是非常不推荐的。Unsafe提供了一个allocateInstance的方法来创建新的实例,而不用调用任何构造函数,也就是不需要调用任何初始化程序。
注意Unsafe只有实例方法,而它仅有的一个构造函数是私有的。。但它提供了一个私有的单例属性。要想获取这个属性的引用,你需要用到一点反射的逻辑,以及一个宽松的安全管理器(SecurityManager)。
这个方法最大的限制在在:
依赖公共API以外的类
使用反射来访问私有属性
只在Oracle的HotSpot JVM上可用
需要设置一个足够宽松的安全管理器
ObjenesisObjenesis是一个旨在不使用构造函数创建新的实例的一个框架。它基于Unsafe提供了一个抽象层。Objenesis在不同的JVM上也同样可用,包括不同版本的OpenJDK, Oracle的JRokkit和Dalvik(也就是Android的),它使用了不同的策略来适配不同的JVM以及不同的版本的组合。
上述的代码可以替换成这面这个:
在Oracle的HotSpot上运行这段代码同样需要一个宽松的安全管理器,因为Objenesis使用的也是上面的Unsafe类。然而,不同的JVM可能会有不同的要求,这些Objenesis都替你处理了。
使用构造方法来初始化所有的属性。
这些属性没有setter方法。
然而,这样的设计使得测试更加复杂甚至无法测试。为了能进行测试,你还需要一个public的无参构造方法。
其它需要无参构造方法的情况包括:
序列化对象的反序列化。
子类中没有调用父类的构造函数。
其它
下面是它的一些解决方案。
实现一个public的无参构造方法
最简单的方式就是创建一个public的无参构造函数了,然后添加一个大的醒目的警告信息让开发人员别去使用这个方法。你可以想像得到,这种方法虽然简单,但是它无法强制约束什么,因为你得依赖开发人员的自觉性来遵循你的规则(或者更多地是他们得能在第一时间看到这段警告文档——这得赌一下了)。
这么做最大的限制就是你得能够修改类的代码。
实现一个包可见的无参构造方法
测试时一个常见的方法就是将类的private方法的可见性改成包可见的,这样的话测试类只要和它们在一个包下面就可以进行测试了。同样的方法也适用于我们这个例子中:实现一个包可见的无参构造方法。
这需要测试类和这个创建了构造函数的类在同一个包底下。和情况1类似,你也得去修改类的代码。
使用Unsafe来实现
JDK像一座埋藏的宝藏:它包含许多隐藏的闪亮的特性;sun.misc.Unsafe就是其中之一。当然了,正如它的名字和所在包所暗示的那样,它的使用是非常不推荐的。Unsafe提供了一个allocateInstance的方法来创建新的实例,而不用调用任何构造函数,也就是不需要调用任何初始化程序。
注意Unsafe只有实例方法,而它仅有的一个构造函数是私有的。。但它提供了一个私有的单例属性。要想获取这个属性的引用,你需要用到一点反射的逻辑,以及一个宽松的安全管理器(SecurityManager)。
Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe = (Unsafe) field.get(null); java.sql.Date date = (java.sql.Date) unsafe.allocateInstance(java.sql.Date.class);
这个方法最大的限制在在:
依赖公共API以外的类
使用反射来访问私有属性
只在Oracle的HotSpot JVM上可用
需要设置一个足够宽松的安全管理器
ObjenesisObjenesis是一个旨在不使用构造函数创建新的实例的一个框架。它基于Unsafe提供了一个抽象层。Objenesis在不同的JVM上也同样可用,包括不同版本的OpenJDK, Oracle的JRokkit和Dalvik(也就是Android的),它使用了不同的策略来适配不同的JVM以及不同的版本的组合。
上述的代码可以替换成这面这个:
Objenesis objenesis = new ObjenesisStd(); ObjectInstantiator instantiator = objenesis.getInstantiatorOf(java.sql.Date.class); java.sql.Date date = (java.sql.Date) instantiator.newInstance(); System.out.println(date);
在Oracle的HotSpot上运行这段代码同样需要一个宽松的安全管理器,因为Objenesis使用的也是上面的Unsafe类。然而,不同的JVM可能会有不同的要求,这些Objenesis都替你处理了。
结论
尽管并不常见也没有专门的要求,但有时候还是需要不使用构造函数来创建实例的。万一碰上这样的情况,Objenesis框架为你提供了一个可移植的抽象层来实现这个,你只需要多增加一个额外的依赖就可以了。相关文章推荐
- 使用反射创建Bean、Spring中是如何根据类名配置创建Bean实例、Java提供了Class类获取类别的字段和方法,包括构造方法
- 使用反射创建Bean、Spring中是如何根据类名配置创建Bean实例、Java提供了Class类获取类别的字段和方法,包括构造方法
- Java中如果把构造方法也私有化,如何创建对象?Java的单例设计模式——饿汉式和懒汉式区别
- Java中用反射通过构造方法创建实例
- Java如何根据类名和方法名创建类的实例,并调用对应方法?
- 【Java】【TIJ】实现一个类:不允许用构造方法来直接创建类实例(对象)
- Java 反射创建类的实例对象(默认构造方法和私有构造方法)
- android之反射创建一个实例:构造方法带参和不带参
- Java静态初始化,实例初始化以及构造方法
- Java中如何实现一个类在内存里只能创建一个实例对象
- java继承中的构造方法实例解析
- 关于Java的泛型在所声明的对象中如何获取class或者实例的方法的总结
- java 程序加载过程---3--类中申明同时申明类的静态对象 创建类的实例 访问类的静态变量 调用类的静态方法 使用反射方法 初始化类的子类对象 直接使用java.exe 调用某个类
- java中 静态成员、实例成员、构造方法在子类和父类中的执行顺序
- JAVA中,构造方法与实例方法的区别
- java中实例变量、类变量以及构造方法的加载顺序
- java里面如何创建一个内部类的实例?
- Java静态初始化,实例初始化以及构造方法
- JAVA动态模块的设计实现以及如何通过newInstance()来创建带参的构造类实例
- Java创建对象实例的三种方法