Java深拷贝的几种方式

584 阅读3分钟

1.简介

1.1 深拷贝和浅拷贝

**深拷贝:**创建一个新的对象,将源对象的各项属性的 "值" 拷贝过来,是 "值" 而不是 "引用" ,新对象跟源对象不共享内存,修改新对象不会影响源对象

**浅拷贝:**将源对象的引用直接赋给新对象,新对象只是源对象的一个引用,修改新对象会影响到源对象

2.实体类

2.1 资料类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Document {

    /**
     * 资料id
     */
    private Integer id;

    /**
     * 资料名称
     */
    private String name;

    /**
     * 资料价格
     */
    private BigDecimal price;

    /**
     * 资料提供者
     */
    private User user;

}

2.2 用户类(资料提供者)

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    /**
     * 用户id
     */
    private Integer id;

    /**
     * 用户名称
     */
    private String name;

}

2.3 注意事项

如果下面的实现方式中对Document类和User类有特殊要求,会将实现后的代码贴出来,如果不需要,那么使用的就是上面简单的Document类和User类

3.实现方式

3.1 手动赋值

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Document {

    /**
     * 资料id
     */
    private Integer id;

    /**
     * 资料名称
     */
    private String name;

    /**
     * 资料价格
     */
    private BigDecimal price;

    /**
     * 资料提供者
     */
    private User user;

    /**
     * 克隆资料对象
     *
     * @param document 源资料对象
     * @return 克隆资料对象
     */
    public Document cloneDocument() {
        return Document.builder()
                .id(this.getId())
                .name(this.getName())
                .price(this.getPrice())
                .user(new User(this.getUser().getId(), this.getUser().getName()))
                .build();
    }

}



//测试
public static void main(String[] args) throws CloneNotSupportedException {
        Document document = Document.builder()
                .id(1)
                .name("五年高考,三年模拟")
                .price(new BigDecimal("29.9"))
                .user(new User(1, "韩非子"))
                .build();

        Document cloneDocument = document.cloneDocument();
        System.out.println(document == cloneDocument);
        System.out.println(document);
        System.out.println(cloneDocument);
}

3.2实现Cloneable, 重写clone

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Document implements Cloneable{

    /**
     * 资料id
     */
    private Integer id;

    /**
     * 资料名称
     */
    private String name;

    /**
     * 资料价格
     */
    private BigDecimal price;

    /**
     * 资料提供者
     */
    private User user;

    /**
     * 重写clone
     *
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected Document clone() throws CloneNotSupportedException {
        return (Document) super.clone();
    }

}



//测试
public static void main(String[] args) throws CloneNotSupportedException {
        Document document = Document.builder()
                .id(1)
                .name("五年高考,三年模拟")
                .price(new BigDecimal("29.9"))
                .user(new User(1, "韩非子"))
                .build();

        Document cloneDocument = document.clone();
        System.out.println(document == cloneDocument);
        System.out.println(document);
        System.out.println(cloneDocument);
    }

3.3 序列化→反序列化

3.3.1 Jdk序列化

将对象进行Jdk序列化,然后进行反序列化生成新对象,拷贝类(包括其成员变量)需要实现Serializable接口(以Apache Commons Lang为例)

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable{

    /**
     * 用户id
     */
    private Integer id;

    /**
     * 用户名称
     */
    private String name;

}



@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Document implements Serializable {

    /**
     * 资料id
     */
    private Integer id;

    /**
     * 资料名称
     */
    private String name;

    /**
     * 资料价格
     */
    private BigDecimal price;

    /**
     * 资料提供者
     */
    private User user;

}



//测试
public static void main(String[] args) throws CloneNotSupportedException {
        Document document = Document.builder()
                .id(1)
                .name("五年高考,三年模拟")
                .price(new BigDecimal("29.9"))
                .user(new User(1, "韩非子"))
                .build();

        Document cloneDocument = SerializationUtils.clone(document);
        System.out.println(document == cloneDocument);
        System.out.println(document);
        System.out.println(cloneDocument);
    }

3.3.2 Json序列化

将对象进行Json序列化,然后进行反序列化生成新对象(以FastJson为例 )

//测试
public static void main(String[] args) throws CloneNotSupportedException {
        Document document = Document.builder()
                .id(1)
                .name("五年高考,三年模拟")
                .price(new BigDecimal("29.9"))
                .user(new User(1, "韩非子"))
                .build();

        String documentString = JSON.toJSONString(document);
        Document cloneDocument = JSON.parseObject(documentString, Document.class);
        System.out.println(document == cloneDocument);
        System.out.println(document);
        System.out.println(cloneDocument);
    }

3.4 反射

  • 以Spring BeanUtils.copyProperties(source, target)为例
  • 调用原理:target.set + source的属性名(source.get + source的属性名):所有source必须有get方法,target必须有set方法
public static void main(String[] args) throws CloneNotSupportedException {
        Document document = Document.builder()
                .id(1)
                .name("五年高考,三年模拟")
                .price(new BigDecimal("29.9"))
                .user(new User(1, "韩非子"))
                .build();

        Document cloneDocument = new Document();
        BeanUtils.copyProperties(document, cloneDocument);

        System.out.println(document == cloneDocument);
        System.out.println(document);
        System.out.println(cloneDocument);
    }

4.总结

深拷贝实现方式优点缺点

手动赋值
1. 实现简单
2. 系统开销小
3. 不需要引入第三方的库
1. 可用性差,每次成员变量变更都需要修改方法



重写clone
1. 实现简单
2.系统开销小
3. 不需要引入第三方的库
4. 可用性强,成员变量变更不需要修改方法
1. 拷贝类(包括其成员变量),需要实现Cloneable接口



序列化→反序列化
1. 可用性强,成员变量变更不需要修改方法1. 底层实现复杂
2. 需要引入第三方的包
3. 如果是Jdk序列化,拷贝类(包括其成员变量)需要实现Serializable接口
3. 序列化与反序列化存在一定的系统开销

反射
1. 可用性强,成员变量变更不需要修改方法1. 底层实现复杂
2. 需要引入第三方的包
3. 反射存在一定的系统开销