什么是值传递和引用传递?

  • 值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
  • 引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身,两者指向同一片内存空间。所以对引用对象进行操作会同时改变原对象。

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 方法


seven97_top
41 声望4 粉丝

程序员seven,记录seven的菜鸟成长之路。在线阅读网站:www.seven97.top