java List.subList方法中的陷阱

本文详细解析了Java中ArrayList的subList方法的工作原理及注意事项。指出直接对subList实例进行修改会同步影响原始列表,并提供了解决方案,即通过创建新的ArrayList来避免这种影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载文章,文章出处:
http://blog.163.com/wm_at163/blog/static/1321734902012526101318539/

ArrayList 中 subList 的基本用法:
subList(fromIndex:int,toIndex:int):List 返回从fromIndex到toindex-1 的 子列表

在使用集合中,可能常常需要取集合中的某一部分子集来进行一下操作,于是subList这个方法就映入我们的眼帘,毫不犹豫地使用。
例如以下代码:

public static void main(final String[] args) {   
    List<Object> lists = new ArrayList<Object>();   

    lists.add("1");   
    lists.add("2");   
    lists.add("3");   
    lists.add("4");   

    List<Object> tempList = lists.subList(2, lists.size());   

    tempList.add("6");   

    System.out.println(tempList); // 1   

    System.out.println(lists); // 2   
}  

代码初步写好后,可能我们想达到的效果是:往集合lists的子集合tempList中添加一个元素6,而原有的集合保持不变。
即到达这样的效果:lists = [1, 2, 3, 4],tempList = [3, 4, 6]。但是我们看到实际的结果确是lists里边也添加了元素6。
这是怎么一会事呢,通过查找java原代码我们可以看到:tempList的subList实现代码在AbstractList类里边,然而无论如何,最终的结果都是返回一个AbstractList的子类:SubList(该类是一个使用默认修饰符修饰的类,其源代码位于AbstractList.java类文件里边),
SubList类的构造方法:

SubList(AbstractList<E> list, int fromIndex, int toIndex) {   
    if (fromIndex < 0)   
        throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);   
    if (toIndex > list.size())   
        throw new IndexOutOfBoundsException("toIndex = " + toIndex);   
    if (fromIndex > toIndex)   
        throw new IllegalArgumentException("fromIndex(" + fromIndex +   
                                           ") > toIndex(" + toIndex + ")");   
    l = list;   
    offset = fromIndex;   
    size = toIndex - fromIndex;   
    expectedModCount = l.modCount;   
}  

里边,将我们原有的list对象给缓存到SubList类对象的一个属性中去了。
而SubList类的add/remove等修改元素的方法中,都使用l进行了操作:

public void add(int index, E element) {   
    if (index<0 || index>size)   
        throw new IndexOutOfBoundsException();   
    checkForComodification();   
    l.add(index+offset, element);   
    expectedModCount = l.modCount;   
    size++;   
    modCount++;   
} 

因此,当我们使用子集合tempList进行元素的修改操作时,会影响原有的list集合。所以在使用subList方法时,一定要想清楚,是否需要对子集合进行修改元素而不影响原有的list集合。

如果需要对子集合的元素进行修改操作而不需要影响原集合时,我们可以使用以下方法进行处理:

