Java对象深拷贝详解(List深拷贝)

这篇具有很好参考价值的文章主要介绍了Java对象深拷贝详解(List深拷贝)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、Java中拷贝的概念

在Java语言中,拷贝一个对象时,有浅拷贝与深拷贝两种

浅拷贝:只拷贝源对象的地址,所以新对象与老对象共用一个地址,当该地址变化时,两个对象也会随之改变。

深拷贝:拷贝对象的所有值,即使源对象发生任何改变,拷贝的值也不会变化。

在User类的基础上,介绍两种浅拷贝案列

User类:

@Data
public class User {
    private String name;
    private Integer age;
}

案列①:普通对象的浅拷贝

package com.shuizhu.study;
//浅拷贝案例1
public class Study01 {
    public static void main(String[] args) {
        User user1 = new User();
        user1.setName("张三");
        user1.setAge(18);
        User user2 = user1;
        System.out.println("user1未改变前,user2的名字为:" + user2.getName());
        user1.setName("李四");
        System.out.println("user1未改变前,user2的名字为:" + user2.getName());
    }
}

结果:改变user1后,user2的值也随之变化

java list 深拷贝,Java基础,java

案列②:List浅拷贝(这也是我们平时项目中,经常遇到的情况)

package com.shuizhu.study;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

//Java浅拷贝案列2
public class Study02 {
    public static void main(String[] args) {
        List<User> list1 = new ArrayList<>();
        User user1 = new User();
        user1.setName("张三");
        user1.setAge(18);
        User user2 = new User();
        user2.setName("李四");
        user2.setAge(19);
        list1.add(user1);
        list1.add(user2);
        //TODO 以下是开发中,经常发生的浅拷贝
        //方式1:通过new ArrayList方式,把list01拷贝给list02
        List<User> list2 = new ArrayList<>(list1);
        System.out.println("list1未改变前,list2的结果为:" + list2);

        //方式2:通过addAll方法,把list01拷贝给list02
        List<User> list3 = new ArrayList<>();
        list3.addAll(list1);
        System.out.println("list1未改变前,list3的结果为:" + list3);

        //方式3:通过stream流的方式,把list01拷贝给list02
        List<User> list4 = list1.stream().collect(Collectors.toList());
        System.out.println("list1未改变前,list4的结果为:" + list4);

        //改变list1集合中的user1对象
        System.out.println("--------------------------------------------");
        user1.setName("老六");
        user1.setAge(78);

        System.out.println("list1改变后,list2的结果为:" + list2);
        System.out.println("list1改变后,list3的结果为:" + list3);
        System.out.println("list1改变后,list4的结果为:" + list4);
    }
}

结果:对List的3种拷贝,其实都是浅拷贝,当源集合中对象发生改变时,新的List也会随之变化

java list 深拷贝,Java基础,java

2、常见的深拷贝方式

  1. 构造函数方式(new的方式)
  2. 重写clone方法
  3. Apache Commons Lang序列化
  4. Gson序列化
  5. Jackson序列化

2.1、构造函数方式

这种方式就是创建一个新的对象,然后通过源对象的get方法与新对象set方法,把源对象的值复制新对象,这里就不再演示了。

缺点:在拷贝的对象数量较少时,可以使用,但是对象数量过多时,会大大增加系统开销,开发中应避免使用。

2.2、重写clone方法

步骤:

1>需要拷贝对象的类,去实现Cloneable接口

2>重写clone方法

3>使用"对象.clone()"的方式进行拷贝

根据上面的案列,进行对应的改造:

首先是User实体类 ,如下:

@Data
public class User implements Cloneable{
    private String name;
    private Integer age;

    @Override
    protected User clone() throws CloneNotSupportedException {
        return (User) super.clone();
    }
}

改造案列①:

package com.shuizhu.study;
//Java深拷贝案列
public class Study03 {
    public static void main(String[] args) throws CloneNotSupportedException {
        User user1 = new User();
        user1.setName("张三");
        user1.setAge(18);
        User user2 = user1.clone();
        System.out.println("user1未改变前,user2的名字为:" + user2.getName());
        user1.setName("李四");
        System.out.println("user1未改变前,user2的名字为:" + user2.getName());
    }
}

