java深浅拷贝


title: java深浅拷贝
date: 2018-12-16 15:54:20
updated: 2020-03-15 13:29:14
categories: java
tags:
- java


此文档为java深浅拷贝的学习总结

Java 浅拷贝和深拷贝

来源:Java 浅拷贝和深拷贝

1.直接赋值

A1 a1 = a2;

那么对象a1/a2指向同一个对象,即a2对对象的操作,a1也跟着变了

2.浅拷贝

类的方法中加个clone方法。这种浅拷贝实现的是:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。

private String name;  //姓名  
private String sex;   //性别  
private int age;      //年龄  
private String experience; //工作经历

public Object clone() {  
    try {  
        return (Resume)super.clone();  
    } catch (Exception e) {  
        e.printStackTrace();  
        return null;  
    }  
}

public static void main(String[] args) {  
        Resume zhangsan = new Resume("zhangsan","男",24);  
        zhangsan.setExperience("2009-2013就读于家里蹲大学,精通JAVA,C,C++,C#等代码拷贝和粘贴");  
        zhangsan.displayResume();  
        Resume zhangsan1 = (Resume)zhangsan.clone();  
        zhangsan1.setAge(23);  
        zhangsan1.displayResume();  
        Resume zhangsan2 = (Resume)zhangsan.clone();  
        zhangsan2.setExperience("2009-2013就读于家里蹲大学,精通JAVA,C,C++,C#等代码");  
        zhangsan2.displayResume();  
        zhangsan.displayResume();  
    }  

上面代码没问题,因为数据没有引用类型,a1/a2指向对象不同,彼此对立,但是下面的就不行了:

class Experience {  
      
    private String educationBackground;  
    private String skills;  
      
    public void setExperience(String educationBackground, String skills) {  
        // TODO Auto-generated constructor stub  
        this.educationBackground = educationBackground;  
        this.skills = skills;  
    }  
    public String toString() {  
        return educationBackground + skills;  
    }  
}  
  
/* 建立类,实现Clone方法  */  
class Resume  implements Cloneable{  
    private String name;  //姓名  
    private String sex;   //性别  
    private int age;      //年龄  
    private Experience experience; //工作经历,引用类型
      
    public Resume(String name, String sex, int age) {  
        this.name = name;  
        this.sex = sex;  
        this.age = age;  
        this.experience = new Experience();  
    }  
      
    public void setAge(int age) {  
        this.age = age;  
    }  
    public int getAge() {  
        return age;  
    }  
      
    public Experience getExperience() {  
        return experience;  
    }  
      
    public void setExperience(String educationBackground, String skills) {  
        experience.setExperience(educationBackground, skills);  
    }  
      
    public void displayResume() {  
        System.out.println("姓名:"+name+" 性别:"+sex+" 年龄:"+age);  
        System.out.println("工作经历:"+experience.toString());  
    }  
      
    public Object clone() {  
        try {  
            return (Resume)super.clone();  
        } catch (Exception e) {  
            e.printStackTrace();  
            return null;  
        }  
    }  
}  
  
public class MainClass {  
    public static void main(String[] args) {  
        Resume zhangsan = new Resume("zhangsan","男",24);  
        zhangsan.setExperience("2009-2013就读于家里蹲大学","精通JAVA,C,C++,C#等代码拷贝和粘贴");  
        zhangsan.displayResume();  
  
        Resume zhangsan2 = (Resume)zhangsan.clone();  
        zhangsan2.setExperience("2009-2013就读于家里蹲大学","精通JAVA,C,C++,C#等");  
        zhangsan2.displayResume();  
        zhangsan.displayResume();  
        zhangsan2.displayResume();  
    }  
}

out:

姓名:zhangsan 性别:男 年龄:24  
工作经历:2009-2013就读于家里蹲大学精通JAVA,C,C++,C#等代码拷贝和粘贴  
姓名:zhangsan 性别:男 年龄:24  
工作经历:2009-2013就读于家里蹲大学精通JAVA,C,C++,C#等  
姓名:zhangsan 性别:男 年龄:24  
工作经历:2009-2013就读于家里蹲大学精通JAVA,C,C++,C#等  
姓名:zhangsan 性别:男 年龄:24  
工作经历:2009-2013就读于家里蹲大学精通JAVA,C,C++,C#等

如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。”其实也就是说,zhangsan和zhangsan2里面的Experience类指向的是同一个对象,不管是zhangsan里面的Experience变化,还是zhangsan2里面的Experience变化都会影响另外一个。

