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