结果:当user1改变后,user2的值不会改变

java list 深拷贝,Java基础,java

改造案列②:List类型深拷贝

package com.shuizhu.study;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

//Java深拷贝案列
public class Study04 {
    public static void main(String[] args) {
        List<User> list1 = new ArrayList<>();
        User user1 = new User();
        user1.setName("张三");
        user1.setAge(18);
        User user2 = new User();
        user2.setName("李四");
        user2.setAge(19);
        list1.add(user1);
        list1.add(user2);
        /
        //通过clone方式,把list01拷贝给list02
        List<User> list2 = new ArrayList<>();  
        //TODO 当数据量多时,建议使用对象的方式,把List当做属性,然后拷贝哦到一个新的对象中,从而不需要循环,可以见Apache Commons Lang序列化深拷贝方式
        list1.forEach(user->{
            try {
                list2.add(user.clone());
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
        });
        System.out.println("list1未改变前,list2的结果为:" + list2);
        
        //改变list1集合中的user1对象
        System.out.println("--------------------------------------------");
        user1.setName("老六");
        user1.setAge(78);

        System.out.println("list1改变后,list2的结果为:" + list2);
    }
}

结果:list1中的每个对象通过clone()添加list2中,当list1中的对象改变时,list2不会改变

java list 深拷贝,Java基础,java

2.3 、Apache Commons Lang序列化

步骤:

1>导入Commons包

<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>3:3.5</version>
</dependency>

2>实体类实现Serializable接口

@Data
public class User implements Serializable {
    private String name;
    private Integer age;
}

3>调用SerializationUtils工具类,实现深拷贝(注意:SerializationUtils不能直接拷贝List类型)

案列如下:

案列①:对象深拷贝

package com.shuizhu.study2;
import org.apache.commons.lang3.SerializationUtils;

//Apache Commons Lang序列化实现对象的深拷贝
public class Study01 {
    public static void main(String[] args) {
        User user1 = new User();
        user1.setName("张三");
        user1.setAge(18);
        User user2 = SerializationUtils.clone(user1);
        System.out.println("user1未改变前,user2的名字为:" + user2.getName());
        user1.setName("李四");
        System.out.println("user1改变后,user2的名字为:" + user2.getName());
    }
}

结果:user1的改变不会导致user2的改变,从而实现深拷贝

java list 深拷贝,Java基础,java 

案列②:List类型深拷贝

(1)改造开始,我们先创建一个专门用于拷贝List<User>类型的实体类

package com.shuizhu.study2;

import java.io.Serializable;
import java.util.List;

/**
 * @author 睡竹
 * @date 2022/12/10
 * 用于深拷贝时,不需要去遍历List<User>集合,只需要拷贝UserCopyDTO 对象就可以
 * 获取到新的List<User>集合
 */
@Data
public class UserCopyDTO implements Serializable {//必须实现Serializable接口
    private List<User> users;
}

(2)拷贝List类型

package com.shuizhu.study2;

import org.apache.commons.lang3.SerializationUtils;

import java.util.ArrayList;
import java.util.List;

//Apache Commons Lang序列化实现List的深拷贝
public class Study02 {
    public static void main(String[] args) {
        List<User> list1 = new ArrayList<>();
        User user1 = new User();
        user1.setName("张三");
        user1.setAge(18);
        User user2 = new User();
        user2.setName("李四");
        user2.setAge(19);
        list1.add(user1);
        list1.add(user2);

        //使用UserCopyDTO对象,专门用于拷贝List<User>类型数据,不需要再去遍历list1 
        UserCopyDTO userCopyDTO = new UserCopyDTO();
        userCopyDTO.setUsers(list1);
        //通过Apache Commons Lang序列化方式,把list01拷贝给list02
        UserCopyDTO clone = SerializationUtils.clone(userCopyDTO);
        List<User> list2 = clone.getUsers();
        System.out.println("list1未改变前,list2的结果为:" + list2);

        //改变list1集合中的user1对象
        System.out.println("--------------------------------------------");
        user1.setName("老六");
        user1.setAge(78);
        System.out.println("list1改变后,list2的结果为:" + list2);

    }
}

结果:

java list 深拷贝,Java基础,java

