万万想不到还能这样创建Java对象

  对象作为面向对象编程语言最重要的概念,在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已经有提供现成的方法,我们直接调用或抄袭一下就行了,非常方便。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。