Java中ArrayList的对象引用问题

前言

事件起因是由于同事使用ArrayList的带参构造方法进行ArrayList对象复制,修改新的ArrayList对象中的元素(对象)的成员变量时也会修改原ArrayList中的元素(对象)的成员变量。

下面会通过复盘代码向大家重现遇到的问题

复盘代码

用户类

public class User {

    private Integer id;

    private String name;

    public User(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

问题重现示例

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

public class ArrayListReference {

    public static void main(String[] args) {
        // 原用户列表
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            users.add(new User(i, "test"));
        }
        // 新用户列表
        List<User> newUsers = new ArrayList<>(users);
        for (int j = 0; j < newUsers.size(); j++) {
            //  修改新用户列表的用户名
            newUsers.get(j).setName(String.valueOf(j));
        }
        // 打印新用户列表
        System.out.println("newUsers:" + newUsers);
        // 重新打印原用户列表
        System.out.println("After update newUsers,users:" + users);
    }
}

示例运行结果

users:[User{id=0, name='test'}, User{id=1, name='test'}, User{id=2, name='test'}, User{id=3, name='test'}, User{id=4, name='test'}, User{id=5, name='test'}, User{id=6, name='test'}, User{id=7, name='test'}, User{id=8, name='test'}, User{id=9, name='test'}]
newUsers:[User{id=0, name='0'}, User{id=1, name='1'}, User{id=2, name='2'}, User{id=3, name='3'}, User{id=4, name='4'}, User{id=5, name='5'}, User{id=6, name='6'}, User{id=7, name='7'}, User{id=8, name='8'}, User{id=9, name='9'}]
After update newUsers,users:[User{id=0, name='0'}, User{id=1, name='1'}, User{id=2, name='2'}, User{id=3, name='3'}, User{id=4, name='4'}, User{id=5, name='5'}, User{id=6, name='6'}, User{id=7, name='7'}, User{id=8, name='8'}, User{id=9, name='9'}]

分析

问题

为什么使用了ArrayList的构造方法重新构造一个新的ArrayList后,操作新ArrayList对象中的元素时会影响到原来的ArrayList中的元素呢?

首先需要分析ArrayList的构造方法

ArrayList源码分析

下面是示例中调用的ArrayList构造方法的源码

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                // 此处为关键代码,此处就是数组元素的复制方法
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

从源码中得知数组复制的关键代码为

elementData = Arrays.copyOf(elementData, size, Object[].class);

下面进入Arrays.copyOf()的源码进行研究

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        // 构造一个新的数组对象
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        // 将原数组元素复制到新数组中
        System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
        return copy;
    }

从上面的源码得知关键代码为

System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));

以下为System.arraycopy()方法的源码

public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos, int length);

由于System.arraycopy()方法为native方法,很难跟踪其实现代码。不过可以从方法注释中可以知道这个方法的特点:

Copies an array from the specified source array, beginning at the specified position, to the specified position of the destination array. A subsequence of array components are copied from the source array referenced by src to the destination array referenced by dest. The number of components copied is equal to the length argument. The components at positions srcPos through srcPos+length-1 in the source array are copied into positions destPos through destPos+length-1, respectively, of the destination array.

翻译结果为

将数组从指定的源数组(从指定位置开始)复制到目标数组的指定位置。将数组组件的子序列从src引用的源数组复制到dest引用的目标数组,复制的组件数量等于length参数。源数组中通过srcPos+length-1位置的组件分别复制到目标数组中通过destPos+length-1位置的destPos。

既然ArrayList的构造方法是复制新的数组,那么是为什么呢?这里提前透露一下结论:数组元素为对象时,实际上存储的是对象的引用,ArrayList进行数组复制也只是复制了对象的引用。所以才会出现一开始说的问题

再次验证

下面将会使用一个数组的复制示例验证结论,使用==来比较对象引用是否相同

问题重现示例

import java.util.Arrays;

public class ArrayReference {

    public static void main(String[] args) {
        // 原用户列表
        User[] users = new User[10];
        for (int i = 0; i < users.length; i++) {
            users[i] = (new User(i, "test"));
        }
        // 新用户列表
        User[] newUsers = Arrays.copyOf(users, users.length);
        for (int j = 0; j < users.length; j++) {
            // 比较对象引用
            System.out.println(j + ":" + (users[j] == newUsers[j]));
        }
    }
}

示例运行结果

0:true
1:true
2:true
3:true
4:true
5:true
6:true
7:true
8:true
9:true

结果分析

从运行结果中可以得知,上面提出的结论是正确的。即数组元素为对象时,实际上存储的是对象的引用

解决办法

解决方法很简单,只需要遍历对象数组中的元素,调用对象的构造方法构造新的对象并加入新的数组中即可

解决办法示例

public class ArrayListReferenceSolution {

    public static void main(String[] args) {
        // 原用户列表
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            users.add(new User(i, "test"));
        }
        // 新用户列表
        List<User> newUsers = new ArrayList<>();
        for (int j = 0; j < users.size(); j++) {
            // 使用构造方法构造新的对象
            newUsers.add(new User(users.get(j).getId(),users.get(j).getName()));
        }
        for (int k= 0; k < users.size(); k++) {
            // 比较对象引用
            System.out.println(k + ":" + (users.get(k) == newUsers.get(k)));
        }
    }
}

示例运行结果

0:false
1:false
2:false
3:false
4:false
5:false
6:false
7:false
8:false
9:false

结果分析

从运行结果可以得知,使用示例中的方法就可以复制出一个不会干扰原ArrayList的对象。

Java,可以使用addAll()方法将两个ArrayList合并成一个新的ArrayList。这个方法会将第二个ArrayList的所有元素添加到第一个ArrayList,而且不会有重复的元素。然后,可以使用重写equals()方法来去重。在重写的equals()方法,可以定义两个对象相等的条件,比如根据对象的某个属性来判断是否相等。接下来,可以使用一个新的ArrayList来存储合并去重后的结果。通过迭代器遍历合并后的ArrayList,如果结果集合不包含当前元素,则将其添加到结果集合。最后,结果集合就是合并去重后的最终集合。以下是一个示例代码: ```java List<Integer> array1 = new ArrayList<>(); List<Integer> array2 = new ArrayList<>(); // 假设array1和array2已经按升序排序 List<Integer> mergedList = new ArrayList<>(); mergedList.addAll(array1); mergedList.addAll(array2); List<Integer> resultList = new ArrayList<>(); for (Integer num : mergedList) { if (!resultList.contains(num)) { resultList.add(num); } } // resultList就是合并去重后的最终集合 ``` 请注意,这只是一个示例代码,具体实现可能会根据你的需求有所不同。希望对你有帮助!\[1\]\[2\] #### 引用[.reference_title] - *1* [在Java将两个arrayList合并到一个新的arrayList,没有重复且没有顺序](https://blog.csdn.net/weixin_35776153/article/details/114511361)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-c72408308d2648ad809ce05146efff37.142^v90^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [合并两个ArrayList对象集合 并对该合并后的集合去重](https://blog.csdn.net/weixin_45798993/article/details/127911798)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-c72408308d2648ad809ce05146efff37.142^v90^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值