文章目录

  • 1 问题背景
  • 2 浅拷贝
  • 2.1 概念
  • 2.2 例子
  • 3 深拷贝
  • 3.1 概念
  • 3.2 例子
  • 3.2.1 实现Cloneable接口的方式
  • 3.2.2 实现Serializable接口的方式
  • 3.2.3 JSON转换的方式


1 问题背景

实现新需求的时候,需要在旧的方法上做添加,但是又不能改变原有的数据,拷贝的数据涉及要引用类型,所以要采用深拷贝。基于各种考虑,我用了实现很简单但性能很低下的JSON转换方式进行深拷贝。简单研究了浅拷贝深拷贝的解决方案。

2 浅拷贝

2.1 概念

Java中的数据类型有基础类型和引用类型。浅拷贝中,拷贝基础类型的数据是拷贝数据的值;拷贝引用类型的数据是拷贝数据的值的内存地址。如下图所示:

浅拷贝和深拷贝的区别 python 浅拷贝和深拷贝的实现_Data

如上图所示,拷贝引用类型的数据并没有重新分配一个内存空间给他,修改拷贝对象A的obj数据会影响对象A的obj数据。

2.2 例子

浅拷贝的一个实现方式是待拷贝的对象实现Cloneable接口并重写clone方法,如下所示:

@Data
public class Address {

    /**
     * 国家码
     */
    private String countryCode;
    /**
     * 省/州 城市 码
     */
    private String childCode;

}
@Data
public class Tax implements Cloneable{

    /**
     * 税率
     */
    private BigDecimal rate;
    /**
     * 地址
     */
    private Address address;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public static void main(String[] args) throws CloneNotSupportedException {
        Tax taxA = new Tax();
        Address addrA = new Address();
        taxA.setRate(new BigDecimal("1.00"));
        taxA.setAddress(addrA);
        addrA.setCountryCode("CN");
        addrA.setCountryCode("GZ");

        Tax taxB = (Tax) taxA.clone();
        Address addrB = taxB.getAddress();
        addrB.setCountryCode("AU");
        addrB.setChildCode("BS");
        taxB.setAddress(addrB);
        taxB.setRate(new BigDecimal("2.00"));

        log.info("taxA's hashCode: {}, address's hashCode: {}, object value: {}", taxA.hashCode(), taxA.getAddress().hashCode(), taxA);
        log.info("taxB's hashCode: {}, address's hashCode: {}, object value: {}", taxB.hashCode(), taxB.getAddress().hashCode(), taxB);


    }

结果:

taxA's hashCode: 316009, address's hashCode: 129510, object value: Tax(rate=1.00, address=Address(countryCode=AU, childCode=BS))
taxB's hashCode: 498909, address's hashCode: 129510, object value: Tax(rate=2.00, address=Address(countryCode=AU, childCode=BS))

解释:

从结果看到,修改了拷贝对象的引用类型的值同时会改变原对象的值,从hashCode可以看出他们是同一个对象,引用都是指向同一个内存地址。

3 深拷贝

3.1 概念

深拷贝拷贝引用类型的数据时是重新申请一个内存区域存储拷贝过来的值,与浅拷贝不同。其余的地方都与浅拷贝相同。如下图所示:

浅拷贝和深拷贝的区别 python 浅拷贝和深拷贝的实现_浅拷贝和深拷贝的区别 python_02

如上图所示,重新申请了一个内存空间存储拷贝后的引用类型的数据值。

3.2 例子

3.2.1 实现Cloneable接口的方式

核心是每个引用类型都需要实现Cloneable接口,拷贝出来的引用类型对象需要赋值给拷贝出来的引用类型对象。

@Data
public class Address implements Cloneable{

    /**
     * 国家码
     */
    private String countryCode;
    /**
     * 省/州 城市 码
     */
    private String childCode;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
@Data
public class Tax implements Cloneable{

    /**
     * 税率
     */
    private BigDecimal rate;
    /**
     * 地址
     */
    private Address address;

    @Override
    public Object clone() throws CloneNotSupportedException {
        Tax tax = (Tax) super.clone();
        tax.address = (Address) address.clone();
        return tax;
    }
}
@Slf4j
public class DeepCopy {

