我们知道通过反射机制可以访问java对象中的任意属性,方法及构造方法。因此完全可以不使用new而通过反射来创建对象。
1.用Class对象的newInstance()方法创建该Class对象的实例
Class对象的newInstance()方法创建对象时要求该类必须要有默认的无参数的构造方法。因为newInstance()本质就是通过调用无参的构造方法来实现创建对象的。
实例:
class Person{
private String name;
private String gender;
private String birthday;
//必须提供默认的无参的构造方法
public Person() {
}
public Person(String name, String gender, String birthday) {
this.name = name;
this.gender = gender;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", birthday='" + birthday + '\'' +
'}';
}
}
public class ClassObjectDemo {
public static void main(String[] args) throws Exception {
//获取类对象
Class clazz = Class.forName("demo2.Person");
Person p = (Person) clazz.newInstance();//通过反射创建对象。
System.out.println(p);
}
}
运行结果:
Person{name='null', gender='null', birthday='null'}
注意: JAVA9之后已经废弃newInstance()方法,推荐使用:clazz.getDeclaredConstructor().newInstance();
2.使用Class对象获取指定的Constructor对象,再调用Constructor的newInstance()方法创建对象
具体步骤如下: - 获取该类的Class对象。 - 利用Class对象的getConstructor()方法来获取指定的构造方法。 - 如果构造方法是私有(private)的申请访问(设置为可访问) - 调用Constructor(构造方法)的newInstance()方法创建对象。
实例:
class Person{
private String name;
private String gender;
private String birthday;
public Person() {
}
public Person(String name, String gender, String birthday) {
this.name = name;
this.gender = gender;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", birthday='" + birthday + '\'' +
'}';
}
}
public class ClassObjectDemo {
public static void main(String[] args) throws Exception {
//获取类对象
Class clazz = Class.forName("demo2.Person");
//通过反射获取带三个参数的构造方法对象。
Constructor constructor = clazz.getDeclaredConstructor(String.class,String.class,String.class);
Person p = (Person) constructor.newInstance("张三","男","1999-10-10");
System.out.println(p);
}
}
运行结果:
Person{name='张三', gender='男', birthday='1999-10-10'}
3.Unsafe简介
new机制有个问题就是: 当类只提供有参的构造函数时,必须使用这个有参的构造函数。
那么问题来了,当反序列化的时候,不可能使用显示地new操作,因为肯定地根据传过来的类型动态地调用。利用newInstance肯定没戏了,因为不能确定这个类是否提供了无参构造函数。只能第三种,利用反射机制,使用Constructor对象来创建对象。
但是Consturctor对象有个约束,就是需要提供参数的类型列表,然后使用Constructor.newInstance方法需要传递相应个数的参数。
在反序列化这个场景下,可以这么做:先根据反射获得Constructor的参数类型列表,然后根据每种类型,构造一个对应的默认值的列表,然后调用Constructor.newInstance()方法。这样可以创建出一个具有默认值的对象。
但是问题又来了,万一这个类的构造函数做了一些特别的操作,比如判断传入的参数的值,如果参数值不符合规范就抛异常,那么创建对象就失败了。
使用sun.misc.Unsafe方法解决这个由于有参构造函数引起的创建Java对象的问题。Unsafe有一个allocateInstance(Class)方法,这个方法只需要传入一个类型就可以创建Java对象了,不正好完美的解决了我们的问题吗?
new操作被解析成了3个步骤,而Unsafe.allocateInstance()方法值做了第一步和第二步,即分配内存空间,返回内存地址,没有做第三步调用构造函数。所以Unsafe.allocateInstance()方法创建的对象都是只有初始值,没有默认值也没有构造函数设置的值,因为它完全没有使用new机制,直接操作内存创建了对象。
4.总结java中创建对象的五种方式
学过反射后,我们可以总结下java中一共有以下五种方式来创建对象: - new 关键字 - clone - 对象反序列化 - 反射 - unsafe
实例:通过以上五种方式创建Person类的对象。
class Person implements Serializable,Cloneable {
private static final long serialVersionUID = 5444891010864000361L;
private String name;
private String gender;
private String birthday;
public Person() {
System.out.println("一个人从世界上诞生了...");
}
public Person(String name, String gender, String birthday) {
this(); //先调用无参的构造方法
this.name = name;
this.gender = gender;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", birthday='" + birthday + '\'' +
'}';
}
//必须重写clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class ClassObjectDemo {
public static void main(String[] args) throws Exception {
//1.使用new关键字
Person p1 = new Person("张三","男","1999-10-10");
System.out.println(p1);
//2.使用clone
Person p2 = (Person) p1.clone();
System.out.println(p2);
//3.使用对象反序列化
File destFile = new File("save.data");
ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(destFile));
ObjectInputStream in = new ObjectInputStream( new FileInputStream(destFile));
out.writeObject(p1);
Person p3 = (Person) in.readObject();
System.out.println(p3);
out.close();
in.close();
//4.使用反射创建对象。
//获取类对象
Class clazz = Class.forName("demo2.Person");
//通过反射获取带三个参数的构造方法对象。
Constructor constructor = clazz.getDeclaredConstructor(String.class, String.class, String.class);
Person p4 = (Person) constructor.newInstance("张三", "男", "1999-10-10");
System.out.println(p4);
//5.使用unsafe创建对象
Class<Unsafe> unsafeClass = Unsafe.class;
// 第一种方式:通过构造器获取Unsafe实例
/*
Constructor<Unsafe> declaredConstructor = unsafeClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Unsafe unsafe = declaredConstructor.newInstance();
*/
// 第二种方法:通过字段获取Unsafe实例
Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe)theUnsafe.get(null);
Person p5 = (Person) unsafe.allocateInstance(Person.class);
System.out.println(p5);
System.out.println(p1 == p2);
System.out.println(p2 == p3);
System.out.println(p3 == p4);
System.out.println(p4 == p5);
}
}
运行结果:
一个人从世界上诞生了...
Person{name='张三', gender='男', birthday='1999-10-10'}
Person{name='张三', gender='男', birthday='1999-10-10'}
Person{name='张三', gender='男', birthday='1999-10-10'}
一个人从世界上诞生了...
Person{name='张三', gender='男', birthday='1999-10-10'}
Person{name='null', gender='null', birthday='null'}
false
false
false
false
通过观察,不难发现利用unsafe实例的allocateInstance方法可以直接不通过构造器来创建实例对象。