前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java漫谈-深拷贝与浅拷贝

Java漫谈-深拷贝与浅拷贝

作者头像
WindCoder
发布2018-09-19 15:55:45
3.1K0
发布2018-09-19 15:55:45
举报
文章被收录于专栏:WindCoderWindCoder

创建对象的方式

1、调用new语句创建对象,最常见的一种

2、运用反射手段创建对象,调用java.lang.Class 或者 java.lang.reflect.Constructor 类的newInstance()实例方

3、调用对象的clone()方法

4、使用反序列化,调用java.io.ObjectInputStream 对象的 readObject()方法.

将一个对象的引用复制给另外一个对象的方法

1、直接赋值 2、浅拷贝 3、深拷贝

直接赋值

实体类Person.java

代码语言:javascript
复制
public class Person {
    //姓名
    private String name;
    // 年龄
    private int age;
    // 编号
    private Integer code;

    public Person(){

    }
	
    public Person(String name, int age, Integer code) {
        this.name = name;
        this.age = age;
        this.code = code;
    }

    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 Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

}

测试类cloneDemo.java

本篇所有测试均会在该类下完成

代码语言:javascript
复制
public class cloneDemo {
    public static void main(String[] args) {
        Person p = new Person("小明",11,123);
        Person p1 = p;
        System.out.println(p);
        System.out.println(p1);
    }
}

打印结果:

代码语言:javascript
复制
Others.base.cloneDemo.Person@4554617c
Others.base.cloneDemo.Person@4554617c

两者打印出来的地址相同,可见二者的引用是同一个对象,并没有创建出一个新的对象。可以把这种现象叫做引用的复制(或引用拷贝)。

对Person.java追加toString方法,

代码语言:javascript
复制
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", code=" + code +
                '}';
    }

cloneDemo.java中追加

代码语言:javascript
复制
        p1.setCode(1234);
        System.out.println("--------windcoder.com----------");
        System.out.println(p);
        System.out.println(p1);

从打印结果中更能体现出这一点,因为是同一个对象的引用,所以两者改一个,另一个对象的值也随之改变:

代码语言:javascript
复制
Person{name='小明', age=11, code=123}
Person{name='小明', age=11, code=123}
--------windcoder.com----------
Person{name='小明', age=11, code=1234}
Person{name='小明', age=11, code=1234}

浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。

如果属性是基本类型,拷贝的就是基本类型的值;

如果属性是内存地址(引用类型),拷贝的就是内存地址(即复制引用但不复制引用的对象) ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

实现对象拷贝的类,必须实现Cloneable接口,并覆写clone()方法

下面改造Person.java

代码语言:javascript
复制
/**
 * 实现对象拷贝的类,必须实现Cloneable接口,并覆写clone()方法
 */
public class Person implements Cloneable{
    //姓名
    private String name;
    // 年龄
    private int age;
    // 编号
    private Integer code;

    public Person(){

    }