    public static void main(String[] args) throws CloneNotSupportedException {
        Tax taxA = new Tax();
        Address addrA = new Address();
        taxA.setRate(new BigDecimal("1.00"));
        taxA.setAddress(addrA);
        addrA.setCountryCode("CN");
        addrA.setCountryCode("GZ");

        Tax taxB = (Tax) taxA.clone();
        Address addrB = taxB.getAddress();
        addrB.setCountryCode("AU");
        addrB.setChildCode("BS");
        taxB.setAddress(addrB);
        taxB.setRate(new BigDecimal("2.00"));

        log.info("taxA's hashCode: {}, address's hashCode: {}, object value: {}", taxA.hashCode(), taxA.getAddress().hashCode(), taxA);
        log.info("taxB's hashCode: {}, address's hashCode: {}, object value: {}", taxB.hashCode(), taxB.getAddress().hashCode(), taxB);

    }
}

结果:

taxA's hashCode: 325192, address's hashCode: 138693, object value: Tax(rate=1.00, address=Address(countryCode=GZ, childCode=null))
taxB's hashCode: 498909, address's hashCode: 129510, object value: Tax(rate=2.00, address=Address(countryCode=AU, childCode=BS))

解释:
可以看到hashCode都不同,修改拷贝后的引用类型的数据值,并不会改变原来的引用类型的数据值

3.2.2 实现Serializable接口的方式

原理与实现Cloneable接口方式一样,每一层的引用数据类型的对象都需要实现Serializable接口。

@Data
public class Address implements Serializable {

    /**
     * 国家码
     */
    private String countryCode;
    /**
     * 省/州 城市 码
     */
    private String childCode;

}
@Data
public class Tax implements Serializable {

    /**
     * 税率
     */
    private BigDecimal rate;
    /**
     * 地址
     */
    private Address address;

}
@Slf4j
public class SerializeDeepCopy {

    public static void main(String[] args) throws CloneNotSupportedException {
        Tax taxA = new Tax();
        Address addrA = new Address();
        taxA.setRate(new BigDecimal("1.00"));
        taxA.setAddress(addrA);
        addrA.setCountryCode("CN");
        addrA.setCountryCode("GZ");


        Tax taxB = SerializationUtils.clone(taxA);
        Address addrB = taxB.getAddress();
        addrB.setCountryCode("AU");
        addrB.setChildCode("BS");
        taxB.setAddress(addrB);
        taxB.setRate(new BigDecimal("2.00"));

        log.info("taxA's hashCode: {}, address's hashCode: {}, object value: {}", taxA.hashCode(), taxA.getAddress().hashCode(), taxA);
        log.info("taxB's hashCode: {}, address's hashCode: {}, object value: {}", taxB.hashCode(), taxB.getAddress().hashCode(), taxB);

    }
}

结果:

taxA's hashCode: 325192, address's hashCode: 138693, object value: Tax(rate=1.00, address=Address(countryCode=GZ, childCode=null))
taxB's hashCode: 498909, address's hashCode: 129510, object value: Tax(rate=2.00, address=Address(countryCode=AU, childCode=BS))

3.2.3 JSON转换的方式

底层原理是反射,所以性能比较低。若使用实现Cloneable接口方式或Serializable接口方式,则每一层中的引用类型都需要实现Cloneable接口,实现起来很麻烦不易于维护以及扩展。

@Data
public class Address{

    /**
     * 国家码
     */
    private String countryCode;
    /**
     * 省/州 城市 码
     */
    private String childCode;

}
@Data
public class Tax{

    /**
     * 税率
     */
    private BigDecimal rate;
    /**
     * 地址
     */
    private Address address;

}
@Slf4j
public class JSONDeepCopy {

    public static void main(String[] args) throws CloneNotSupportedException {
        Tax taxA = new Tax();
        Address addrA = new Address();
        taxA.setRate(new BigDecimal("1.00"));
        taxA.setAddress(addrA);
        addrA.setCountryCode("CN");
        addrA.setCountryCode("GZ");

        String taxAJsonStr = JSON.toJSONString(taxA);
        Tax taxB = JSON.parseObject(taxAJsonStr, Tax.class);
        Address addrB = taxB.getAddress();
        addrB.setCountryCode("AU");
        addrB.setChildCode("BS");
        taxB.setAddress(addrB);
        taxB.setRate(new BigDecimal("2.00"));

        log.info("taxA's hashCode: {}, address's hashCode: {}, object value: {}", taxA.hashCode(), taxA.getAddress().hashCode(), taxA);
        log.info("taxB's hashCode: {}, address's hashCode: {}, object value: {}", taxB.hashCode(), taxB.getAddress().hashCode(), taxB);

    }
}

结果:

taxA's hashCode: 325192, address's hashCode: 138693, object value: Tax(rate=1.00, address=Address(countryCode=GZ, childCode=null))
taxB's hashCode: 498909, address's hashCode: 129510, object value: Tax(rate=2.00, address=Address(countryCode=AU, childCode=BS))