如果饿了就吃,困了就睡,渴了就喝,人生就太无趣了
作者:可耳(keer)
更新时间 : 2020年04月01日
源码地址:https://github.com/keer123456789/java_study_demo
1.概念
1.1 浅拷贝(浅复制、浅克隆):
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所拷贝的对象,而不复制它所引用的对象。
如图1:对象obj1
经过浅克隆生成对象obj2
,但是二者都指向相同地址,这样只要修改任意一个对象值,另一对象也会改变
1.2 深拷贝(深复制、深克隆):
被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍
如图2:对象obj1
经过深克隆生成对象obj2
,此时obj2
和obj1
指向不同的内存地址,修改任何一方,都不会引起另一方的改变。
2.不带属性对象
2.1 浅拷贝
public class User {
private String num;
public String getNum() {
return num;
}
public void setNum(String num) {
this.num = num;
}
public static void main(String[] args) {
User user1=new User();
user1.setNum("123456");
User user2= user1;
User user3=new User();
user3.setNum("123456");
System.out.println("克隆后,未做修改前,两用户的信息:");
System.out.println("User1.num:"+user1.getNum());
System.out.println("User2.num:"+user2.getNum());
System.out.println("User1和User2是否相等:"+user1.equals(user2));
user2.setNum("7890");
System.out.println("克隆后,修改User2.num=:"+user2.getNum()+",两用户信息如下:");
System.out.println("User1.num:"+user1.getNum());
System.out.println("User2.num:"+user2.getNum());
System.out.println("User1和User2是否相等:"+user1.equals(user2));
}
}
运行结果:
观察debug
运行时的内存地址(@
后面是该变量的相对内存地址):
可以看到user2
是经过user1
浅克隆的,二者都指向同一个内存地址,user3
是单独的User
对象的实例,虽然具有相同的值,但是user3
有单独的内存地址。
2.2 深拷贝
public class User implements Cloneable {
private String num;
public String getNum() {
return num;
}
public void setNum(String num) {
this.num = num;
}
@Override
protected User clone() throws CloneNotSupportedException {
return (User) super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
User user1=new User();
user1.setNum("123456");
User user2= user1.clone();
System.out.println("克隆后,未做修改前,两用户的信息:");
System.out.println("User1.num:"+user1.getNum());
System.out.println("User2.num:"+user2.getNum());
System.out.println("User1和User2是否相等:"+user1.equals(user2));
user2.setNum("7890");
System.out.println("克隆后,修改User2.num=:"+user2.getNum()+",两用户信息如下:");
System.out.println("User1.num:"+user1.getNum());
System.out.println("User2.num:"+user2.getNum());
System.out.println("User1和User2是否相等:"+user1.equals(user2));
}
}
运行结果:
可以看到对user2
进行修改,没有对user1
产生影响。
观察debug
运行时的内存地址:此时内存地址都是不同的。
3.带有属性对象
3.1 浅拷贝(属性对象没有实现Cloneable接口)
public class Address {
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
}
public class Student implements Cloneable {
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try {
stu = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
public static void main(String[] args) {
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student) stu1.clone();
System.out.println("克隆后,未做修改前,学生的信息:");
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
System.out.println("stu1 和stu2是否相等:"+stu1.equals(stu2));
System.out.println("stu1 和stu2的地址是否相等:"+stu1.getAddr().equals(stu2.getAddr()));
stu1.setNumber(3333);
stu1.getAddr().setAdd("中国");
System.out.println("克隆后,修改stu1后,学生的信息:");
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
System.out.println("stu1 和stu2是否相等:"+stu1.equals(stu2));
System.out.println("stu1 和stu2的地址是否相等:"+stu1.getAddr().equals(stu2.getAddr()));
}
}
运行结果:
修改其中一个学生的信息,两个学生的地址信息会随着改变而变化,但是学号不会改变。看一下运行时的内存情况:
在修改学生信息前:
- 两个学生的实例的内存地址是不同的
- 由于地址对象没有实现
Cloneable
接口,所以每个学生对象的实例的地址属性的内存地址都指向同一个地址。
在修改学生信息后:
- 地址的变化会随着改变而导致所有的学生实例中的地址属性也发生变化。
- 一个实例的学号改变,不会影响其他人的信息。
3.2 浅拷贝(属性对象实现Cloneable接口,但未显示的调用)
public class Address2 implements Cloneable{
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
@Override
public Object clone() {
Address2 address = null;
try {
address = (Address2) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return address;
}
}
public class Student2 implements Cloneable{
private int number;
private Address2 addr;
public Address2 getAddr() {
return addr;
}
public void setAddr(Address2 addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student2 stu = null;
try {
stu = (Student2) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
public static void main(String[] args) {
Address2 addr = new Address2();
addr.setAdd("杭州市");
Student2 stu1 = new Student2();
stu1.setNumber(123);
stu1.setAddr(addr);
Student2 stu2 = (Student2) stu1.clone();
System.out.println("克隆后,未做修改前,学生的信息:");
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
System.out.println("stu1 和stu2是否相等:"+stu1.equals(stu2));
System.out.println("stu1 和stu2的地址是否相等:"+stu1.getAddr().equals(stu2.getAddr()));
stu1.setNumber(3333);
stu1.getAddr().setAdd("中国");
System.out.println("克隆后,修改stu1后,学生的信息:");
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
System.out.println("stu1 和stu2是否相等:"+stu1.equals(stu2));
System.out.println("stu1 和stu2的地址是否相等:"+stu1.getAddr().equals(stu2.getAddr()));
}
}
运行结果:
和之前的相似,地址属性依旧没有进行克隆,只是将学生进行了浅拷贝。
运行时内存的分布:
- 两个学生实例的地址属性都指向同一个地址。
3.3 深拷贝(属性对象实现Cloneable接口,显示调用)
public class Address implements Cloneable{
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
@Override
public Object clone() {
Address address = null;
try {
address = (Address) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return address;
}
}
public class Student implements Cloneable {
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try {
stu = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
// 对象属性设置
stu.addr = (Address) addr.clone();
return stu;
}
public static void main(String[] args) {
Address addr = new Address();
addr.setAdd("北京市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student) stu1.clone();
System.out.println("克隆后,未做修改前,学生的信息:");
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
System.out.println("stu1 和stu2是否相等:" + stu1.equals(stu2));
System.out.println("stu1 和stu2的地址是否相等:" + stu1.getAddr().equals(stu2.getAddr()));
stu1.setNumber(3333);
stu1.getAddr().setAdd("中国");
System.out.println("克隆后,修改stu1后,学生的信息:");
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
System.out.println("stu1 和stu2是否相等:" + stu1.equals(stu2));
System.out.println("stu1 和stu2的地址是否相等:" + stu1.getAddr().equals(stu2.getAddr()));
}
}
运行结果:
此次克隆过后,两个学生实例的地址属性不是相等的,修改其中一个学生的地址属性,不会引起其实例的地址属性变化。
运行时内存的分布:
两个学生的地址属性的所在的内存地址是不同的。