Java 中创建一个对象的 6 种方式


为了方便后边的代码展示,我们先提前定义一个演示类:

@Data
public class User {

    private String userName;
    private short age;

    public User() {
        System.out.println("------ 调用无参构造方法 ------");
    }

    public User(String userName, short age) {
        this.userName = userName;
        this.age = age;

        System.out.println(String.format("------ 调用有参构造方法:userName[%s], age[%d] ------", userName, age));
    }
}
1、使用 new 关键字创建对象

这是最常见也是最简单的创建对象的方式。通过这种方式,我们可以调用定义的构造函数(无参的和带参数的)来创建对象。
如:

public static void main(String[] args) {
	User user1 = new User();
	User user2 = new User("Lucy", (short) 18);

	System.out.println(user1);
	System.out.println(user2);
}

输出:

------ 调用无参构造方法 ------
------ 调用有参构造方法:userName[Lucy], age[18] ------
User(userName=null, age=0)
User(userName=Lucy, age=18)

2、使用 Constructor 类的 newInstance 方法

通过 Class 类对象的 getConstructor() 或 getDeclaredConstructor() 方法获得构造器(Constructor)对象,并调用其 newInstance() 方法创建对象,适用于无参和有参构造方法。
如:

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException,
		InvocationTargetException, InstantiationException {

	// 获取 User 的无参构造函数,并创建对象
	User user1 = User.class.getDeclaredConstructor().newInstance();
	// 获取 User 的第一个参数是String, 第二个参数是short的构造函数,并创建对象
	User user2 = User.class.getDeclaredConstructor(String.class, short.class).newInstance("Lili", (short)17);

	System.out.println(user1);
	System.out.println(user2);
}

输出:

------ 调用无参构造方法 ------
------ 调用有参构造方法:userName[Lili], age[17] ------
User(userName=null, age=0)
User(userName=Lili, age=17)

这是通过反射创建对象的一种方式,这里 getConstructor() 和 getDeclaredConstructor() 区别是:

  • getConstructor : 构造函数的访问权限必须是 public
  • getDeclaredConstructor : 构造函数可以是任意访问权限

通过这种方式可以调用类私有的构造方法,但需要禁用构造器对象的安全检查。
如:

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException,
		InvocationTargetException, InstantiationException {

	Constructor<User> constructor = User.class.getDeclaredConstructor();
	// 禁用 constructor 构造器对象的安全检查
	constructor.setAccessible(true);

	User user = constructor.newInstance();

	System.out.println(user);
}

禁用安全检查后,就可以通过私有的构造方法创建新对象了。

3、使用 Class 类的 newInstance 方法

这种方式跟上边第 2 种方式差不多,都是通过反射来创建对象。内部其实也是先获取到 Constructor 构造器对象,然后再使用 Constructor 类的 newInstance 方法创建对象。不过通过这种方式创建对象,调用的是无参构造方法,并且此无参构造方法不能是 private (Class 类的 newInstance 方法中做了访问类型校验)。
如:

public static void main(String[] args) throws IllegalAccessException, InstantiationException {

	User user = User.class.newInstance();

	System.out.println(user);

}

输出:

------ 调用无参构造方法 ------
User(userName=null, age=0)

4、使用 clone 方法

想要通过 clone 方式创建对象,则需要对 User 类做一些改动:

  1. User 类实现 Cloneable 接口;
  2. User 类中,重写 Object 类中的 clone() 方法。

这里有个有意思的地方,查看 Cloneable 接口源码,我们可以看到,这个接口中一个方法也没有。重写的 clone() 方法是 Object 这个超级父类中的。

那这里的 Cloneable 接口有什么用呢?

其实,这里的 Cloneable 接口起到的是一个标记的作用,在JVM中,具有这个标记的对象才有可能被拷贝。那怎么才能从“有可能被拷贝” 转换为 “可以被拷贝” 呢? 那就是重写 Object 类的 clone() 方法。

改动后的 User 类如下:

@Data
public class User implements Cloneable{

    private String userName;
    private short age;

    protected User() {
        System.out.println("------ 调用无参构造方法 ------");
    }

    public User(String userName, short age) {
        this.userName = userName;
        this.age = age;

        System.out.println(String.format("------ 调用有参构造方法:userName[%s], age[%d] ------", userName, age));
    }

    @Override
    protected User clone() throws CloneNotSupportedException {
        System.out.println("------ 调用 clone 方法 ------");
        return (User)super.clone();
    }
}

