Java 如何复制 List ?

article/2025/8/24 10:02:48

  List 复制在项目开发时,使用到的频率还是比较高的。List 复制有浅拷贝和深拷贝两种方式。在陈述复制方法前,先总结下什么是浅拷贝和深拷贝(以下内容均站在 Java 语言基础上进行讨论)。

一、什么是浅拷贝(Shallow Copy)和深拷贝(Deep Copy)?

  浅拷贝只复制某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存。深拷贝会创造一个一模一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对象。
  假设 B 复制了 A,当修改 A 时,看 B 是否会发生变化。如果 B 也跟着变了,说明这是浅拷贝,如果 B 没变,那就是深拷贝。

1.1 浅拷贝

  对于数据类型是基本数据类型(整型:byte、short、int、long;字符型:char;浮点型:float、double;布尔型:boolean)的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。

  对于数据类型是引用数据类型(比如说成员变量是某个数组、某个类的对象等)的成员变量,浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例,在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

1.2 深拷贝

  相对于浅拷贝而言,深拷贝对于引用类型的修改,并不会影响到对应的拷贝对象的值。

  备注:一般在讨论深拷贝和浅拷贝时,通常是针对引用数据类型而言的。因为基本数据类型在进行赋值操作时(也就是拷贝)是直接将值赋给了新的变量,也就是该变量是原变量的一个副本,这个时候你修改两者中的任何一个的值都不会影响另一个。而对于引用数据类型来说,在进行浅拷贝时,只是将对象的引用复制了一份,也就是内存地址,即两个不同的变量指向了同一个内存地址,那么改变任一个变量的值,都是改变这个内存地址所存储的值,所以两个变量的值都会改变。
  Java 对对象和基本数据类型的处理是不一样的。在 Java 中,用对象作为入口参数传递时,缺省为 “引用传递”,也就是说仅仅传递了对象的一个”引用”。当方法体对输入变量修改时,实质上就是直接操作这个对象。 除了在函数传值的时候是”引用传递”,在任何用 ”=” 向对象变量赋值的时候都是”引用传递”。
  将对象序列化为字节序列后,再通过反序列化即可完美地实现深拷贝。

1.3 对象如何实现深拷贝?

  Object 对象声明了 clone() 方法,如下代码所示。

	/*** Creates and returns a copy of this object.  The precise meaning* of "copy" may depend on the class of the object. The general* intent is that, for any object {@code x}, the expression:* <blockquote>* <pre>* x.clone() != x</pre></blockquote>* will be true, and that the expression:* <blockquote>* <pre>* x.clone().getClass() == x.getClass()</pre></blockquote>* will be {@code true}, but these are not absolute requirements.* While it is typically the case that:* <blockquote>* <pre>* x.clone().equals(x)</pre></blockquote>* will be {@code true}, this is not an absolute requirement.* <p>* By convention, the returned object should be obtained by calling* {@code super.clone}.  If a class and all of its superclasses (except* {@code Object}) obey this convention, it will be the case that* {@code x.clone().getClass() == x.getClass()}.* <p>* By convention, the object returned by this method should be independent* of this object (which is being cloned).  To achieve this independence,* it may be necessary to modify one or more fields of the object returned* by {@code super.clone} before returning it.  Typically, this means* copying any mutable objects that comprise the internal "deep structure"* of the object being cloned and replacing the references to these* objects with references to the copies.  If a class contains only* primitive fields or references to immutable objects, then it is usually* the case that no fields in the object returned by {@code super.clone}* need to be modified.* <p>* The method {@code clone} for class {@code Object} performs a* specific cloning operation. First, if the class of this object does* not implement the interface {@code Cloneable}, then a* {@code CloneNotSupportedException} is thrown. Note that all arrays* are considered to implement the interface {@code Cloneable} and that* the return type of the {@code clone} method of an array type {@code T[]}* is {@code T[]} where T is any reference or primitive type.* Otherwise, this method creates a new instance of the class of this* object and initializes all its fields with exactly the contents of* the corresponding fields of this object, as if by assignment; the* contents of the fields are not themselves cloned. Thus, this method* performs a "shallow copy" of this object, not a "deep copy" operation.* <p>* The class {@code Object} does not itself implement the interface* {@code Cloneable}, so calling the {@code clone} method on an object* whose class is {@code Object} will result in throwing an* exception at run time.** @return     a clone of this instance.* @throws  CloneNotSupportedException  if the object's class does not*               support the {@code Cloneable} interface. Subclasses*               that override the {@code clone} method can also*               throw this exception to indicate that an instance cannot*               be cloned.* @see java.lang.Cloneable*/protected native Object clone() throws CloneNotSupportedException;

  Object 的 clone() 方法本身是一个浅拷贝的方法,但是我们可以通过实现 Cloneable 接口,重写该方法来实现深拷贝,除了调用父类中的 clone() 方法得到新的对象, 还要将该类中的引用变量也 clone 出来。如果只是用 Object 中默认的 clone() 方法,是浅拷贝的。
  如下代码所示,我们创建一个测试类 TestClone,实现深拷贝。

