平时在写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创建的对象不会调用构造器