接下来试下通过 clone 创建对象:

public static void main(String[] args) throws CloneNotSupportedException {

	User user = new User("Lilei", (short) 20);
	// 通过 clone 方式创建对象
	User cloneUser = user.clone();

	System.out.println(user);
	System.out.println(cloneUser);
	
	// 看下对象是否是同一个对象
	System.out.println(user == cloneUser);

}

输出:

------ 调用有参构造方法:userName[Lilei], age[20] ------
------ 调用 clone 方法 ------
User(userName=Lilei, age=20)
User(userName=Lilei, age=20)
false

通过 clone 方式创建对象,并不会调用任何构造函数,而是直接在内存中对二进制流的拷贝,要比直接 new 一个对象性能好很多。

注意:在使用 clone 方式创建对象时,需要注意深拷贝和浅拷贝的不同,不了解的可以百度下,这里就不进行扩展了。

5、使用 Serializable 反序列化方式

想要通过 Serializable 反序列化方式创建对象,则类需要实现 Serializable 接口。这里 Serializable 接口也是一个标记的作用,当我们让实体类实现 Serializable 接口时,其实是在告诉JVM此类可被默认的序列化机制序列化。

改动后的 User 类如下:

@Data
public class User implements Serializable {

    private static final long serialVersionUID = 7330451128451152699L;
    
    private String userName;
    private short age;

    protected User() {
        System.out.println("------ 调用无参构造方法 ------");
    }

    public User(String userName, short age) {
        this.userName = userName;
        this.age = age;

        System.out.println(String.format("------ 调用有参构造方法:userName[%s], age[%d] ------", userName, age));
    }

}

接下来试下通过反序列化方式创建对象:

public static void main(String[] args) throws IOException, ClassNotFoundException {

	User user = new User("Hanmeimei", (short) 18);

	// 通过 ObjectOutputStream 对象输出流,将 user 对象序列化写入到 user.txt 文件中
	ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:\\user.txt"));
	out.writeObject(user);
	out.close();

	// 通过 ObjectInputStream 对象输入流,将 user.txt 文件中内容反序列化,创建一个 User 对象
	ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\user.txt"));
	User user_1 = (User)in.readObject();

	// 再次通过 ObjectInputStream 对象输入流,将 user.txt 文件中内容反序列化,创建一个 User 对象
	in = new ObjectInputStream(new FileInputStream("D:\\user.txt"));
	User user_2 = (User)in.readObject();

	System.out.println(user);
	System.out.println(user_1);
	System.out.println(user_2);

	// 判断两次反序列化创建的对象是否是同一个对象
	System.out.println(user_1 == user_2);

}

输出:

------ 调用有参构造方法:userName[Hanmeimei], age[18] ------
User(userName=Hanmeimei, age=18)
User(userName=Hanmeimei, age=18)
User(userName=Hanmeimei, age=18)
false

通过 Serializable 反序列化方式创建对象,也不会调用任何构造函数。如果想控制哪个字段属性不被序列化和反序列化,可以通过 transient 关键字,将属性指定为瞬时态。

上边是使用的JVM默认的序列化和反序列化逻辑,如果想要使用自定义的序列化和反序列化,需要添加两个方法 private void writeObject(ObjectOutputStream out)private void readObject(ObjectInputStream in)

改动后的 User 类如下:

@Data
public class User implements Serializable {

    private static final long serialVersionUID = 7330451128451152699L;

    private String userName;
    private short age;

    public User() {
        System.out.println("------ 调用无参构造方法 ------");
    }

    public User(String userName, short age) {
        this.userName = userName;
        this.age = age;

        System.out.println(String.format("------ 调用有参构造方法:userName[%s], age[%d] ------", userName, age));
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.writeShort(age); // 序列化时,只写入 age 的值(哪些序列化哪些不序列化,都由我们自己控制)
        System.out.println("------ 调用 writeObject 方法 ------");
    }

    private void readObject(ObjectInputStream in) throws IOException,
            ClassNotFoundException {
        age = in.readShort(); // 反序列化时,只读取 age 的值,这个顺序需要跟写入顺序一致
        System.out.println("------ 调用 readObject 方法 ------");
    }

}

再来试下反序列化方式创建对象:

