zl程序教程

您现在的位置是:首页 >  Java

当前栏目

Java中的深浅拷贝

2023-04-18 14:04:20 时间

Java中对象复制的方式

  • 直接赋值
  • 浅拷贝
  • 深拷贝

概念明确

Java把内存划分成两种:一种是堆内存,一种是栈内存。

堆(heap):主要用于存储实例化的对象,数组。由JVM动态分配内存空间,堆内存还可以用来存放由new创建的对象和数组。一个JVM只有一个堆内存,线程是可以共享数据的。

栈(stack):主要用于存储局部变量和对象的引用变量,每个线程都会有一个独立的栈空间,所以线程之间是不共享数据的。

talk is c heap , show me your code.

直接赋值Demo

@Data
@AllArgsConstructor
public class Person {
    private String name;
    private int age;
    private String email;
    private String desc;
}

class PersonApp {
    public static void main(String[] args) {
        Person person = new Person("张三", 22, "156xxxx2775@163.com", "我是张三");

        //复制对象
        Person person1 = person;
        person1.setName("李四");
        System.out.println("person" + person + " " + person.hashCode());
        System.out.println("person1" + person1 + " " + person1.hashCode());
        Console.log(person == person1);
    }
}

输出: personPerson(name=李四, age=20, email=156xxxx2775@163.com, desc=我是张三) 1528617539 person1Person(name=李四, age=20, email=156xxxx2775@163.com, desc=我是张三) 1528617539 true

直接赋值是Java开发中最简单且常用的方式,通过Demo输出可见,原对象person赋值给新对象person1,并给person1对象的某个字段进行重新赋值,其hashCode是一致的,说明在Java的中并没有创建新的内存地址,而是复制了原对象的引用地址而已。

图片源自

浅拷贝Demo

@Data
@AllArgsConstructor
public class Person implements Cloneable {
    /**
     * 基本类型属性
     */
    private String name;
    private int age;
    private String email;
    private PersonDesc personDesc;

    public void setDesc(String desc) {
        this.personDesc.setDesc(desc);
    }
    public Person(String name, int age, String email, String desc) {
        this.name = name;
        this.age = age;
        this.email = email;
        this.personDesc = new PersonDesc();
        this.personDesc.setDesc(desc);
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    /**
     * 引用类型属性
     *
     * @return
     * @throws CloneNotSupportedException
     */

    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person("张三", 20, "123456@qq.com", "Java开发");
        Person person1 = (Person) person.clone();
        person1.setName("Clone 张三S");
        person1.setAge(23);
        person1.setDesc("JavaScript");
        System.out.println(person + " " + person.hashCode());
        System.out.println(person1 + " " + person1.hashCode());
        System.out.println(person == person1);
    }
}

输出:

Person(name=张三, age=20, email=123456@qq.com, personDesc=PersonDesc(desc=JavaScript)) 1110312536 Person(name=Clone 张三S, age=23, email=123456@qq.com, personDesc=PersonDesc(desc=JavaScript)) 57334109 false

浅拷贝在原对象中的基本类型拷贝中,会复制一份到克隆对象,并在堆中开辟新的内存空间,对于引用类型,则会拷贝引用对象的内存地址,并不会把引用类型也克隆一份到堆内存中,由于原对象和克隆对象是引用的内存地址,因此如果两对象的任何一方改变这个地址,就会影响到另一个对象。

深拷贝Demo

  1. 实现Cloneable方式 PersonDesc.class
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PersonDesc implements Cloneable {
    private String desc;
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

​ Person.class

@Data
@AllArgsConstructor
public class Person implements Cloneable {
    /**
     * 基本类型属性
     */
    private String name;
    private int age;
    private String email;
    private PersonDesc personDesc;

    public void setDesc(String desc) {
        this.personDesc.setDesc(desc);
    }

    public Person(String name, int age, String email, String desc) {
        this.name = name;
        this.age = age;
        this.email = email;
        this.personDesc = new PersonDesc();
        this.personDesc.setDesc(desc);
    }

    /**
     * 深克隆的方式
     *
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        Person person = (Person) super.clone();
        //对引用对象进行克隆
        person.personDesc = (PersonDesc) personDesc.clone();
        return person;
    }

    /**
     * 引用类型属性
     *
     * @return
     * @throws CloneNotSupportedException
     */

    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person("张三", 20, "123456@qq.com", "Java开发");
        Person person1 = (Person) person.clone();
        person1.setName("Clone 张三S");
        person1.setAge(23);
        person1.setDesc("JavaScript");
        System.out.println(person + " " + person.hashCode());
        System.out.println(person1 + " " + person1.hashCode());
        System.out.println(person == person1);
    }
}		

输出:

Person(name=张三, age=20, email=123456@qq.com, personDesc=PersonDesc(desc=Java开发)) 2056507198 Person(name=Clone 张三S, age=23, email=123456@qq.com, personDesc=PersonDesc(desc=JavaScript)) 57334109 false

​ 2.实现 Serializable 接口方式

​ Person.class

@Data
@AllArgsConstructor
public class Person implements Serializable {
    private static final long serialVersionUID = 369285298572941L;

    private String name;
    private int age;
    private String email;
    private PersonDesc personDesc;


    public Person(String name, int age, String email, String desc) {
        this.name = name;
        this.age = age;
        this.email = email;
        this.personDesc = new PersonDesc();
        this.personDesc.setDesc(desc);
    }

    @Override
    public Person clone() {
        Person person = null;
        try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);
            // 将流序列化成对象
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            person = (Person) ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return person;
    }

    public void setDesc(String desc) {
        this.personDesc.setDesc(desc);
    }


    public static class PersonApp {
        public static void main(String[] args) throws Exception {
            // 初始化一个对象
            Person person = new Person("平头", 20, "123456@qq.com", "技术");
            // 复制对象
            Person person1 = (Person) person.clone();
            // 改变 person1 的属性值
            person1.setName("我是平头的克隆对象");
            // 修改 person age 的值
            person1.setAge(22);
            person1.setDesc("我已经关注了技术");
            System.out.println("person对象:" + person);
            System.out.println();
            System.out.println("person1对象:" + person1);
        }
    }
}

PersonDesc.class

@Data
public class PersonDesc implements Serializable {
    private static final long serialVersionUID = 872390113109L;
    private String desc;
}

深拷贝是把所有的属性都在内存堆里去创建新的内存地址,克隆对象与原对象是两个不同的对象。

拷贝方式的选择

  1. 如果对象的属性全是基本类型的,那么可以使用浅拷贝。
  2. 如果对象有引用属性且引用对象经常改变,那么就选择深拷贝,如果引用对象一成不变,可以选择浅拷贝。