package com.example.test;import lombok.Data;
import lombok.SneakyThrows;@Data
public class TestClone implements Cloneable {private String a;// 构造函数TestClone(String str) {this.a = str;}@Overrideprotected TestClone clone() throws CloneNotSupportedException {TestClone newTestClone = (TestClone) super.clone();newTestClone.setA(this.a);return newTestClone;}@SneakyThrowspublic static void main(String[] args) {TestClone clone1 = new TestClone("a");TestClone clone2 = clone1.clone();System.out.println(clone2.a);     // aclone2.setA("b");System.out.println(clone1.a);     // aSystem.out.println(clone2.a);     // b}
}
二、List 复制

  List 复制时有深拷贝和浅拷贝两类方式,分述如下。

2.1 浅拷贝

  List 其本质就是数组,而其存储的形式是地址,如下图所示。
在这里插入图片描述

  将 listA 列表浅拷贝为 listB 时,listA 与 listB 指向同一地址,造成的后果就是,改变 listB 的同时也会改变 listA,因为改变listB 就是改变 listB 所指向的地址的内容,由于 listA 也指向同一地址,所以 listA 与 listB 一起改变。其常见的实现方式有如下几种(以上述代码中的 TestClone 为测试类)。

2.1.1 循环遍历复制(含测试方法)
	@SneakyThrowspublic static void main(String[] args) {List<TestClone> listA = new ArrayList<>();TestClone testClone = new TestClone("a");listA.add(testClone);System.out.println(listA);  // [TestClone(a=a)]List<TestClone> listB = new ArrayList<>(listA.size());for (TestClone clone : listA) {listB.add(clone);}System.out.println(listB);  // [TestClone(a=a)]listA.get(0).setA("b");System.out.println(listA);  // [TestClone(a=b)]System.out.println(listB);  // [TestClone(a=b)]}
2.1.2 使用 List 实现类的构造方法
	@SneakyThrowspublic static void main(String[] args) {List<TestClone> listA = new ArrayList<>();TestClone testClone = new TestClone("a");listA.add(testClone);List<TestClone> listB = new ArrayList<>(listA);}
2.1.3 使用 list.addAll() 方法
	@SneakyThrowspublic static void main(String[] args) {List<TestClone> listA = new ArrayList<>();TestClone testClone = new TestClone("a");listA.add(testClone);List<TestClone> listB = new ArrayList<>();listB.addAll(listA);}
2.1.4 使用 java.util.Collections.copy() 方法
	public static void main(String[] args) {List<TestClone> listA = new ArrayList<>();TestClone clone = new TestClone("a");listA.add(clone);List<TestClone> listB = new ArrayList<>();listB.add(new TestClone("c"));System.out.println(listB);      // [TestClone(a=c)]Collections.copy(listB, listA);System.out.println(listB);      // [TestClone(a=a)]listA.get(0).setA("b");System.out.println(listA);      // [TestClone(a=b)]System.out.println(listB);      // [TestClone(a=b)]}
2.1.5 使用 Java 8 Stream API 将 List 复制到另一个 List 中
	public static void main(String[] args) {List<TestClone> listA = new ArrayList<>();TestClone clone = new TestClone("a");listA.add(clone);List<TestClone> listB = listA.stream().collect(Collectors.toList());System.out.println(listB); // [TestClone(a=a)]listA.get(0).setA("b");System.out.println(listA); // [TestClone(a=b)]System.out.println(listB); // [TestClone(a=b)]}
2.1.6 在 JDK 10 中的使用方式

  JDK 10 在线调试网址:https://lightly.teamcode.com/

	public static void main(String[] args) {List<TestClone> listA = new ArrayList<>();TestClone clone = new TestClone("a");listA.add(clone);List<TestClone> listB = List.copyOf(listA);listA.get(0).setA("b");System.out.println(listA);                  // [TestClone@76ed5528]System.out.println(listA.get(0).getA());    // bSystem.out.println(listB);                  // [TestClone@76ed5528]System.out.println(listB.get(0).getA());    // b}
2.2 深拷贝

在这里插入图片描述

  如图,深拷贝就是将 listA 复制给 listB 的同时,给 listB 创建新的地址,再将地址 A 的内容传递到地址 B。listA 与 listB 内容一致,但是由于所指向的地址不同,所以各自改变内容不会影响对方。深拷贝时,向新列表添加的是原列表中的元素执行 clone() 方法后的新对象。

	@SneakyThrowspublic static void main(String[] args) {List<TestClone> listA = new ArrayList<>();TestClone testClone = new TestClone("a");listA.add(testClone);List<TestClone> listB = new ArrayList<>();listB.add(listA.get(0).clone());System.out.println(listB);  // [TestClone(a=a)]listA.get(0).setA("b");System.out.println(listA);  // [TestClone(a=b)]System.out.println(listB);  // [TestClone(a=a)]}

  本文提到的几种浅拷贝的方法对于 List<String> 可以实现深拷贝的效果,其测试代码和原因分析见 《Java 如何实现 List<String> 的深拷贝?》。

文章参考:
  • 深拷贝和浅拷贝的区别
  • List的深拷贝与浅拷贝

http://chatgpt.dhexx.cn/article/asrB6Yk5.shtml

相关文章

Java对象复制

文章目录 前言何不可变类对象复制方式1.直接赋值2.浅拷贝3.深拷贝 对象复制方案1.get/set2.Spring BeanUtils3.Apache BeanUtils4.BeanCopier5.Orika6.Dozer7.MapStruct8.Bean Mapping9.Bean Mapping ASM10.ModelMapper11.JMapper12.Json2Json 复制方案选择 前言 在我们实际项…

Java 复制Excel工作表

本文归纳了关于Java如何复制Excel工作表的方法&#xff0c;按不同复制需求&#xff0c;可分为&#xff1a; 1. 复制工作表 1.1 在同一个工作簿内复制工作表 1.2 在不同工作簿间复制工作表 2. 复制指定单元格数据 对于复制方法copy()&#xff0c;这里简单整理了一个表格&am…

个人如何接入微信支付和支付宝等支付接口,免签约

企业的资质足够高了才能够得到微信或者支付宝官方的支付接口&#xff08;而且这个官方接口收费的最低费率在0.38%以上&#xff09;那么个人如何做&#xff1f; 个人开发者或者小微企业团队如何使用在线收款支付功能呢&#xff1f; 第四方支付&#xff0c;市面上各种收款工具要…

微信支付接口开发详流程

微信支付 文章目录 1.支付接口分析2. 开发创建订单接口3. 开发根据订单id查询订单详情接口4. 开发生成二维码接口5. 开发查询订单支付状态接口 1.支付接口分析 引入依赖 <dependencies><dependency><groupId>com.github.wxpay</groupId><artifact…

微信支付API v3接口使用应用篇

目录 前言版本应用基础配置1.申请商户API证书2.设置接口密钥3.下载平台证书 接口实测微信支付API官方客户端1.客户端2.支付调起参数签名3.回调通知 参考资料 前言 最近新项目中有涉及到微信支付相关接口业务的交互&#xff0c;毕竟原先开发接触过支付这块&#xff0c;轻车熟路…

Java对接微信、支付宝、银联第三方支付

一、微信支付 1、业务平台介绍&#xff1a; &#xff08;1&#xff09;微信公众平台 微信公众平台是微信公众账号申请入口和管理后台。商户可以在公众平台提交基本资料、业务资料、财务资料申请开通微信支付功能。 &#xff08;2&#xff09; 微信开放平台 微信开放平台是商…

php微信支付宝第三方接口开发平台,帝国CMS第三方个人支付接口微信支付宝免签约即时到账api_帝国网站管理系统插件...

帝国CMS网站管理系统是一个高效的网站解决方案,本文将给出开发好的帝国CMS第三方个人免签约支付接口插件(支持支付宝/微信扫码即时到账支付),可用于帝国系统收款使用,供有需求的用户参考或快速接入。本代码无加密完全开源,欢迎参考/使用。 概述 环境 本接口插件基于帝国CMS…

第三方支付接口之微信扫码支付

此篇文章是为了记录学习如何编写第三方支付接口&#xff0c;熟悉这个流程。使用的是威富通第三方支付平台https://open.swiftpass.cn/ 对接的是微信扫码和公总号支付 基本成员&#xff1a;用户&#xff0c;商户&#xff0c;第三方支付方 扫码支付业务流程&#xff1a; 接口…

第三方支付接口有哪些?怎么申请?

第三方支付接口有哪些&#xff1f; 目前中国国内的第三方支付产品主要有支付宝&#xff08;阿里巴巴旗下&#xff09;、微信&#xff08;腾讯公司&#xff09;、QQ钱包&#xff08;财付通公司&#xff09;、云闪付&#xff08;中国银联旗下&#xff09;等。其中最用户数量最大…

调用微信和支付宝第三方接口方法总结

最近项目上用到了调用微信和支付宝的第三方支付接口&#xff0c;因为以前没用过&#xff0c;所以这次用到了之后总结一下分享给大家&#xff0c;这里介绍两种支付方式&#xff0c;即app支付和扫码支付方式。 一、app支付&#xff08;这里只介绍java端调用支付&#xff0c;安卓…

第三方支付:微信公众号接入支付宝支付开发

第三方支付&#xff1a;微信公众号接入支付宝支付开发 引言 这篇文章使用一些简单的代码例子来解释微信接入支付宝支付功能的操作步骤&#xff0c;即使新手也可以轻松参透的。 第三方支付是指具备一定实力和信誉保障的独立机构&#xff0c;采用与各大银行签约的方式&#xff…

第三方支付接入(微信,支付宝)

写在最前面 以下内容关于微信相关的&#xff0c;除了binarywang&#xff0c;个人认为都不要再用了。相比较王大哥的封装&#xff0c;我自己写的真是连弟弟都不如。 支付宝相关的&#xff0c;好久不用了&#xff0c;不知道还能不能工作。建议找创建时间比较新的文章来看。 关…

微信第三方支付接口java调用详细文档

1.显示支付二维码 支付流程 首先用户选好商品后 跳到结算页面 在点击支付提交时应先将表单的数据保存到数据库(一般都会有订单表一二级) 经过后台保存数据后再转发到前台(二维码是后台调用微信生成的) 看到扫码页面 当用户扫码成功后 更改订单状态为已支付(一般情况 根据业务…

微信支付接口配置教程(下)

微信第三方平台微信支付接口配置教程&#xff08;下&#xff09; 上传微信支付证书 登录后台系统&#xff0c;在后台微信商城里&#xff0c;点击微信支付证书。这一步我们需要上传对应的微信支付证书&#xff0c;这个证书就是文章《微信第三方平台微信支付接口配置教程&#…

聊一聊对“事务”的理解

什么是事务 事务即英文Transaction&#xff0c;在软件开发过程中&#xff0c;难免需要考虑处理事务。从微观层面看亦或者从成员最早了解到这个词汇看&#xff0c;事务通常指多条写入数据库的语句需要并发成功执行&#xff0c;从宏观层面看得话则是客户端发出的并发请求需要一致…

聊一聊wifi6

引言 8.11&#xff0c;小米十周年&#xff0c;雷军即将召开发布会&#xff0c;相信又是are you ok 放大招的时候了。作为小米各种产品的使用者&#xff08;包括但不限于手机&#xff0c;路由器&#xff0c;音响&#xff0c;手环&#xff0c;中性笔… &#xff09;&#xff0c;…

聊一聊我对测试开发的看法

前言 在一线大厂&#xff0c;没有测试这个岗位&#xff0c;只有测开这个岗位 即使是做业务测试&#xff0c;那么你的title也是测开 所以想聊一聊测开的看法 但不代表这是正确的看法&#xff0c;仅供参考 还没来阿里之前&#xff0c;我对测开的看法 一直以为专职做自动化测…

聊一聊,我对DDD的关键理解

作者&#xff1a;闵大为 阿里业务平台解决方案团队 当我们在学习DDD的过程中&#xff0c;感觉学而不得的时候&#xff0c;可能会问&#xff1a;我们还要学么&#xff1f;这的确引人深思。本文基于工作经验&#xff0c;尝试谈谈对DDD的一些理解。 一、序 《阿甘正传》中&#xf…

聊一聊Kotlin的泛型

Kotlin的泛型 简介 与java一样&#xff0c;kotlin也支持泛型&#xff0c;用法和java泛型差别不大&#xff0c;kotlin特色是型变支持。 基本用法&#xff1a; 定义类&#xff1a; 跟java相同&#xff0c;定义在类后面的尖括号&#xff1a; open class Basket<T>{}定…

聊一聊学习方法

聊一聊学习方法 为什么学习没效果&#xff1f;你只是看起来很努力自知与不自知&#xff0c;真的不是一字之差什么时候真的能出效果&#xff1f;学习方法一&#xff08;拍照式记忆&#xff09;学习方法二&#xff08;殊途同归&#xff09;学习方法三&#xff08;精细化管理&…