 2.4、Gson序列化

步骤:

1、导入Gson依赖

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.5</version>
</dependency>

2>创建Gson对象,使用该对象进行深拷贝(实体类不再需要实现Serializable接口)

案例如下:只演示对象的深拷贝,LIst类型的深拷贝与之前的流程是相似的

package com.shuizhu.study3;
import com.google.gson.Gson;

//Gson序列化实现对象的深拷贝
public class Study01 {
    public static void main(String[] args) {
        User user1 = new User();
        user1.setName("张三");
        user1.setAge(18);
        Gson gson = new Gson();
        User user2 = gson.fromJson(gson.toJson(user1), User.class);
        System.out.println("user1未改变前,user2的名字为:" + user2.getName());
        user1.setName("李四");
        System.out.println("user1改变后,user2的名字为:" + user2.getName());
    }
}

重点:

java list 深拷贝,Java基础,java

结果:

java list 深拷贝,Java基础,java 

 2.5、Jackson序列化

该方式与Gson原理、使用方式相似,但是Jackson序列化深拷贝,要求拷贝的对象必须有无参构造函数

步骤:

1>导入Jackson依赖

<dependency>
    <groupId>com.fasterxml.jackson</groupId>
    <artifactId>core</artifactId>
    <version>2.2.2</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson</groupId>
    <artifactId>databind</artifactId>
    <version>2.2.2</version>
</dependency>

2>创建ObjectMapper对象,进行深拷贝(用法与Gson一致)

package com.shuizhu.study4;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;

//Jackson序列化实现对象的深拷贝
public class Study01 {
    public static void main(String[] args) {
        User user1 = new User();
        user1.setName("张三");
        user1.setAge(18);
        ObjectMapper mapper = new ObjectMapper();
        User user2 = null;
        try {
            user2 = mapper.readValue(mapper.writeValueAsString(user1), User.class);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("user1未改变前,user2的名字为:" + user2.getName());
        user1.setName("李四");
        System.out.println("user1改变后,user2的名字为:" + user2.getName());
    }
}

重点:

java list 深拷贝,Java基础,java

结果:

java list 深拷贝,Java基础,java 

3、总结

方式 优点 缺点
构造函数 1. 底层实现简单 2. 不需要引入第三方包 3. 系统开销小 4. 对拷贝类没有要求,不需要实现额外接口和方法 1. 可用性差,每次新增成员变量都需要新增新的拷贝构造函数
重载clone()方法

1. 底层实现较简单 2. 不需要引入第三方包 3. 系统开销小

追求性能的可以采用该方式

1. 可用性较差,每次新增成员变量可能需要修改clone()方法 2. 拷贝类(包括其成员变量)需要实现Cloneable接口
Apache Commons Lang序列化 1. 可用性强,新增成员变量不需要修改拷贝方法 1. 底层实现较复杂 2. 需要引入Apache Commons Lang第三方JAR包 3. 拷贝类(包括其成员变量)需要实现Serializable接口 4. 序列化与反序列化存在一定的系统开销
Gson序列化 1. 可用性强,新增成员变量不需要修改拷贝方法 2. 对拷贝类没有要求,不需要实现额外接口和方法 1. 底层实现复杂 2. 需要引入Gson第三方JAR包 3. 序列化与反序列化存在一定的系统开销
Jackson序列化 1. 可用性强,新增成员变量不需要修改拷贝方法 1. 底层实现复杂 2. 需要引入Jackson第三方JAR包 3. 拷贝类(包括其成员变量)需要实现默认的无参构造函数 4. 序列化与反序列化存在一定的系统开销

 文章来源地址https://www.toymoban.com/news/detail-781085.html

 

 

 

 

到了这里,关于Java对象深拷贝详解(List深拷贝)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 对java中的List进行深拷贝,并进行删除测试

