美文网首页程序员
Java再回顾(2)—浅拷贝与深拷贝

Java再回顾(2)—浅拷贝与深拷贝

作者: 史小豪 | 来源:发表于2020-06-15 13:40 被阅读0次

前言

对象的拷贝是java开发中绕不过去的话题,实际开发中运用到的也很多。
有的时候,需要一份对象的复制结果,然后对复制结果进行裁剪、修改等,可是又不能影响到原对象,那么一个简单的'b=a'肯定就再难堪大任(只针对可变对象哦,对于不可变对象这种场景压根儿不存在),于是我们的主角clone就要闪亮登场。

clone()方法

clone()是object里的protected方法:
clone()是protected 的,即包可见和可继承。猜想应该是由于不知道子类具体的clone后的返回类型,于是设置成protected的,让子类来重写该方法。

    protected Object clone() throws CloneNotSupportedException {
        if (!(this instanceof Cloneable)) {
            throw new CloneNotSupportedException("Class " + getClass().getName() +
                                                 " doesn't implement Cloneable");
        }

        return internalClone();
    }

clone()方法要求子类实现Cloneable接口

public interface Cloneable {
}

Cloneable接口是java四大接口之一,如上面代码所示,Cloneable接口只是一个标记接口,它的出现就是让使用者知道要对clone方法重写。如果没有implement这个接口,又使用了clone()方法,就会抛出异常。

