浅拷贝与深拷贝、以及深拷贝的实现

浅拷贝与深拷贝

所谓的浅拷贝,顾名思义就是很表面的很表层的拷贝,如果我们要克隆Administrator对象,只克隆他自身以及他包含的所有对象的引用地址

而深拷贝,就是非浅拷贝。拷贝除自身以外所有的对象,包括自身所包含的所有对象实例。至于深拷贝的层次,由具体的需求决定,也有“N层拷贝”一说。

但是,所有的基本(primitive)类型数据,无论是浅拷贝还是深拷贝,都会进行原值拷贝。毕竟他们都不是对象,不是存储在堆中。注意:基本数据类型并不包括他们对应的包装类。


对于克隆(Clone),Java有一些限制:
1、被克隆的类必须自己实现Cloneable 接口,以指示 Object.clone() 方法可以合法地对该类实例进行按字段复制。Cloneable 接口实际上是个标识接口,没有任何接口方法。
2、实现Cloneable接口的类应该使用公共方法重写 Object.clone(它是受保护的)。某个对象实现了此接口就克隆它是不可能的。即使 clone 方法是反射性调用的,也无法保证它将获得成功。
3、在Java.lang.Object类中克隆方法是这么定义的:
protected Object clone()
                throws CloneNotSupportedException
创建并返回此对象的一个副本。表明是一个受保护的方法,同一个包中可见。
按照惯例,返回的对象应该通过调用 super.clone 获得。

浅拷贝:

 

class Employer {
    private String username;
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
}
 
 
class Employee implements Cloneable{
    private String username;
    private Employer employer;
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public Employer getEmployer() {
        return employer;
    }
    public void setEmployer(Employer employer) {
        this.employer = employer;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        //克隆Employee对象
        Employee employee = (Employee)super.clone();
        return employee;
    }
    public static void main(String[] args) throws CloneNotSupportedException {
        Employer employer = new Employer();
        employer.setUsername("arthinking");
        Employee employee = new Employee();
        employee.setUsername("Jason");
        employee.setEmployer(employer);
        //employee2由employee浅拷贝得到
        Employee employee2 = (Employee) employee.clone();
        //这样两个employee各自保存了的是同一个employer
        employee2.getEmployer().setUsername("Jason");
        System.out.println(employee.getEmployer().getUsername()); //Jason
        System.out.println(employee2.getEmployer().getUsername());//Jason
    }
}

深拷贝:

class Employer implements Cloneable{
    private String username;
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
  
package com.and1kaney.DeepClone;
/**
 * User: dinggan@meituan.com
 * Date: 14-8-19
 * Time: 下午11:32
 */
class Employee implements Cloneable{
    private String username;
    private Employer employer;
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public Employer getEmployer() {
        return employer;
    }
    public void setEmployer(Employer employer) {
        this.employer = employer;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        //克隆Employee对象并手动的
        Employee employee = (Employee)super.clone();
        //进一步克隆Employee对象中包含的Employer对象
        employee.setEmployer((Employer) employee.getEmployer().clone());
        return employee;
    }
    public static void main(String[] args) throws CloneNotSupportedException {
        Employer employer = new Employer();
        employer.setUsername("arthinking");
        Employee employee = new Employee();
        employee.setUsername("Jason");
        employee.setEmployer(employer);
        //employee2由employee深复制得到
        Employee employee2 = (Employee) employee.clone();
        //这样两个employee各自保存了两个employer
        employee2.getEmployer().setUsername("Jason");
        System.out.println(employee.getEmployer().getUsername());//arthinking
        System.out.println(employee2.getEmployer().getUsername());//Jason
    }
}

总结下利用Cloneable接口实现深拷贝:

1. 让该类实现java.lang.Cloneable接口;

2. 确认持有的对象是否实现java.lang.Cloneable接口并提供clone()方法;

3. 重写(override)Object类的clone()方法,并且在方法内部调用持有对象的clone()方法;

很麻烦,调来调去的,如果有N多个持有的对象,那就要写N多的方法,突然改变了类的结构,还要重新修改clone()方法。

 

利用序列化来进行对象的深拷贝

序列化即是把对象写到流里面的过程;反序列化即是把对象从流中读取出来的过程。写在流里的是对象的一个拷贝,而原来的对象仍然在JVM里面。

 

具体的实现是:

前提是对象以及对象内部所有用到的对象都是可序列化的,否则就需要考虑把那些不可序列化的对象标记为transient,从而把它排除到复制范围之外。

 

然后使对象实现Serializable接口。

 

把对象写入到一个流里(不用依赖于文件,直接暂存在内存中即可),在从流里读取出来,便得到了一个深复制的对象。

//创建Employer2类实现序列化接口
class Employer2 implements Serializable{
 
    private static final long serialVersionUID = 1L;
    private String name;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}
  
创建Employee2类实现序列化接口,并通过序列化编写深复制的方法:
class Employee2 implements Serializable{
 
    private static final long serialVersionUID = 3969438177161438988L;
    private String name;
    private Employer2 employer;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Employer2 getEmployer() {
        return employer;
    }
    public void setEmployer(Employer2 employer) {
        this.employer = employer;
    }
    /**
     * 实现深复制的方法
     */
    public Object deepCopy() throws IOException, ClassNotFoundException{
        //字节数组输出流,暂存到内存中
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        //序列化
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        //反序列化
        return ois.readObject();
    }
}


关于Serializable接口的类中的serialVersionUID:

如果没有提供serialVersionUID,对象序列化后存到硬盘上之后,在增加或减少类的filed。这样,当反序列化时,就会出现Exception,造成不兼容问题。

 

但当serialVersionUID相同时,它就会将不一样的field以type的缺省值反序列化。这样就可以避开不兼容问题了。

如果没有指定serialVersionUID的,那么java编译器会自动给这个class进行一个摘要算法,类似于指纹算法,只要这个文件多一个空格,得到的UID就会截然不同的,可以保证在这么多类中,这个编号是唯一的。所以,我们添加了一个字段后,由于没有显指定serialVersionUID,编译器又为我们生成了一个UID,当然和前面保存在文件中的那个不会一样了,于是就出现了2个号码不一致的错误。

因此,只要我们自己指定了serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方法可以用,呵呵。但是serialVersionUID我们怎么去生成呢?你可以写1,也可以写2,都无所谓,但是最好还是按照摘要算法,生成一个惟一的指纹数字,eclipse/idea 可以自动生成的,jdk也自带了这个工具。

默认情况下Intellij IDEA是关闭了继承了java.io.Serializable的类生成serialVersionUID的警告。如果需要ide提示生成serialVersionUID,那么需要做以下设置:

1、setting->Inspections->Serialization issues,将serialzable class without "serialVersionUID"打上勾;

2、将光标放到类名上,按atl+enter键,就会提示生成serialVersionUID了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值