什么是值传递和引用传递?
- 值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
- 引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身,两者指向同一片内存空间。所以对引用对象进行操作会同时改变原对象。
java中不存在引用传递,只有值传递。即不存在变量a指向变量b,变量b指向对象的这种情况。
clone()
clone方法是Java中拷贝对象的方法
在Java中拷贝对象其实是指复制一个与原对象一样的新对象出来,但是在Java中 赋值 = 是复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的。
看下面例子:
Animal a1 = new Animal();
a1.category = "人类";
Animal a2 = a1;
System.out.println(a1==a2);//true
a2.category = "猫科动物";
System.out.println(a1.category);//猫科动物
显然,a1==a2返回为true,a1和a2指向的是同一个引用
并且对对象a2的属性值进行修改后,a1的属性值也跟着改变,因此这不是拷贝,要实现拷贝,需要用到Object对象的clone()方法,实现对对象中各个属性的复制,但它的可见范围是protected的
protected native Object clone() throws CloneNotSupportedException;
-
所以实体类使用clone()方法的前提是:实现Cloneable接口,这是一个标记接口,自身没有方法,这算是一种约定。调用clone方法时,会去判断有没有实现Cloneable接口,没有实现Cloneable的话会抛异常CloneNotSupportedException。
-
覆盖clone()方法,可见性提升为public。
public class Animal implements Cloneable {
String category;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Animal a1 = new Animal();
a1.category = "人类";
Animal a2 = (Animal) a1.clone();
System.out.println(a1 == a2);
a2.category = "猫科动物";
System.out.println(a1.category);
}
}
//输出:
false
人类
拷贝了一个新对象,与原对象引用不同,因此返回false,并且修改新对象的属性值,旧对象的属性值不改变
浅拷贝
当类中含有引用类型的属性时,新对象和旧对象的引⽤类型属性指向的是同⼀个对象,即为浅拷贝
public class Animal implements Cloneable {
String category;
Person person;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Animal a1 = new Animal();
Person person = new Person("旧对象");
a1.category = "人类";
a1.person = person;
Animal a2 = (Animal) a1.clone();
a2.category = "新人类";
a2.person.name = "新对象";
System.out.println(a1.category + ":" + a1.person.name);//人类:新对象
}
}
如上,改变 a2中的引用类型 person.name = "新对象"后,a1的的引用类型 person.name 也发生了改变。
也就是说,新对象和旧对象的引⽤类型属性指向的是同⼀个对象,因此当改变新对象的引用类型(person)的属性值时,旧对象的引用类型的属性值也被改变。因此只是浅拷贝
这里可能就有疑问了,String类型也是引用类型,为什么进行了深拷贝,即如上所示,改变a2的属性值category,但却没有改变a1的category?
String类型有点特殊,它本身没有实现Cloneable接口,故根本无法克隆,只能传递引用(注意:Java只有值传递,只是这里传递是原来引用地址值)。在clone()后,克隆后的对象开始也是指向的原来引用地址值(刚克隆完检查a1.category == a2.category 为true),但是一旦String的值发生改变(String作为不可更改的类——immutable class,在新赋值的时候,会创建了一个新的对象)就改变了克隆后对象指向的地址,让它指向了一个新的String地址,不会影响原对象的指向和值,原来的String对象还是指向的它自己的的地址。这样String在拷贝的时候就表现出了深拷贝的特点
深拷贝
当类中含有引用类型的属性时,新对象和旧对象的引⽤类型属性指向的不是同⼀个对象,即为深拷贝
要实现深拷贝,在clone方法中不仅调用了super.clone,而且需要调用Person对象的clone方法(Person也要实现Cloneable接口并重写clone方法),从而实现了深拷贝。
public class Animal implements Cloneable {
String category;
Person person;
@Override
protected Object clone() throws CloneNotSupportedException {
Animal animal = null;
animal = (Animal) super.clone();
animal.person = (Person) person.clone();
return animal;
}
public static void main(String[] args) throws CloneNotSupportedException {
Animal a1 = new Animal();
Person person = new Person("旧对象");
a1.category = "人类";
a1.person = person;
Animal a2 = (Animal) a1.clone();
a2.person.name = "新对象";
System.out.println(a1.person.name);//旧对象
}
}
可以看到,新对象的引用类型 person 不会再受到旧对象的影响。
但是,在EffectiveJava中,反对使用clone方法来进行克隆,详情关注 谨慎重写 clone 方法
关于作者
来自一线程序员Seven的探索与实践,持续学习迭代中~
本文已收录于我的个人博客:https://www.seven97.top
公众号:seven97,欢迎关注~