目录
一、引用拷贝
引用的拷贝其实将一个引用的保存的地址给另外一个引用。他们指向了同一个对象。
public class Test {
public static void main(String[] args) {
Hero hero1 = new Hero(15,"Timor");
Hero hero2 = hero1;
System.out.println(hero1);
System.out.println(hero2);
}
}
class Hero {
public int age;
public String name;
public Hero(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Hero{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
结果
内存图:
平时刷题的时候我们会用到引用的拷贝,比如二叉树和链表的题,定义ListNode cur = head;
等。
二、浅拷贝 vs 深拷贝
1、实现Cloneable接口
我们在操作数组的时候,有时候会需要拷贝数组:
关于拷贝数组,我写了一篇关于数组的总结,里面包含了数组的四种拷贝方式:关于数组的总结
public static void main(String[] args) {
int[] array = {1,2,3};
int[] copy = array.clone();
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(copy));
}
结果:
当我们像拷贝自定义的类的时候:
出现了编译错误。对于这种情况,我们需要让自定义类实现Cloneable
接口:
此时还是编译错误,对,没错,我没重写Cloneable
的方法。
public interface Cloneable {
}
不是我不想写,是这该写啥?
我们发现数组的clone();
方法是重写Object的方法。来吧,照葫芦画瓢,在自定义类试试。
方法:
1、alt + insert
2、选中Override Methods
3、选中clone();
方法回车就完成了。
2、抛异常与类型转换。
抛异常:
类型转换:
因为
clone();
方法的返回值是Object,所以是向下转型,需要强制类型转换。
public static void main1(String[] args) throws CloneNotSupportedException {
Hero hero1 = new Hero(15,"Timor");
Hero hero2 = (Hero) hero1.clone();
}
3、这可是浅拷贝!
至于为什么说是浅拷贝,我们先看代码:
public static void main1(String[] args) throws CloneNotSupportedException {
Hero hero1 = new Hero(15,"Timor");
Hero hero2 = (Hero) hero1.clone();
System.out.println(hero1);
System.out.println(hero2);
}
结果:
我们来验证是不是真正的拷贝了。
public static void main(String[] args) throws CloneNotSupportedException {
Hero hero1 = new Hero(15,"Timor");
Hero hero2 = (Hero) hero1.clone();
System.out.println("克隆前");
System.out.println(hero1);
System.out.println(hero2);
hero2.age = 22;
hero2.name = "Ashe";
System.out.println("克隆后");
System.out.println(hero1);
System.out.println(hero2);
}
内存图
结果:
确实是修改成功了。并且原来的值没有改变,但是这个其实不是正在的深拷贝。
4、小可爱,这是陷阱
深拷贝会对引用类型重新在创新一次(包括值类型),在新对象做的任何修改都不会影响到源对象本身。
来看看这个例子你就明白了!
我们重新创建一个 技能类 :
class Skill {
public Skill skill;
}
并且在 英雄类 中加入这个 技能 属性 :
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Hero hero1 = new Hero(15,"Timor");
hero1.skills.skill = "种蘑菇";
Hero hero2 = (Hero) hero1.clone();
System.out.println("克隆前");
System.out.println(hero1);
System.out.println(hero2);
hero2.age = 22;
hero2.name = "Ashe";
hero2.skills.skill = "万箭齐发";
System.out.println("克隆后");
System.out.println(hero1);
System.out.println(hero2);
}
}
class Hero implements Cloneable{
public int age;
public String name;
public Skill skills;
public Hero(int age, String name) {
this.age = age;
this.name = name;
this.skills = new Skill();
}
@Override
public String toString() {
return "Hero{" +
"age=" + age +
", name='" + name + '\'' +
", skills=" + skills.skill +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Skill {
public String skill;
}
内存图:
结果:
哦豁,提莫会万箭齐发了?那么蛮王有什么想法?
言归正传,说明这个还是浅拷贝,修改一个引用的值会影响到另外一个引用。
5、我们如果想把它改为深拷贝,可以这样做
1、让技能类(class Skill )实现Cloneable
接口,并重写克隆方法
class Skill implements Cloneable{
public String skill;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
2、在英雄类(class Hero)中修改克隆方法
@Override
protected Object clone() throws CloneNotSupportedException {
Hero heroClone = (Hero) super.clone();
heroClone.skills = (Skill) this.skills.clone();
return heroClone;
}
6、完整代码
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Hero hero1 = new Hero(15,"Timor");
hero1.skills.skill = "种蘑菇";
Hero hero2 = (Hero) hero1.clone();
System.out.println("克隆前");
System.out.println(hero1);
System.out.println(hero2);
hero2.age = 22;
hero2.name = "Ashe";
hero2.skills.skill = "万箭齐发";
System.out.println("克隆后");
System.out.println(hero1);
System.out.println(hero2);
}
}
class Hero implements Cloneable{
public int age;
public String name;
public Skill skills;
public Hero(int age, String name) {
this.age = age;
this.name = name;
this.skills = new Skill();
}
@Override
public String toString() {
return "Hero{" +
"age=" + age +
", name='" + name + '\'' +
", skills=" + skills.skill +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Hero heroClone = (Hero) super.clone();
heroClone.skills = (Skill) this.skills.clone();
return heroClone;
}
}
class Skill implements Cloneable{
public String skill;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
内存图:
我们来看下结果:
完成,收工!
三、关于深浅拷贝的区别(面试题)
1、一般的数组拷贝,因为数组中的数据类型是基本类型,我们修改原数组的值不会影响到克隆数组的值,那么可以理解为深拷贝;(注意可以理解)
2、如果自定义类型实现了拷贝,并且这个类中还有自定义类(可以理解为这个类中的类是这个类的属性),我们如果只是简单的克隆这个类,而不管这个类的"属性",那么它还是浅拷贝。如果我们"更深层次的’'的拷贝这个类。那么就是深拷贝了!
浅拷贝只需要考虑所需要复制的对象,不管对象里还有没有对象。
深拷贝则不仅需要考虑所复制的对象,还需要考虑对面里面还有没有对象。
深拷贝会对引用类型重新在创新一次(包括值类型),在新对象做的任何修改都不会影响到源对象本身。
3、本质上数组的拷贝还是浅拷贝,面试大哥要是问的话就说是浅拷贝。