编程自学指南:java程序设计开发,Java 克隆对象浅拷贝、深拷贝
一、课程信息
学习目标
- 理解克隆对象的概念和用途。
- 掌握浅拷贝和深拷贝的区别。
- 学会在 Java 中实现浅拷贝和深拷贝。
- 能够根据实际需求选择合适的拷贝方式。
二、课程导入
生活实例引入
- 以复印文件为例,复印一份文件就相当于创建一个文件的副本。在 Java 中,我们也有类似的需求,需要创建对象的副本,这就是克隆对象。
- 提问学生生活中还有哪些类似克隆的例子,引导他们思考克隆在编程中的应用。
三、克隆对象的基本概念
什么是克隆对象
- 在 Java 中,克隆对象就是创建一个与现有对象具有相同属性值的新对象。克隆可以避免重新创建对象并手动设置属性值,提高代码的效率和可维护性。
为什么需要克隆对象
- 当我们需要创建一个对象的副本,并且希望对副本进行修改而不影响原对象时,就可以使用克隆。例如,在游戏中,每个角色都有自己的属性,当需要创建多个相同类型的角色时,就可以通过克隆来实现。
Java 中的克隆机制
- Java 提供了一种克隆机制,通过实现
Cloneable
接口和重写 clone()
方法来实现对象的克隆。
四、浅拷贝
浅拷贝的定义
- 浅拷贝是指创建一个新对象,新对象的属性值与原对象相同,但对于引用类型的属性,新对象和原对象共享同一个引用,即它们指向内存中的同一个对象。
实现浅拷贝的步骤
- 让类实现
Cloneable
接口。 - 重写
clone()
方法,在方法中调用 super.clone()
。
示例代码
// 定义一个简单的类
class Address {
String city;
public Address(String city) {
this.city = city;
}
}
// 定义一个包含引用类型属性的类
class Person implements Cloneable {
String name;
Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// 测试浅拷贝
public class ShallowCopyDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("Beijing");
Person person1 = new Person("Alice", address);
// 克隆对象
Person person2 = (Person) person1.clone();
// 修改原对象的引用类型属性
person1.address.city = "Shanghai";
// 输出克隆对象的地址信息
System.out.println(person2.address.city); // 输出 "Shanghai"
}
}
代码解释
- 在上述代码中,
Person
类实现了 Cloneable
接口并重写了 clone()
方法。当调用 person1.clone()
时,会创建一个新的 Person
对象 person2
,但 person2
的 address
属性和 person1
的 address
属性指向同一个 Address
对象。因此,当修改 person1.address.city
时,person2.address.city
也会受到影响。
浅拷贝的优缺点
- 优点:实现简单,效率高。
- 缺点:对于引用类型的属性,新对象和原对象共享同一个引用,修改其中一个对象的引用类型属性会影响另一个对象。
五、深拷贝
深拷贝的定义
- 深拷贝是指创建一个新对象,新对象的属性值与原对象相同,对于引用类型的属性,也会创建一个新的对象,新对象和原对象的引用类型属性指向不同的对象。
实现深拷贝的方法
方法一:手动实现
- 对于每个引用类型的属性,在
clone()
方法中递归地调用其 clone()
方法。
示例代码
// 定义一个简单的类
class Address implements Cloneable {
String city;
public Address(String city) {
this.city = city;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// 定义一个包含引用类型属性的类
class Person implements Cloneable {
String name;
Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person clone = (Person) super.clone();
// 手动克隆引用类型属性
clone.address = (Address) address.clone();
return clone;
}
}
// 测试深拷贝
public class DeepCopyDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("Beijing");
Person person1 = new Person("Alice", address);
// 克隆对象
Person person2 = (Person) person1.clone();
// 修改原对象的引用类型属性
person1.address.city = "Shanghai";
// 输出克隆对象的地址信息
System.out.println(person2.address.city); // 输出 "Beijing"
}
}
代码解释
- 在上述代码中,
Address
类和 Person
类都实现了 Cloneable
接口并重写了 clone()
方法。在 Person
类的 clone()
方法中,除了调用 super.clone()
外,还手动克隆了 address
属性,这样 person2
的 address
属性和 person1
的 address
属性指向不同的 Address
对象。因此,当修改 person1.address.city
时,person2.address.city
不会受到影响。
方法二:使用序列化和反序列化
- 将对象序列化为字节流,然后再将字节流反序列化为新对象,这样可以实现深拷贝。
示例代码
import java.io.*;
// 定义一个简单的类,实现 Serializable 接口
class Address implements Serializable {
String city;
public Address(String city) {
this.city = city;
}
}
// 定义一个包含引用类型属性的类,实现 Serializable 接口
class Person implements Serializable {
String name;
Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
// 深拷贝方法
public Person deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Person) ois.readObject();
}
}
// 测试深拷贝
public class DeepCopyBySerializationDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Address address = new Address("Beijing");
Person person1 = new Person("Alice", address);
// 克隆对象
Person person2 = person1.deepClone();
// 修改原对象的引用类型属性
person1.address.city = "Shanghai";
// 输出克隆对象的地址信息
System.out.println(person2.address.city); // 输出 "Beijing"
}
}
代码解释
- 在上述代码中,
Address
类和 Person
类都实现了 Serializable
接口。Person
类提供了一个 deepClone()
方法,该方法通过将对象序列化为字节流,然后再将字节流反序列化为新对象,实现了深拷贝。
深拷贝的优缺点
- 优点:新对象和原对象完全独立,修改其中一个对象的引用类型属性不会影响另一个对象。
- 缺点:实现相对复杂,效率较低,尤其是使用序列化和反序列化的方式。
六、浅拷贝和深拷贝的对比
对比表格
对比项 | 浅拷贝 | 深拷贝 |
---|
定义 | 新对象和原对象共享引用类型属性的引用 | 新对象和原对象的引用类型属性指向不同的对象 |
实现方式 | 实现 Cloneable 接口,重写 clone() 方法,调用 super.clone() | 手动递归克隆引用类型属性或使用序列化和反序列化 |
效率 | 高 | 低 |
独立性 | 低,修改引用类型属性会影响原对象或克隆对象 | 高,修改引用类型属性不会影响原对象或克隆对象 |
适用场景 | 对象中不包含引用类型属性或引用类型属性不需要独立时 | 对象中包含引用类型属性且需要独立时 |
七、课堂练习
练习一
- 定义一个
Student
类,包含 name
和 score
两个属性,score
是一个 int
数组。实现 Student
类的浅拷贝和深拷贝,并编写测试代码验证。
练习二
- 定义一个
Company
类,包含 name
和 employees
两个属性,employees
是一个 List<Employee>
类型的列表,Employee
类包含 name
和 age
两个属性。实现 Company
类的深拷贝,并编写测试代码验证。
八、课程总结
知识回顾
- 回顾克隆对象的概念和用途。
- 总结浅拷贝和深拷贝的区别、实现方式和优缺点。
- 强调根据实际需求选择合适的拷贝方式。
常见问题解答
口诀总结
- “克隆对象有深浅,浅拷贝易效率显。引用共享有风险,修改影响两对象。深拷贝来更安全,属性独立不牵连。手动递归或序列,按需选择才周全。”
九、课后作业
作业一
- 定义一个
Book
类,包含 title
、author
和 price
三个属性,author
是一个 Author
类的对象,Author
类包含 name
和 age
两个属性。实现 Book
类的浅拷贝和深拷贝,并编写测试代码验证。
作业二
- 思考如果一个对象中包含多层嵌套的引用类型属性,如何实现深拷贝?