public static void main(final String[] args) public static void main(final String[] args) {   
    List<Object> lists = new ArrayList<Object>();   

    lists.add("1");   
    lists.add("2");   
    lists.add("3");   
    lists.add("4");   

    //注意这里是和本文顶部的代码不同的....   
    List<Object> tempList = new ArrayList<Object>(lists.subList(2, lists.size()));   

    tempList.add("6");   

    System.out.println(tempList); // 1   

    System.out.println(lists); // 2   
} 
<think>我们被要求判断两个List是否相等。根据引用,我们需要考虑两种情况:元素是否完全相同(包括顺序)和元素是否相同(不考虑顺序)。 引用[1]提到:有时候我们需要判断两个List中的元素是否完全一样,有时候我们只需要判断两个List中的元素是否相同,而不关心元素的顺序。 引用[4]指出:使用==比较的是内存地址,而equals方法List中被重写了,用于比较列表中的元素。 在Java中,List接口的equals方法实现是:比较两个列表的元素个数是否相等,并且对应位置的元素是否相等(通过元素的equals方法比较)。所以,如果两个List的元素顺序相同且内容相同,那么equals方法会返回true。 但是,如果我们不关心顺序,那么就不能直接使用equals方法。引用[2]和[3]提到,直接使用List的contains方法来判断两个List是否包含相同元素(不考虑顺序)可能会有问题,因为contains方法依赖于元素的equals方法,而且如果列表中有重复元素,这种方法可能会出错。 因此,我们可以总结如下: 1. 如果要求两个List的元素顺序和内容都相同,则可以直接使用List的equals方法。 2. 如果只要求两个List包含的元素相同(不考虑顺序和重复次数),那么我们可以先对两个List进行排序,然后使用equals方法比较(需要元素实现Comparable接口,或者使用自定义Comparator)。 3. 如果要求两个List包含的元素相同(包括重复元素的个数也相同,但不考虑顺序),那么我们可以使用“袋”的概念(即多重集合),可以通过HashMap来统计每个元素出现的次数,然后比较两个Map是否相同。 根据引用[3]的示例代码,我们看到: List<String> c = new ArrayList<>(); c.add("A"); c.add("B"); List<String> c1 = new ArrayList<>(c); // 复制c List<String> c2 = c.subList(0, c.size()); c2.add("c"); // 此时c2为[A, B, c] System.out.println("c == c1?"+c.equals(c1)); // 输出true,因为c和c1都是[A, B] System.out.println("c == c2?"+c.equals(c2)); // 输出false,因为c是[A, B],c2是[A, B, c] 所以,对于顺序和内容都相同的情况,使用equals即可。 对于不考虑顺序的情况,我们可以这样处理: 方法一:排序后比较 如果两个List的长度相同,我们可以对两个List进行排序,然后比较排序后的列表。 示例代码: Collections.sort(list1); Collections.sort(list2); return list1.equals(list2); 注意:这种方法要求列表中的元素是可比较的(实现Comparable接口),或者我们提供Comparator。 方法二:使用HashMap计数 我们可以分别遍历两个List,用一个HashMap来记录每个元素出现的次数,然后比较两个HashMap是否相同。 示例代码: Map<Object, Integer> countMap1 = new HashMap<>(); for (Object obj : list1) { countMap1.put(obj, countMap1.getOrDefault(obj, 0) + 1); } Map<Object, Integer> countMap2 = new HashMap<>(); for (Object obj : list2) { countMap2.put(obj, countMap2.getOrDefault(obj, 0) + 1); } return countMap1.equals(countMap2); 方法三:使用第三方库(如Apache Commons Collections) 如果可以使用第三方库,那么可以使用CollectionUtils.isEqualCollection方法,它比较两个集合(Collection)是否相等(即元素相同,出现次数相同,不考虑顺序)。 根据需求选择合适的方法。 因此,回答用户的问题: 在Java中,判断两个List是否相等(即元素顺序和内容都相同)可以直接使用List的equals方法。例如: list1.equals(list2) 如果要判断两个List包含的元素相同(包括重复元素的个数相同,但顺序可以不同),我们可以使用以下方法之一: 1. 排序后比较(要求元素可排序): if (list1.size() != list2.size()) { return false; } List<Integer> sortedList1 = new ArrayList<>(list1); List<Integer> sortedList2 = new ArrayList<>(list2); Collections.sort(sortedList1); Collections.sort(sortedList2); return sortedList1.equals(sortedList2); 2. 使用HashMap统计元素出现次数: if (list1.size() != list2.size()) { return false; } Map<Object, Integer> freq1 = new HashMap<>(); for (Object o : list1) { freq1.put(o, freq1.getOrDefault(o, 0) + 1); } Map<Object, Integer> freq2 = new HashMap<>(); for (Object o : list2) { freq2.put(o, freq2.getOrDefault(o, 0) + 1); } return freq1.equals(freq2); 3. 使用Apache Commons Collections库(需要添加依赖): import org.apache.commons.collections4.CollectionUtils; ... return CollectionUtils.isEqualCollection(list1, list2); 注意:如果List中元素是自定义对象,那么该对象需要正确重写equals和hashCode方法,因为无论是List的equals方法还是HashMap的统计,都依赖于元素的equals和hashCode方法。 另外,引用[2]中提到的问题,是因为List的contains方法在判断元素存在时也是使用equals方法,但如果我们不考虑顺序,使用contains方法逐个检查并移除元素(两次循环)也可以,但是要注意重复元素的情况。例如,我们可以这样: if (list1.size() != list2.size()) { return false; } List<Object> temp = new ArrayList<>(list2); for (Object o : list1) { if (!temp.remove(o)) { // 移除第一个出现的o,如果找不到返回false return false; } } return temp.isEmpty(); 这种方法也可以,但是会修改临时列表(我们使用临时列表避免修改原列表),而且效率较低(O(n^2)),所以对于大列表不推荐。 综上所述,根据具体需求选择合适的方法。 生成相关问题: 1.Java中,如何比较两个List的元素是否相同(不考虑顺序)? 2. 如果List中的元素是自定义对象,如何正确比较两个List是否相等? 3. 使用Apache Commons Collections库比较两个集合是否相等有什么优势? 4. 在比较两个List相等时,如何处理null元素? 5. 为什么在比较两个List相等时,需要重写元素的equals和hashCode方法?</think>### Java 判断两个 List 是否相等 在 Java 中判断两个 `List` 是否相等需考虑两种场景: 1. **顺序敏感**(元素顺序和内容完全一致) 2. **顺序不敏感**(元素内容相同但顺序可不同) #### 一、顺序敏感比较(推荐) 直接使用 `List.equals()` 方法,该方法会按顺序比较每个元素: ```java List<String> list1 = Arrays.asList("A", "B", "C"); List<String> list2 = Arrays.asList("A", "B", "C"); boolean isEqual = list1.equals(list2); // true ``` #### 二、顺序不敏感比较 ##### 方法1:排序后比较(需元素可排序) ```java import java.util.Collections; import java.util.List; boolean isEqualIgnoreOrder(List<?> list1, List<?> list2) { if (list1.size() != list2.size()) return false; List<?> sorted1 = new ArrayList<>(list1); List<?> sorted2 = new ArrayList<>(list2); Collections.sort(sorted1); Collections.sort(sorted2); return sorted1.equals(sorted2); } ``` ##### 方法2:使用 HashMap 统计频次(支持所有元素类型) ```java import java.util.HashMap; import java.util.List; import java.util.Map; boolean isEqualIgnoreOrder(List<?> list1, List<?> list2) { if (list1.size() != list2.size()) return false; Map<Object, Integer> freqMap = new HashMap<>(); // 统计 list1 元素频次 for (Object item : list1) { freqMap.put(item, freqMap.getOrDefault(item, 0) + 1); } // 校验 list2 元素频次 for (Object item : list2) { int count = freqMap.getOrDefault(item, 0); if (count == 0) return false; freqMap.put(item, count - 1); } return true; } ``` #### 三、注意事项 1. **元素必须正确实现 `equals()` 和 `hashCode()`** 若 `List` 存放自定义对象,需重写这两个方法: ```java class Person { private String name; // 必须重写 equals 和 hashCode @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name); } } ``` 2. **避免使用 `==` 比较** `==` 比较对象内存地址,而非内容: ```java List<String> list1 = new ArrayList<>(); List<String> list2 = new ArrayList<>(); list1.add("A"); list2.add("A"); System.out.println(list1 == list2); // false(地址不同) System.out.println(list1.equals(list2)); // true(内容相同) ``` 3. **子列表陷阱** 使用 `subList()` 生成的列表与原列表共享数据,修改子列表会影响原列表: ```java List<String> original = new ArrayList<>(List.of("A", "B")); List<String> sub = original.subList(0, 2); sub.add("C"); // 添加元素到子列表 System.out.println(original); // [A, B, C](原列表被修改) System.out.println(original.equals(sub)); // false(长度已不同) ``` #### 四、总结 | 场景 | 方法 | 时间复杂度 | 要求 | |--------------------|--------------------------|------------|--------------| | 顺序敏感 | `list1.equals(list2)` | $O(n)$ | 元素需实现 `equals()` | | 顺序不敏感(通用) | HashMap 频次统计 | $O(n)$ | 元素需实现 `equals()` 和 `hashCode()` | | 顺序不敏感(有序) | 排序后比较 | $O(n \log n)$ | 元素需实现 `Comparable` | > **关键点**:优先使用 `List.equals()` 进行顺序敏感比较;若需忽略顺序,HashMap 频次统计是最通用高效的方案[^1][^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值