欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java 对象的浅拷贝和深拷贝

程序员文章站 2024-03-05 18:40:25
...

一、概述

对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去。在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用对象的部分或全部数据。Java中有三种类型的对象拷贝:浅拷贝(Shallow Copy)、深拷贝(Deep Copy)。:

  • 浅拷贝: 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
  • 深拷贝:深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
  • 无论是浅拷贝还是深拷贝,都需要实现 clone() 方法,来完成操作。

二、浅拷贝

Java 对象的浅拷贝和深拷贝

public class TestDemo{
    public static void main(String[] args) {

        Friend friend = new Friend("Ace","man",18);
        // 原始对象
        Person person = new Person("Jeffrey",18, friend);
        System.out.println("原始对象: " + person);

         // 拷贝对象
        Person clonedPperson = (Person)person.clone();
        System.out.println("拷贝对象: " + clonedPperson);

        //原始对象和拷贝对象是否一样
        System.out.println("原始对象和拷贝对象是否一样: " + (person == clonedPperson));

        //修改基本类型
        person.setAge(20);
        System.out.println("修改基本类型后的对比:");
        System.out.println("原始对象: " + person);
        System.out.println("拷贝对象: " + clonedPperson);
        //修改引用类型
        person.getFriend().setAge(30);
        System.out.println("修改引用类型后的对比:");
        System.out.println("原始对象: " + person);
        System.out.println("拷贝对象: " + clonedPperson);
    }
}

class Person implements Cloneable{

    private String name;
    private int age;
    private Friend friend;
    public Person(String name, int age ,Friend friend) {
        this.name = name;
        this.age = age;
        this.friend = friend;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Friend getFriend() {
        return friend;
    }

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

    public String toString() {
        return "Person={ " +  "name=" + name + ", age=" + age +  ", friend=" + friend+" }";
    }
}


class Friend{
    private String name;
    private String sex;
    private int age;
    public Friend(String name,String sex,int age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "{ Friend:" +  "name=" + name + ", sex=" + sex + ", age=" + age + " }";
    }

}

输出结果:

原始对象: Person={ name=Jeffrey, age=18, friend={ Friend:name=Ace, sex=man, age=18 } }
拷贝对象: Person={ name=Jeffrey, age=18, friend={ Friend:name=Ace, sex=man, age=18 } }
原始对象和拷贝对象是否一样: false
修改基本类型后的对比:
原始对象: Person={ name=Jeffrey, age=20, friend={ Friend:name=Ace, sex=man, age=18 } }
拷贝对象: Person={ name=Jeffrey, age=18, friend={ Friend:name=Ace, sex=man, age=18 } }
修改引用类型后的对比:
原始对象: Person={ name=Jeffrey, age=20, friend={ Friend:name=Ace, sex=man, age=30 } }
拷贝对象: Person={ name=Jeffrey, age=18, friend={ Friend:name=Ace, sex=man, age=30 } }

根据结果来看,通过Clonable接口并重写Object类的clone()方法来赋值的对象,值类型的赋值不会改变,引用类型会改变,印证了只是赋值栈上面的数据和引用对象地址,最终指向堆的内存地址一致。

三、深拷贝

Java 对象的浅拷贝和深拷贝
常用的方案有两种:

  • 继续利用 clone()方法,对其内的引用类型的变量,再进行一次clone()(Friend实现),或者在clone()创建新的引用变量赋值和新的对象
  • 序列化(serialization)这个对象,再反序列化回来,就可以得到这个新的对象。

1、clone

修改Pesron类,新创建一个对象返回:

  @Override
    public Object clone(){
        // 深拷贝,创建拷贝类的一个新对象,这样就和原始对象相互独立
        return new Person(name, age,new Friend(friend.name,friend.sex,friend.age));
    }

2、序列化实现

具体参考Java 序列化,只是把FileOutputStream改成ByteArrayOutputStream。因为主要是FileOutputStream作用于文件,后者作用于byte[]。

参考Java 深拷贝和浅拷贝细说 Java 的深拷贝和浅拷贝