如何绕过Java的构造方法来创建实例

我所设计的绝大部分类,我都会关注它们的不可变性。要想获得不可变性需要这样做:

  • 使用构造方法来初始化所有的属性。
  • 这些属性没有setter方法。

然而,这样的设计使得测试更加复杂甚至无法测试。为了能进行测试,你还需要一个public的无参构造方法。

其它需要无参构造方法的情况包括:

  • 序列化对象的反序列化。
  • 子类中没有调用父类的构造函数。
  • 其它

下面是它的一些解决方案。

  1. 实现一个public的无参构造方法

最简单的方式就是创建一个public的无参构造函数了,然后添加一个大的醒目的警告信息让开发人员别去使用这个方法。你可以想像得到,这种方法虽然简单,但是它无法强制约束什么,因为你得依赖开发人员的自觉性来遵循你的规则(或者更多地是他们得能在第一时间看到这段警告文档——这得赌一下了)。

这么做最大的限制就是你得能够修改类的代码。

  1. 实现一个包可见的无参构造方法

测试时一个常见的方法就是将类的private方法的可见性改成包可见的,这样的话测试类只要和它们在一个包下面就可以进行测试了。同样的方法也适用于我们这个例子中:实现一个包可见的无参构造方法。

这需要测试类和这个创建了构造函数的类在同一个包底下。和情况1类似,你也得去修改类的代码。

  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上可用
  • 需要设置一个足够宽松的安全管理器
  1. 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框架为你提供了一个可移植的抽象层来实现这个,你只需要多增加一个额外的依赖就可以了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值