直接看例子
package domain;
public class Subject {
private String name;
private int score;
public Subject(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "Subject{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
package domain;
public class Student implements Cloneable {
//基础类型和String不可变对象
private String name;
private int age;
//引用类型
private Subject subject;
public Student(String name, int age, Subject subject) {
this.name = name;
this.age = age;
this.subject = subject;
}
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;
}
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", subject=" + subject +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
//直接拷贝
return super.clone();
}
}
import domain.Student;
import domain.Subject;
class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Subject subject = new Subject("math", 90);
Student student = new Student("pjq", 25, subject);//原对象
Student student_copy = (Student) student.clone();//拷贝对象
//修改原对象
student.setName("jqp");
student.setAge(26);
subject.setName("english");
subject.setScore(95);
student.setSubject(subject);
System.out.println(student);
System.out.println(student_copy);
}
}
输出结果:
Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}
Student{name=‘pjq’, age=25, subject=Subject{name=‘english’, score=95}}
对于引用类型,如果是浅拷贝,那么更改其中一个会影响多个。基本类型是值传递的,更改一个不会影响拷贝的。而字符串类型是存在常量池StringTable中的,更改相当于在StringTable中新创建一个字符串,其实就是深拷贝。
自己编写的类,如何实现深拷贝呢?一种方法是重写clone方法。
package domain;
public class Subject implements Cloneable{
private String name;
private int score;
public Subject(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "Subject{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
//Subject 如果也有引用类型的成员属性,也应该和 Student 类一样实现
return super.clone();
}
}
package domain;
public class Student implements Cloneable {
//基础类型和String不可变对象
private String name;
private int age;
//引用类型
private Subject subject;
public Student(String name, int age, Subject subject) {
this.name = name;
this.age = age;
this.subject = subject;
}
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;
}
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", subject=" + subject +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
//深拷贝
Student student = (Student) super.clone();
student.subject = (Subject) subject.clone();
return student;
}
}
import domain.Student;
import domain.Subject;
class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Subject subject = new Subject("math", 90);
Student student = new Student("pjq", 25, subject);//原对象
Student student_copy = (Student) student.clone();//拷贝对象
//修改原对象
student.setName("jqp");
student.setAge(26);
subject.setName("english");
subject.setScore(95);
student.setSubject(subject);
System.out.println(student);
System.out.println(student_copy);
}
}
输出结果:
Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}
Student{name=‘pjq’, age=25, subject=Subject{name=‘math’, score=90}}
通过结果可以看出此时是深拷贝
上文给出了一种深拷贝的方法,即通过重写clone的方式,但是如果不是我们自己写的类,例如,想要深拷贝一个容器内的所有内容,怎么实现?以下先展示几种错误方式。
import domain.Student;
import domain.Subject;
import java.util.ArrayList;
import java.util.List;
class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Subject subject = new Subject("math", 90);
Student student = new Student("pjq", 25, subject);
List<Student> students = new ArrayList<>();//原始List
students.add(student);
List<Student> students_copy = new ArrayList<>();//拷贝的List
for (int i = 0; i < students.size(); ++i) {
students_copy.add(students.get(i));
}
//修改原始List
students.get(0).setName("jqp");
students.get(0).setAge(26);
students.get(0).getSubject().setName("english");
students.get(0).getSubject().setScore(95);
System.out.println(students);
System.out.println(students_copy);
}
}
输出结果:
[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]
[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]
从结果看出,毫无疑问是浅拷贝。
class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Subject subject = new Subject("math", 90);
Student student = new Student("pjq", 25, subject);
List<Student> students = new ArrayList<>();//原始List
students.add(student);
List<Student> students_copy = new ArrayList<>(students);//拷贝的List
//修改原始List
students.get(0).setName("jqp");
students.get(0).setAge(26);
students.get(0).getSubject().setName("english");
students.get(0).getSubject().setScore(95);
System.out.println(students);
System.out.println(students_copy);
}
}
输出结果:
[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]
[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]
同样是浅拷贝
class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Subject subject = new Subject("math", 90);
Student student = new Student("pjq", 25, subject);
List<Student> students = new ArrayList<>();//原始List
students.add(student);
List<Student> students_copy = new ArrayList<>();//拷贝的List
students_copy.addAll(students);
//修改原始List
students.get(0).setName("jqp");
students.get(0).setAge(26);
students.get(0).getSubject().setName("english");
students.get(0).getSubject().setScore(95);
System.out.println(students);
System.out.println(students_copy);
}
}
输出结果:
[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]
[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]
还是浅拷贝。
其实查看源码就知道,list.addAll()是使用的System.arraycopy,而System.arraycopy是一个浅拷贝,那么Array.copyOf显然也是浅拷贝,它底层也是调用的System.arraycopy。
那么到底如何深拷贝呢?
显然,深拷贝要为容器中的每个元素重新分配内存。
import domain.Student;
import domain.Subject;
import java.util.ArrayList;
import java.util.List;
class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Subject subject = new Subject("math", 90);
Student student = new Student("pjq", 25, subject);
List<Student> students = new ArrayList<>();//原始List
students.add(student);
List<Student> students_copy = new ArrayList<>();//拷贝的List
for (int i = 0; i < students.size(); ++i) {
//不仅student对象要分配内存,它student对象内部的subject对象也要重新分配,否则还是subject对象还是浅拷贝
Subject sbuject_temp = new Subject(students.get(i).getSubject().getName(), students.get(i).getSubject().getScore());
Student student_temp = new Student(students.get(i).getName(), students.get(i).getAge(), sbuject_temp);
students_copy.add(student_temp);
}
//修改原始List
students.get(0).setName("jqp");
students.get(0).setAge(26);
students.get(0).getSubject().setName("english");
students.get(0).getSubject().setScore(95);
System.out.println(students);
System.out.println(students_copy);
}
}
输出结果:
[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]
[Student{name=‘pjq’, age=25, subject=Subject{name=‘math’, score=90}}]
实现了深拷贝,但是还存在一个明显的问题:不仅要为List内部的每一个Student重新分配内存,每一个Student内部的每一个Subject还要重新分配内存,如果Subject内部还有其他引用类型,显然也要重新分配内存,太麻烦了。
import domain.Student;
import domain.Subject;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
class Main {
public static List<Student> deepCopy(List<Student> students) throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(students);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (List<Student>) objectInputStream.readObject();
}
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
Subject subject = new Subject("math", 90);
Student student = new Student("pjq", 25, subject);
List<Student> students = new ArrayList<>();//原始List
students.add(student);
List<Student> students_copy = deepCopy(students);//拷贝的List
//修改原始List
students.get(0).setName("jqp");
students.get(0).setAge(26);
students.get(0).getSubject().setName("english");
students.get(0).getSubject().setScore(95);
System.out.println(students);
System.out.println(students_copy);
}
}
输出结果:
[Student{name=‘jqp’, age=26, subject=Subject{name=‘english’, score=95}}]
[Student{name=‘pjq’, age=25, subject=Subject{name=‘math’, score=90}}]
注意:这种方法需要Student类和Subject类实现Serializable接口。
Java 浅拷贝和深拷贝