转过头来,我们再看一看clone()方法的注释,这里截取了一部分。

 /**
     * Creates and returns a copy of this object.  The precise meaning
     * of "copy" may depend on the class of the object. The general
     * intent is that, for any object {@code x}, the expression:
     * <blockquote>
     * <pre>
     * x.clone() != x</pre></blockquote>
     * will be true, and that the expression:
     * <blockquote>
     * <pre>
     * x.clone().getClass() == x.getClass()</pre></blockquote>
     * will be {@code true}, but these are not absolute requirements.
     * While it is typically the case that:
     * <blockquote>
     * <pre>
     * x.clone().equals(x)</pre></blockquote>
     * will be {@code true}, this is not an absolute requirement.
     * <p>
     * By convention, the returned object should be obtained by calling
     * {@code super.clone}.  If a class and all of its superclasses (except
     * {@code Object}) obey this convention, it will be the case that
     * {@code x.clone().getClass() == x.getClass()}.

里面提到:准确的copy含义也许取决于该对象的类。这样做的意图是,对于任意的一个对象,以下表达式:
x.clone() != x为真
x.clone().getClass() == x.getClass()为真
但是它们并非必须满足的要求。
通常情况下,x.clone().equals(x)为真(依旧非必须满足)。
按照惯例,clone()方法返回的对象应该由调用super.clone获得,
如果一个类和其所有除了object的超类都遵循了此惯例,那么x.clone().getClass() == x.getClass()为真。

害,绕过来绕过去的,说白了,具体情况具体对待


熊猫人.png

总的来说,要拷贝就要implement Cloneable这个接口,重写clone()方法并在其中合理地调用super.clone()。那么clone()方法返回的是当前对象的一个拷贝。而这个拷贝是浅拷贝的。

浅拷贝

前面提到了浅拷贝,辣么什么是浅拷贝呢?
这里有百度百科的一串概念:

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

结合自己的理解,我来通俗地翻译并且转化一下这段话:
在进行浅拷贝时,会为新对象分配一块空间来进行对象的拷贝。浅拷贝得到的新对象和旧对象的内存地址已经不同,但成员属性完全相同。
而这种属性分为两类:
1.基本类型
2.对象(可变对象、不可变对象(典型的就是String,在稍后的例子中我们会看到)
所以浅拷贝得到的成员属性,对于基本类型来说,就是复制一下值。第二种,则只是复制了引用哦,只是将新对象的对应属性的引用指向原引用。所以就会存在互相影响的问题(然而不可变对象是个例外,由于它的特殊性,这种互相影响就不存在)
示意图大概就是下面这样(图片来自百度百科):

百度到的图.jpg.png

下面我们来看个例子:
这是一个类,名叫人,它拥有三个成员:age(基本类型int)、name(不可变对象String)、House(可变对象)。它就覆盖了我们上面所谈到的情况。

public class Person implements Cloneable{

    private String name ;
    private int age;
    private House house;

    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 House getHouse() {
        return house;
    }

    public void setHouse(House house) {
        this.house = house;
    }

    @Override
    public Object clone()  {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public String toString(){
        return "[Person: "+this.hashCode()+" name: "+name+" age: "+age+"  house:"+house+" ]";
    }

}

public class House {

    private int money;
    private String type;

    public House(int money ,String type){
        this.money=money;
        this.type=type;
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    @Override
    public String toString(){
        return "[House: "+this.hashCode()+" money: "+money+" type: "+type+" ]";
    }

}

测试:

public class copyTest {
    public static void main(String[] args){
        Person person1 = new Person();
        person1.setAge(18);
        person1.setHouse(new House(20,"small house"));
        person1.setName("alex");

        Person person2 =person1;//=测试,其实这个完全不需要,但还是做个对比
        person2.setName("alice");
        person2.setAge(20);
        House house2 = person2.getHouse();
        house2.setMoney(40);

        System.out.println(person1.toString());
        System.out.println(person2.toString());

        System.out.println("华丽丽的分割线");

        Person person3 = (Person) person1.clone();//浅拷贝
        person3.setName("tom");
        person3.setAge(28);
        House house3 = person3.getHouse();
        house3.setMoney(60);
        System.out.println(person1.toString());
        System.out.println(person3.toString());

    }
}

运行结果:

[Person: 1163157884 name: alice age: 20  house:[House: 1956725890 money: 40 type: small house ] ]
[Person: 1163157884 name: alice age: 20  house:[House: 1956725890 money: 40 type: small house ] ]
华丽丽的分割线
[Person: 1163157884 name: alice age: 20  house:[House: 1956725890 money: 60 type: small house ] ]
[Person: 356573597 name: tom age: 28  house:[House: 1956725890 money: 60 type: small house ] ]

显然,浅拷贝确实新创建了一个对象,但是对于该对象内的可变对象成员的拷贝只是复制了引用。

所以person3和person1并非独立,对其中的house对象的修改会互相影响。
那么问题来了,我现在希望这两个person是完全独立的,又该怎么做呢?

这时就涉及到深拷贝。

深拷贝

说白了,就是在浅拷贝的达到的基础上再把要复制的对象里的可变对象也新建对象空间复制一份,而不是复制引用。

达到深拷贝的方式主要有两种:
1.层层浅拷贝(每一层的可变对象都给它clone)
2.序列化

下面我们先试第一种方法:

public class Person implements Cloneable{

    private String name ;
    private int age;
    private House house;

    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 House getHouse() {
        return house;
    }

    public void setHouse(House house) {
        this.house = house;
    }

    @Override
    public Object clone()  {
        try {
            Person person=(Person) super.clone();
            person.house=(House) house.clone();
            return person;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public String toString(){
        return "[Person: "+this.hashCode()+" name: "+name+" age: "+age+"  house:"+house+" ]";
    }

}
public class House implements Cloneable{

    private int money;
    private String type;

    public House(int money ,String type){
        this.money=money;
        this.type=type;
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    @Override
    public Object clone(){
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public String toString(){
        return "[House: "+this.hashCode()+" money: "+money+" type: "+type+" ]";
    }

}

测试:

public class copyTest {
    public static void main(String[] args){
        Person person1 = new Person();
        person1.setAge(18);
        person1.setHouse(new House(20,"small house"));
        person1.setName("alex");

        Person person3 = (Person) person1.clone();
        person3.setName("tom");
        person3.setAge(28);
        House house3 = person3.getHouse();
        house3.setMoney(60);
        System.out.println(person1.toString());
        System.out.println(person3.toString());

    }
}

结果:

[Person: 1163157884 name: alex age: 18  house:[House: 1956725890 money: 20 type: small house ] ]
[Person: 356573597 name: tom age: 28  house:[House: 1735600054 money: 60 type: small house ] ]
image.png

下面看一下序列化的使用方法,一个类要能序列化,就必须实现Serializable这个标记接口。
这里我们对person和House分别implements上Serializable接口后进行测试:

public class copyTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {

          Person person1 = new Person();
          person1.setAge(18);
          person1.setHouse(new House(20,"small house"));
          person1.setName("alex");

          Person person3 = (Person) deepClone(person1);
          person3.setName("tom");
          person3.setAge(28);
          House house3 = person3.getHouse();
          house3.setMoney(60);
          System.out.println(person1.toString());
          System.out.println(person3.toString());

    }

    public static Object deepClone(Object object) throws IOException, ClassNotFoundException {
        //写入对象
        ByteArrayOutputStream bo=new ByteArrayOutputStream();
        ObjectOutputStream oo=new ObjectOutputStream(bo);
        oo.writeObject(object);
        //读取对象
        ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
        ObjectInputStream oi=new ObjectInputStream(bi);
        return(oi.readObject());
    }
}
[Person: 325040804 name: alex age: 18  house:[House: 1173230247 money: 20 type: small house ] ]
[Person: 2065951873 name: tom age: 28  house:[House: 1791741888 money: 60 type: small house ] ]
image.png

后记

个人觉得浅拷贝、深拷贝的理解还是蛮重要,有时候莫名其妙的值的改变的问题很大程度就是多线程或者对拷贝的理解不透彻的问题。

相关文章

  • java中的深拷贝和浅拷贝

    简单记录一下java中的深拷贝和浅拷贝,深拷贝和浅拷贝只是针对对象而言的. 1 深拷贝代码 2 浅拷贝代码 3 测...

  • java 对象的拷贝

    拷贝:即复制 对象拷贝:即对象复制 java 对象拷贝分类:浅拷贝、深拷贝 java 对象的浅拷贝和深拷贝针对包含...

  • Java基础 - 深拷贝和浅拷贝

    Java 的深拷贝和浅拷贝 什么是深拷贝、浅拷贝 (深克隆、浅克隆)? 在 Java 中,数据类型分为 基本数据类...

  • 深拷贝和浅拷贝

    1: iOS开发 深拷贝与浅拷贝 2: iOS 浅谈:深.浅拷贝与copy.strong 3: iOS开发——深...

  • Java再回顾(2)—浅拷贝与深拷贝

    前言 对象的拷贝是java开发中绕不过去的话题,实际开发中运用到的也很多。有的时候,需要一份对象的复制结果,然后对...

  • Java------List的深拷贝与浅拷贝

    Java的浅拷贝(Shallow Copy)、深拷贝(Deep Copy)。 浅拷贝(Shallow Copy) ...

  • JS中的深拷贝与浅拷贝

    知乎:js中的深拷贝和浅拷贝? 掘金: js 深拷贝 vs 浅拷贝 前言 首先深拷贝与浅拷贝只针对 Object,...

  • 高级二.深浅copy

    1. ==,is的使用 2.拷贝 2.1 浅拷贝与深拷贝 浅拷贝是对于一个对象...

  • 8.2-浅拷贝深拷贝和随机数

    清醒、自律、知进退、爱自己! 2.浅Copy与深Copy 引用、浅拷贝和深拷贝的区别 浅拷贝:影子拷贝;为了解决函...

  • C++封装(二)

    第2章 对象成员与对象数组 第3章 深拷贝与浅拷贝 浅拷贝: 深拷贝: 第4章 对象指针 对象指针: 栈中: 对象...

网友评论

    本文标题:Java再回顾(2)—浅拷贝与深拷贝

    本文链接:https://www.haomeiwen.com/subject/pbpqxktx.html