对象创建的边界在哪里?那些你可能不知道的Java黑魔法

平时在写java代码的时候,创建对象都是使用的new关键字,框架中呢,常使用反射创建对象。后来在看八股的时候看到一个问题,问java中创建对象的方式,当时没专门了解过,思来想去也只想到了这两个方法,还是平时的积累太薄弱了,于是我就想来专门的记录一篇文章,用来记录一下这个问题。


我们在介绍之前,先准备一个自己的对象
我这里简单创建一个User类,后续的代码都会在这个User类上面进行操作

 

java

代码解读

复制代码

public class User { private String name; private int age; public User() { // 在构造函数中打印一句话,后面可以用来查看创建对象时是否使用了构造函数 System.out.println("通过构造函数创建 User 对象"); } 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; } @Override public String toString() { return "User{" + "name='" + name + ''' + ", age=" + age + '}'; } }

1. new 关键字

第一个方法毫无疑问就是我们最常用的new关键字了,这个大家都知道,就不做过多的描述了。
直接给出main函数的代码。

 

java

代码解读

复制代码

public class Main { public static void main(String[] args) { User user = new User(); } }

2. 反射

我们通过 User 的Class 类对象获取这个类的构造器对象,然后调用 Constructor 对象的newInstance()方法来创建对象。

 

java

代码解读

复制代码

public class Main { public static void main(String[] args) { try { Constructor<User> constructor = User.class.getDeclaredConstructor(); User user = constructor.newInstance(); user.setName("张三"); user.setAge(18); System.out.println(user); } catch (Exception e) { System.out.println("出异常了!"); } } }

从运行截图可以看出来,对象创建成功,并且调用了 User类的构造器。

该方法可以通过暴力反射调用私有的构造器 constructor.setAccessible(true);

3. 克隆

我这里重点是创建对象,就不介绍深克隆和浅克隆了。
克隆方式创建对象需要让User实现Cloneable接口并重写clone()方法,此方法是 protected 的,不重写无法调用。
User类修改部分代码:

 

java

代码解读

复制代码

public class User implements Cloneable{ private String name; private int age; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }

main函数

 

java

代码解读

复制代码

public class Main { public static void main(String[] args) { try { User user = new User(); user.setAge(18); user.setName("张三"); User clone = (User) user.clone(); System.out.println(clone); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } }

运行结果:

注意:

  • 出来的对象和原来的对象的值是一样的,用类型只是复制了引用类型的值(浅克隆)
  • 克隆的方式创建对象不会调用该类型的构造方法,运行结果图中的构造器调用是创建原始对象是调用的,克隆对象没有调用构造方法

4. 反序列化

使用反序列化的方式创建对象,需要实现Serializable接口。
我这里是直接序列化成的字节数组,持久化到磁盘文件是一样的

 

java

代码解读

复制代码

public class Main { public static void main(String[] args) { User user = new User(); user.setAge(18); user.setName("张三"); try { // 序列化 byte[] bytes = serialize(user); // 反序列化为对象 User user1 = deserialize(bytes); System.out.println(user1); System.out.println(user1 == user); } catch (Exception e) { throw new RuntimeException(e); } } // 把对象序列化成 byte[] 数组 public static byte[] serialize(User user) throws IOException { ByteArrayOutputStream bs = new ByteArrayOutputStream(); ObjectOutputStream os = new ObjectOutputStream(bs); // 使用对象流把对象写入 os.writeObject(user); byte[] bytes = bs.toByteArray(); return bytes; } // 反序列化为对象 public static User deserialize(byte[] bytes) throws IOException, ClassNotFoundException { ByteArrayInputStream bs = new ByteArrayInputStream(bytes); ObjectInputStream os = new ObjectInputStream(bs); return (User) os.readObject(); } }

运行截图:

  • 反序列化不会调用User的构造方法,但是会调用父类的构造方法,这里就不演示了
  • 反序列化的对象和原来的对像不是用一个对象

5. MethodHandle

这种方法类似于反射,但是它是另一套 API
indConstructor(User.class, MethodType.methodType(void.class))这个方法是找到对应的构造器,第一个参数的类的类型,第二个参数是MethodType,分别填入返回值类型,和对应参数的类型。

 

java

代码解读

复制代码

public class Main { public static void main(String[] args) { try { MethodHandle constructor = MethodHandles.lookup().findConstructor(User.class, MethodType.methodType(void.class)); // 调用构造方法 User user = (User) constructor.invoke(); user.setName("张三"); user.setAge(21); System.out.println(user); } catch (Exception e) { throw new RuntimeException(e); } catch (Throwable e) { throw new RuntimeException(e); } } }

运行截图:

6. Unsafe

可以通过Unsafe来创建对象,因为Unsafe有些特别,我这里使用反射来获取Unsafe的对象。

 

java

代码解读

复制代码

public class Main { public static void main(String[] args) { try { Class klass = Unsafe.class; Field field = klass.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe = (Unsafe) field.get(null); User user = (User) unsafe.allocateInstance(User.class); user.setName("张三"); user.setAge(16); System.out.println(user); } catch (Exception e) { throw new RuntimeException(e); } } }

运行截图:

  • 通过Unsafe创建的对象不会调用构造器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值