Java中创建对象的方式有四种,分别为:new关键字、反射机制、clone方法、反序列化操作等。作为Java中必知必懂的一个知识点,准备亲测和分析一番,将需要注意的一些细节进行总结。
目录
以Book实体类为例:
public class Book implements Serializable, Cloneable {
public String bookName; //基本类型
public PublishTime publishTime; //引用类型
public Book() {
}
public Book(String bookName) {
this.bookName = bookName;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
// 简单进行重写Object的clone()
// @Override
// public Object clone() throws CloneNotSupportedException {
// return super.clone();
// }
}
1、new关键字
用法很简单,通过new关键字调用Book的构造器(无参或有参):
// new的方式
public static void main(String[] args) {
Book bookRedis = new Book("Redis设计与实现");
System.out.println("创建对象方式之-----new------>" + bookRedis.getBookName());
}
# 打印结果:
创建对象方式之-----new------>Redis设计与实现
2、反射机制
Java中把动态获取类的信息以及动态调用对象方法的功能称之为反射。换言之,通过反射机制可以发现对象的类型,找到类中包含的方法和属性;可以创建对象并访问任意对象方法和属性。而反射有三种方式可以获取Class类(如下面代码标记的①②③所示),Class类的newInstance()则可以创建新的对象。
通过Class类的newInstance()创建对象:
// 反射方式Class类
public static void main(String[] args) {
try {
// Book bookLinux = bookRedis.getClass().newInstance(); //①
// Book bookLinux = Book.class.newInstance(); //②
Book bookLinux = (Book) Class.forName("com.xxwei.demo.controller.Book").newInstance(); //③
bookLinux.setBookName("鸟哥的Linux私房菜(基础学习篇)");
System.out.println("创建对象方式之-----反射之Class类------>" + bookLinux.getBookName());
} catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) {
e.printStackTrace();
}
}
# 打印结果:
创建对象方式之-----反射之Class类------>鸟哥的Linux私房菜(基础学习篇)
也可以通过Constructor类的newInstance()创建对象:(上面Class类的newInstance()方式本质是通过Constructor类的newInstance()实现的)
// 反射方式之Constructor类
public static void main(String[] args) {
try {
Constructor<Book> bookConstructor = Book.class.getConstructor();
Book bookLinux2 = bookConstructor.newInstance();
bookLinux2.setBookName("鸟哥的Linux私房菜(服务器架设篇)");
System.out.println("创建对象方式之-----反射之Constructor类------>" + bookLinux2.getBookName());
} catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
}
# 打印结果:
创建对象方式之-----反射之Constructor类------>鸟哥的Linux私房菜(服务器架设篇)
3、clone()方法
注意:使用Object的clone()方法,Book类需要实现Cloneable接口,它是一个空接口,标识实现该接口的类的实例可被克隆。
// 克隆方式 -- #Object.clone()
public static void main(String[] args) {
try {
// Book类没有重写clone()
Book bookJava = new Book("Java核心技术 卷I");
Book newObj = (Book) bookJava.clone();
System.out.println("创建对象方式之-----clone------>" + (bookJava.getBookName()));
System.out.println("创建对象方式之-----clone------>" + (newObj.getBookName()));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
# 打印结果:
创建对象方式之-----clone------>Java核心技术 卷I
创建对象方式之-----clone------>Java核心技术 卷I
从打印的结果来看:Book类不重写clone()方法或重写clone()方法,新对象中都复制了原型对象的基本类型变量。那么对于引用类型的变量呢?继续看后面拓展的分析。
4、反序列化
注意:Book类需要实现Serializable接口,这是一个空接口,标识实现该接口的类的实例可被序列化。除了原型对象使用static、transient修饰的变量不参与序列化外,反序列化后得到的新对象和原型对象一致。
// 反序列化方式(本质上是一种深度克隆,或深拷贝)
public static void main(String[] args) {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("E:\\test\\test.txt"));
ObjectInputStream in = new ObjectInputStream(new FileInputStream("E:\\test\\test.txt"))
) {
Book bookMySQL = new Book("MySQL必知必会的技巧");
// 对象序列化
out.writeObject(bookMySQL);
out.flush();
// 尝试修改序列化对象的属性(这里是不起作用的)
bookMySQL.setBookName("鸟哥的Linux私房菜");
// 反序列化
Book mysqlBook = (Book) in.readObject();
System.out.println("创建对象方式之-----反序列化------>" + mysqlBook.getBookName());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
# 打印结果:
创建对象方式之-----反序列化------>MySQL必知必会的技巧
拓展 —— 对象克隆中的深拷贝和浅拷贝问题
Java中生成对象的基本方式就这4种,其中new方式最简单而普遍使用。
实际上,对于克隆对象的做法可能会有些额外的要求,比如克隆时:只复制原型对象的本身及基本类型的成员变量(不需要引用类型的成员变量),或者需要将原型对象的所有成员变量一并复制到新对象,前者就是浅拷贝,后者为深拷贝,二者的区别就是克隆对象时是否支持对引用类型成员变量的复制。
一个包含了基本类型和引用类型变量的实体类(如Book类),实现了Cloneable接口并进行简单的重写clone()方法,克隆出的新对象结果是浅拷贝类型。如果要做到深度拷贝,Book类及关联的引用类都要实现Cloneable接口并重写clone()方法,在Book类重写时,特别注意要将引用类型成员变量重新进行赋值初始化,如下:
public class Book implements Serializable, Cloneable {
@Override
public Object clone() throws CloneNotSupportedException {
Book book = (Book) super.clone();
book.publishTime = (PublishTime) publishTime.clone(); //attention!!!
return book;
}
}
class PublishTime implements Cloneable{
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
然而,现在有很多的工具类是支持普通对象或集合对象的克隆,没必要再重复造轮子了,推荐一款便捷好用的工具类:
- org.apache.commons.beanUtils的工具类BeanUtils和PropertyUtils,都提供了copyProperties()方法克隆对象;
<!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>x.x.x</version>
</dependency>
小结一下:
至此,通过亲测重新巩固了一下Java中创建对象的几种方式,还区分了对象克隆时出现的深拷贝和浅拷贝问题,最后推荐了经常使用的工具类,不积硅步无以至千里,点滴付出终将有所收获,共同进步 ~