对象作为面向对象编程语言最重要的概念,在Java中平时都是new一个对象来创建新对象,其实在Java中有4种也可以说5种方式来创建对象:
-
1、通过new创建对象
new一个新对象是最常用的方式,比如定义一个Apple的类,然后new Apple(),在new一个对象的操作中,需要经历两个过程:类加载及初始化、创建对象,详细过程有机会在讲。 -
2、通过clone创建对象
clone是Object的方法,被clone的类需要实现Cloneable接口,并重写clone方法,否则会出现java.lang.CloneNotSupportedException异常。
public class Apple implements Cloneable{
@NonNull
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Apple apple=new Apple();
try {
Apple cloneApple=(Apple) apple.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
-
3、通过反序列化创建对象
当我们使用反序列化一个对象的时候,JVM会给我们创建一个对象。但是,反序列化的时候JVM并不会去调用类的构造函数来创建对象,而是通过之前序列化对象的字节序列来创建的。为了反序列化一个对象,我们需要让我们的类实现Serializable接口。 -
4、通过newInstance创建对象
newInstance调用有两种:一种是Class.newInstance;一种是Constructor.newInstance;因此有些人把它们当作两种创建对象方法。
Class.newInstance和Constructor.newInstance区别在于:
Class.newInstance只能够调用无参构造函数,即默认构造函数
Constructor.newInstance可以调用有参构造函数
Class clz=Apple.class;
try {
clz.newInstance();
Constructor constructor= clz.getDeclaredConstructor();
constructor.newInstance();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
上面是Java中创建对象的4的种方法,接下来请看下面的代码:
public class Student {
private Student() {
throw new IllegalArgumentException("can not create.");
}
public String name;
}
请问该如何创建Student对象?这是在wanandroid上面看到的一个问题和大神给的解答,特意记录一下
公布答案:
Student student = UnsafeAllocator.create().newInstance(Student.class);
在Gson中有一个UnsafeAllocator类,代码如下:
public abstract class UnsafeAllocator {
public abstract <T> T newInstance(Class<T> c) throws Exception;
public static UnsafeAllocator create() {
// try JVM
// public class Unsafe {
// public Object allocateInstance(Class<?> type);
// }
try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field f = unsafeClass.getDeclaredField("theUnsafe");
f.setAccessible(true);
final Object unsafe = f.get(null);
final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
return new UnsafeAllocator() {
@Override
@SuppressWarnings("unchecked")
public <T> T newInstance(Class<T> c) throws Exception {
assertInstantiable(c);
return (T) allocateInstance.invoke(unsafe, c);
}
};
} catch (Exception ignored) {
}
它是一个抽象类,不过有提供一个静态方法,这个方法里面会即时创建一个匿名内部类,继承UnsafeAllocator并实现了那个抽象方法——newInstance。
可以看到,它内部其实是依赖Unsafe这个类(它里面有一系列直接操作内存的方法,非常强大,感兴趣的同学可以去了解下)的allocateInstance方法来实现的(通过这个方法创建的对象甚至连目标类的【初始化块】代码都不会走),我们只需要传一个Class进去就行了。
当然了,这个方法也不是万能的,比如:
不能直接创建接口对象;
不能直接创建抽象类对象;
看上面的newInstance方法,在通过反射调用Unsafe的allocateInstance之前,会做一个检查(调用assertInstantiable方法):
static void assertInstantiable(Class<?> c) {
int modifiers = c.getModifiers();
if (Modifier.isInterface(modifiers)) {
throw new UnsupportedOperationException("Interface can't be instantiated! Interface name: " + c.getName());
}
if (Modifier.isAbstract(modifiers)) {
throw new UnsupportedOperationException("Abstract class can't be instantiated! Class name: " + c.getName());
}
}
检测到目标Class是接口或抽象类,会直接抛出异常。
总结:
在日常开发中,如果碰到一些类,在创建对象时就抛异常了,而且无法通过继承来解决的时候,怎么绕过这个会出错的构造方法来创建对象呢?
可以通过反射借助隐藏类Unsafe的allocateInstance方法来实现;Google爸爸的Gson已经有提供现成的方法,我们直接调用或抄袭一下就行了,非常方便。