    public Person(String name, int age, Integer code) {
        this.name = name;
        this.age = age;
        this.code = code;
    }

    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 Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", code=" + code +
                '}';
    }

    /**
     * 覆写clone()方法
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

cloneDemo.java中追加

代码语言:javascript
复制
        Person p2 = p;
        p2.setName("小明克隆人");
        p2.setAge(12);
        p2.setCode(12345);
        System.out.println(p);
        System.out.println(p2);
        System.out.println("--------windcoder.com----------");

得到结果:

代码语言:javascript
复制
Person{name='小明', age=11, code=1234}
Person{name='小明克隆人', age=12, code=12345}
--------windcoder.com----------

此时p2完成了一次拷贝。现在继续改造Person,添加一个Address对象。 Address.java

代码语言:javascript
复制
public class Address {
    private String name;

    public Address( ) {

    }

    public Address(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

Person.java中添加对应属性

代码语言:javascript
复制
private Address address;
public Address getAddress() {
	return address;
}

public void setAddress(Address address) {
	this.address = address;
}
// 修改toString,追加address
public String toString() {
	return "Person{" +
			"name='" + name + '\'' +
			", age=" + age +
			", code=" + code +
			", address=" + address +
			'}';
}

cloneDemo.java追加如下:

代码语言:javascript
复制
p.setAddress(new Address("我是一个小学生"));
Person p2 = (Person)p.clone();
System.out.println(p);
System.out.println(p2);
System.out.println("--------windcoder.com----------");
p2.setName("小明克隆人");
p2.setAge(12);
p2.setCode(12345);
p2.getAddress().setName("我是一个小学生克隆人");
System.out.println(p);
System.out.println(p2);
System.out.println("--------windcoder.com----------");

我们预期是能够获得p还是原来的address,p2的address发生改变,但结果如何呢,打印发现两者都发生了改变:

代码语言:javascript
复制
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生克隆人'}}
Person{name='小明克隆人', age=12, code=12345, address=Student{name='我是一个小学生克隆人'}}
--------windcoder.com----------

这是由于,此时发生的仅是浅拷贝,即被拷贝对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。针对基本类型及其封装类都是将对应的基本类型值拷贝,对于其余对象,则仅是拷贝其引用,这导致最终里面的对象是同一个,更改一个,另一个的拷贝/原对象的对应值也随之更改。

深拷贝

如果想将上述的address也单独复制一份,这就用到了深拷贝。

被拷贝对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍

通俗的说,如果说浅拷贝,开始的时候是两条线,如果在最后有一个其他类的变量,那么这两条线最后会合二为一,共同指向这变量,都能对他进行操作。深拷贝则是完完全全的两条线,互不干涉,因为他已经把所有的内部中的变量的对象全都复制一遍了。

深拷贝在代码中,需要在clone方法中多书写调用这个类中其他类的变量的clone函数。

改造方案

Address.java 实现Cloneable接口并重写clone

代码语言:javascript
复制
public class Address implements Cloneable{
    private String name;

    public Address( ) {

    }

    public Address(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }

    /**
     * 覆写clone()方法
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
        protected Object clone()   {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Person.java修改clone方法:

代码语言:javascript
复制
    protected Object clone()   {
        try {
            // super.clone();
            // return super.clone();

            // 改为深复制:
            Person newPerson = (Person)super.clone();
            // 拷贝一份新的address
            newPerson.address = (Address) address.clone();
            return newPerson;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

此时再运行之前的cloneDemo.java,发现结果已经是预期的效果:

代码语言:javascript
复制
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
Person{name='小明克隆人', age=12, code=12345, address=Student{name='我是一个小学生克隆人'}}
--------windcoder.com----------

拓展:利用串行化来做深复制

把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做 “解冻”或者“回鲜(depicking)”过程。

代码语言:javascript
复制
public Object deepClone() { 
 //写入对象 
 ByteArrayOutoutStream bo=new ByteArrayOutputStream(); 
 ObjectOutputStream oo=new ObjectOutputStream(bo); 
 oo.writeObject(this); 
 //读取对象
 ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray()); 
 ObjectInputStream oi=new ObjectInputStream(bi); 
 return(oi.readObject()); 
}
改造示例

Address追加Serializable接口实现

代码语言:javascript
复制
public class Address implements Serializable, Cloneable{
	.....
}

Person追加Serializable接口实现

代码语言:javascript
复制
public class Person implements Serializable, Cloneable{
	......
	/**
     * 利用串行化来做深复制
     * by: windCoder.com
     * @return
     */
    public Object deepClone()  {

        try {
            //写入对象
            ByteArrayOutputStream bo= new ByteArrayOutputStream();
            ObjectOutputStream os = new ObjectOutputStream(bo);
            os.writeObject(this);
            //读取对象
            ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
            ObjectInputStream oi=new ObjectInputStream(bi);
            return(oi.readObject());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}

cloneDemo.java追加:

代码语言:javascript
复制
Person p3 = (Person)p.deepClone();
System.out.println(p);
System.out.println(p3);
System.out.println("--------windcoder.com----------");
p3.setName("小明克隆人");
p3.setAge(12);
p3.setCode(12345);
p3.getAddress().setName("我是一个小学生克隆人233333");
System.out.println(p);
System.out.println(p3);
System.out.println("--------windcoder.com----------");

运行结果:

代码语言:javascript
复制
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
Person{name='小明克隆人', age=12, code=12345, address=Student{name='我是一个小学生克隆人233333'}}
--------windcoder.com----------

注:可以使用transient关键字,修饰不需要序列化的属性。当序列化对象的时候,这个属性就不会序列化到指定的目的地中。

参考资料

java创建对象的四种方式

java 深拷贝与浅拷贝机制详解

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 创建对象的方式
  • 将一个对象的引用复制给另外一个对象的方法
    • 直接赋值
      • 浅拷贝
        • 深拷贝
          • 改造方案
        • 拓展:利用串行化来做深复制
          • 改造示例
      • 参考资料
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档