引子
首先,一个常问的问题,Java中常用的实例化一个对象的方法有哪些?答案可以简单归类为以下几种方式
- 用new 语句创建对象,这是最常用的创建对象方法。这是我们最常用的最基本的方法
- 运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法。在我们需要将java对象存储到磁盘保存或者通过网络传输的时候会用到这种方法,但是缺点是java自带的这种序列化方式后字节码较大、效率不高。目前来说需要对Java对象序列化及反序列化的话有其他更好的方案
- 运用反射手段,调用Java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。这是我们在需要动态创建对象,不确定类名的时候最常用的方法
- 以及调用对象的clone()方法。这种方式需要现有的Java类实现Cloneable接口,再Override Object类的clone()方法,且还存有浅拷贝、深拷贝的问题,Object的clone()方法默认是浅拷贝
关于Objenesis
除了上面这些,我们还有另外一个选项,使用Objenesis,Objenesis是一个小的Java库,它有一个用途:实例化一个特定类的新对象。
我们举一个栗子,比如如下这个类
public class TestObj {
private final long userId;
private String name;
private int age;
public TestObj(long userId){
//todo check userId unique, or else throw Exception
this.userId = userId;
}
public long getUserId() {
return userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
我们只创建了一个需要传入userId的构造函数,并在构造函数中需要做一些校验的工作保证当前构造的userId是唯一的,那么在遇到这种情况的时候,如果我们想要newInstance()实例话就不可能了。执行一句
TestObj.class.newInstance();
会报出如下错误
而如果使用Objenesis,我们可以实现这一点,通过绕过对象实例化的构造函数来克服这些限制。简单代码如下
TestObj testObj = new TestObj(123);
testObj.setAge(12);
testObj.setName("CheungQ");
Objenesis objenesis = new ObjenesisStd();
TestObj testObj2 = objenesis.newInstance(TestObj.class);
BeanUtils.copyProperties(testObj,testObj2);
Field[] fields = TestObj.class.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
field.set(testObj2,field.get(testObj));
}
最终可以得到两个userId均为123的对象
如果你还是觉得有所疑惑,我们不妨再修改下代码,这样可以看得更清楚些。新增一个StaticConstant类,他包含了一个变量HashSet HASH_SET
public class StaticConstant {
public static final HashSet<Long> HASH_SET = new HashSet<>();
private StaticConstant() {
}
}
并把原来TestObj类的构造方法改造成如下,这种做法旨在保证当前内存中每一个TestObj对象的Obj都是唯一的,在某些类型的系统或者应用实践中会需要有这样的保证
public TestObj(long userId) throws Exception {
//todo check userId unique, or else throw Exception
if (HASH_SET.contains(userId)){
throw new Exception("userId should be unique");
}
HASH_SET.add(userId);
this.userId = userId;
}
此时如果执行
TestObj testObjExt = new TestObj(123);
TestObj testObj = new TestObj(123);
则会得到这样的异常信息
所以,通过以上的演示我们知道,Java已经支持使用class.newinstance()的类动态实例化,但是必须要有一个合适的构造函数。而很多场景下类不能够用这种方式去实例化,例如:
- 构造函数需要参数(Constructors that require arguments)
- 有副作用的构造函数(Constructors that have side effects)
- 会抛出异常的构造函数(Constructors that throw exceptions)
因此,常见的是在类库中看到类必须要有一个默认的构造函数的限制。
需要在不调用构造函数的情况下实例化对象是一项相当特殊的任务,但是在某些情况下这是有用的:
- 序列化,远程调用和持久化-对象需要被实例化并恢复到特定的状态,而不需要调用代码
- 代理、 AOP 库和 mock 对象-类可以被子类继承而子类不用担心父类的构造器
- 容器框架-对象可以以非标准的方式动态地实例化
简单使用
上面的示例中我们已经简单的使用过了Objenesis,这里我们再稍作展开再细说一下。最简单的使用Objenesis的方法是使用ObjenesisStd(标准)和ObjenesisSerializer(Serializable兼容)。默认情况下,自动确定最佳策略,所以你不必去考虑。
Objenesis objenesis = new ObjenesisStd(); // or ObjenesisSerializer
一旦有了Objenesis实现,就可以为特定类型类对象创建ObjectInstantiator。
ObjectInstantiator instantiator = objenesis.getInstantiatorOf(YourJavaObject.class);
最后,您可以使用它来实例化这种类型的新实例。
YourJavaObject obj = (YourJavaObject)instantiator.newInstance();
为了提高性能,最好尽可能多地重用ObjectInstantiator对象。例如,如果您正在实例化一个特定类的多个实例,那么从相同的ObjectInstantiator中执行它。InstantiatorAtorstrategy和ObjectInstantiator是线程安全的,他们都可以在多个线程之间共享和并发调用。http://objenesis.org/tutorial.html
发表评论