    ListString list = new ArrayList();  // 需要拷贝的原始List list.add(\\\"aaa\\\"); list.add(\\\"bbb\\\"); list.add(\\\"ccc\\\"); ListString listNew = new ArrayList();  // 新List // 将原始List的值赋值给新List CollectionUtils.mergeArrayIntoCollection(new Object[list.size()], listNew); Collections.copy(listNew, list); // 现在将心List中的值删除一个,会

    2024年02月11日
    浏览(23)
  • 【Java List与对象】List<T>对象和对象List<T>的区别(125)

    List<T>对象 :对象里面的元素属性包含List<T>(只有一个对象); 对象List<T> :List<T>里面的数据是对象类型(可以有多组对象,但必须是同一个类型); 测试案例: Book类: Student类: 测试Demo : ( 重点 ) 测试输出 :

    2024年02月11日
    浏览(26)
  • Java List对象集合转Json & Json转List对象集合

    使用了阿里巴巴的 fastjson  首先引入依赖 示例如下 输出结果:

    2024年02月15日
    浏览(37)
  • java8利用Stream方法求两个List对象的交集、差集与并集(即:anyMatch和allMatch和noneMatch的区别详解)

    1、anyMatch  判断数据列表中是否存在任意一个元素符合设置的predicate条件,如果是就返回true,否则返回false。 接口定义: boolean anyMatch(Predicate? super T predicate); 方法描述: 在anyMatch 接口定义中是接收 Predicate 类型参数,在Lamdba表达式中 PredicateT 是接收一个T类型参数,然后经过

    2024年02月06日
    浏览(34)
  • 【java】List对象集合去除特定对象的优雅写法(基于java8)

    【java】List对象集合去除特定对象的优雅写法(基于java8) 主要用的是 Java List.removeIf方法,该方法用于删除所有满足特定条件的数组元素,入参其实是一个布尔值的表达式即可。 使用场景,用户类中的性别字段有三个,分别是男、女、未知。 当前端要查询:“男”时,要求

    2024年02月16日
    浏览(28)
  • <Java工具类>json字符串、List Map,List 对象转换为相应的JavaBean对象

    依赖: 工具类(直接上代码): 使用场景: (1).使用泛型方法:把json字符串转换为相应的JavaBean对象 ; 一般用于:Controller层: 例如: (2).List Map转换List 对象:如List,将第二个参数传递为Student对象; (3).List 对象转换List Map:

    2024年02月12日
    浏览(48)
  • java List<对象> 根据对象的一个属性进行去重

    1.实现一个自定义函数,该函数将用于确定两个对象是否相等,例如: 这个函数接受一个Function对象作为参数,该函数将从对象中提取唯一标识属性,并使用ConcurrentHashMap来确定是否已经处理过该属性值。   2.现在你可以使用这个函数来进行去重了,例如:  这个代码将对m

    2024年02月12日
    浏览(30)
  • java 对象List中对象的某一属性重新赋值

    描述: 在我们开发过程中经常会遇到在拿到一个对象list集合的时候回对某一个字段重新赋值,比如:在无任务下面的若干子任务,开始执行主任务对子任务的状态统一设置待执行。这个时候就需要使用 Java8 stream 或者 List forEach 这两种方式来实现,方便快捷,也不需要再遍历

    2024年02月12日
    浏览(39)
  • Java8 stream、List forEach 遍历对象 List 对某一字段重新赋值

    Java8 stream、List forEach 遍历对象 List 对某一字段重新赋值 经常会需要遍历对象 List 对某一字段重新赋值,使用 Java8 stream 或者 List forEach 操作实现非常便捷。 一、Java8 stream 操作 示例代码如下: 二、List forEach 操作 示例代码如下: 将 Object 替换为需要提取的对象即可。 遍历对象

    2024年02月16日
    浏览(34)
  • JAVA 使用stream流将List中的对象某一属性创建新的List

    Java Stream是Java 8引入的一种新机制,它可以让我们以声明式方式操作集合数据,提供了更加简洁、优雅的集合处理方式。Stream是一个来自数据源的元素队列,并支持聚合操作。它的特点有: Stream 不会修改原始数据源。 Stream 操作是延迟执行的。 Stream 操作可以是无限的(比如

    2024年02月03日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包