3.深拷贝

java深拷贝两种方法:

  • 1.继承Cloneable重写clone方法
  • 2.对象实现Serializable通过序列化实现深拷贝

其实出现问题的关键就在于clone()方法上,我们知道该clone()方法是使用Object类的clone()方法,但是该方法存在一个缺陷,它并不会将对象的所有属性全部拷贝过来,而是有选择性的拷贝,基本规则如下:

1、 基本类型

如果变量是基本很类型,则拷贝其值,比如int、float等。

2、 String字符串

若变量为String字符串,则拷贝其地址引用。但是在修改时,它会从字符串池中重新生成一个新的字符串,原有对象保持不变。

3、 对象/引用类型

如果变量是一个实例对象,则拷贝其地址引用,也就是说此时新对象与原来对象是公用该实例变量。(即浅拷贝)

因此深拷贝就是在clone方法中把引用类型深拷贝即可。

public class YuelyLog implements Cloneable {
    private Attachment attachment;//引用类型
    private String name;
    private String date;

    @Override
    protected YuelyLog clone() throws CloneNotSupportedException {
    	YuelyLog o = (YuelyLog)super.clone(); //Object.clone()
		o.attachment = (Attachment)attachment.clone();
		return o;
    }
}

三点说明:

  • 1、clone方法实现要点:实现 Cloneable接口 + 使用protected访问修饰符重新定义clone方法以实现深拷贝

  • 2、clone方法是Object类的一个protected方法,因此无法调用anObj.clone(),也就是说用户在编写的代码中不能直接调用它,只有Employee类才能克隆Employee对象(这样通过类来拷贝将避免出现浅拷贝的 问题)

  • 3、Cloneable接口是java提供的几个标记接口(tagging interface),与通常接口不同的是它没有指定方法,Cloneable中的clone方法是从Object类继承而来,这个Cloneable接口作为标记是想说明如果一个对象需要克隆而没有实现Cloneable接口,就会产生一个异常。

深拷贝问题解决了,但对于上面的解决方案还是存在一个问题,若我们系统中存在大量的对象是通过拷贝生成的,如果我们每一个类都写一个clone()方法,并将还需要进行深拷贝,新建大量的对象,这个工程是非常大的,这里我们可以利用对象的序列化来实现对象的拷贝

如何利用序列化来完成对象的拷贝呢?在内存中通过字节流的拷贝是比较容易实现的。把母对象写入到一个字节流中,再从字节流中将其读出来,这样就可以创建一个新的对象了,并且该新对象与母对象之间并不存在引用共享的问题,真正实现对象的深拷贝。

public class CloneUtils {
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj){
        T cloneObj = null;
        try {
            //写入字节流
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream obs = new ObjectOutputStream(out);
            obs.writeObject(obj);
            obs.close();
            
            //分配内存,写入原始对象,生成新对象
            ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(ios);
            //返回生成的新对象
            cloneObj = (T) ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }
}

使用该工具类的对象必须要实现Serializable接口,否则是没有办法实现克隆的。

public class Person implements Serializable{
    private static final long serialVersionUID = 2631590509760908280L;

    ..................
    //去除clone()方法

}

public class Email implements Serializable{
    private static final long serialVersionUID = 1267293988171991494L;
    
    ....................
}

使用该工具类的对象只要实现Serializable接口就可实现对象的克隆,无须实现Cloneable接口中的clone()方法。

public class Client {
    public static void main(String[] args) {
        //写封邮件
        Email email = new Email("请参加会议","请与今天12:30到二会议室参加会议...");
        
        Person person1 =  new Person("张三",email);
        
        Person person2 =  CloneUtils.clone(person1);
        person2.setName("李四");
        Person person3 =  CloneUtils.clone(person1);
        person3.setName("王五");
        person1.getEmail().setContent("请与今天12:00到二会议室参加会议...");
        
        System.out.println(person1.getName() + "的邮件内容是:" + person1.getEmail().getContent());
        System.out.println(person2.getName() + "的邮件内容是:" + person2.getEmail().getContent());
        System.out.println(person3.getName() + "的邮件内容是:" + person3.getEmail().getContent());
    }
}
-------------------
Output:
张三的邮件内容是:请与今天12:00到二会议室参加会议...
李四的邮件内容是:请与今天12:30到二会议室参加会议...
王五的邮件内容是:请与今天12:30到二会议室参加会议...

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值