public static void main(String[] args) throws IOException, ClassNotFoundException {

	User user = new User("Hanmeimei", (short) 18);

	// 通过 ObjectOutputStream 对象输出流,将 user 对象序列化写入到 user.txt 文件中
	ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:\\user.txt"));
	out.writeObject(user);
	out.close();

	// 通过 ObjectInputStream 对象输入流,将 user.txt 文件中内容反序列化,创建一个 User 对象
	ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\user.txt"));
	User user_1 = (User)in.readObject();

	System.out.println(user);
	System.out.println(user_1);

}

输出:

------ 调用有参构造方法:userName[Hanmeimei], age[18] ------
------ 调用 writeObject 方法 ------
------ 调用 readObject 方法 ------
User(userName=Hanmeimei, age=18)
User(userName=null, age=18)

这里为什么加上 writeObjectreadObject 方法后,就可以实现自定义的序列化和反序列化呢?这是因为 ObjectOutputStream 和 ObjectInputStream 使用反射来寻找是否声明了这两个方法。如果声明了这俩方法,并且访问类型是私有的,则使用这两个方法来进行序列化和反序列化。

注意1:使用自定义序列化和反序列化时,可以序列化和反序列化瞬态变量,因此 transient 没有效果。

注意2:serialVersionUID 用来表明实现序列化类的不同版本间的兼容性。如果类内容没有修改,或者修改后的类兼容之前的类,请不要修改此值,否则之前序列化的类在反序列化时会报错。

6、使用 Externalizable 反序列化方式

Externalizable 是 Serializable 接口的子接口,它也允许我们控制整个序列化过程。通过这种方式反序列化创建对象时,会先调用默认的无参构造函数创建对象(访问类型必须是 public ),然后再将反序列化读取到的值填充到对象中。

使用 Externalizable 方式,需要实现 writeExternal(ObjectOutput out) 和 readExternal(ObjectInput in) 方法的逻辑。

我们来看下 User 改动后:

@Data
public class User implements Externalizable {

    private static final long serialVersionUID = 7330451128451152697L;

    private String userName;
    private short age;

    public User() {
        System.out.println("------ 调用无参构造方法 ------");
    }

    public User(String userName, short age) {
        this.userName = userName;
        this.age = age;

        System.out.println(String.format("------ 调用有参构造方法:userName[%s], age[%d] ------", userName, age));
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeShort(age); // 序列化时,只写入 age 的值(哪些序列化哪些不序列化,都由我们自己控制)
        System.out.println("------ 调用 writeExternal 方法 ------");
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        age = in.readShort(); // 反序列化时,只读取 age 的值,这个顺序需要跟写入顺序一致
        System.out.println("------ 调用 readExternal 方法 ------");
    }
}

接下来试下通过反序列化方式创建对象:

public static void main(String[] args) throws IOException, ClassNotFoundException {

	User user = new User("Hanmeimei", (short) 18);

	// 通过 ObjectOutputStream 对象输出流,将 user 对象序列化写入到 user.txt 文件中
	ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:\\user.txt"));
	out.writeObject(user);
	out.close();
	
	// 通过 ObjectInputStream 对象输入流,将 user.txt 文件中内容反序列化,创建一个 User 对象
	ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\user.txt"));
	User user_1 = (User)in.readObject();
	
	System.out.println(user);
	System.out.println(user_1);
	
}

输出:

------ 调用有参构造方法:userName[Hanmeimei], age[18] ------
------ 调用 writeExternal 方法 ------
------ 调用无参构造方法 ------
------ 调用 readExternal 方法 ------
User(userName=Hanmeimei, age=18)
User(userName=null, age=18)

注意1:使用 Externalizable,可以序列化/反序列化瞬态变量,因此 transient 没有效果。

注意2:serialVersionUID 用来表明实现序列化类的不同版本间的兼容性。如果类内容没有修改,或者修改后的类兼容之前的类,请不要修改此值,否则之前序列化的类在反序列化时会报错。


6种创建对象方式的总结

最后,我们来看下6种创建对象方式的区别:

方式是否用到构造函数特点
使用 new 关键字简单
使用 Constructor 类的 newInstance可以使用任何访问类型的构造函数
使用 Class 类的 newInstance只能使用非私有的无参构造函数
使用 clone 方法直接在内存中对二进制流的拷贝,性能好
使用 Serializable 反序列化
使用 Externalizable 反序列化相比 Serializable ,可以实现构造方法的初始化过程(必须是 public 的无参构造方法)
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓呆同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值