行为"包含所有"有重复

时间:2018-01-12 16:18:33

标签: java list collections duplicates

containsAll的{​​{3}}(official documentation)仅表示"如果此列表包含指定集合的​​所有元素,则返回true。"。但是,我刚测试了这个:

List<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
list1.add(1);
List<Integer> list2 = new ArrayList<>();
list2.add(2);
list2.add(1);
list2.add(2);
System.out.println(list1.containsAll(list2));

结果为true,即使list1不包含第二个2

那么containsAll的官方完全定义的行为是什么?它是否表现为从两个列表中删除了所有重复项?我记得在某个地方读过它可能会导致重复问题,但我不知道确切的情况。

2 个答案:

答案 0 :(得分:3)

List.containsAll方法的行为与文档一样:如果给定集合的所有元素都属于此集合,则返回true,否则返回false。文档没有说明元素的顺序或基数。

containsAll方法在Collection接口中声明,并在ListSet接口中重新声明,但它首先在Collection接口中实现{3}}类的{3}}层次结构,如下所示:

public boolean containsAll(Collection<?> c) {
    for (Object e : c)
        if (!contains(e))
            return false;
    return true;
}

据我所知,这个实现是由Java Collections框架中实现AbstractCollection接口的大多数常见类继承的,除了CopyOnWriteArrayList类和其他专用类,例如空列表和检查和不可变的包装等。

因此,如果您查看代码,您会发现它符合您引用的文档:

  

如果此列表包含指定集合的​​所有元素,则返回true

Collection方法的文档中,还有@implSpec标记,其中包含以下内容:

  

@implSpec

     

此实现迭代指定的集合,依次检查迭代器返回的每个元素以查看它是否包含在此集合中。如果包含所有元素,则返回true,否则返回false

关于可能的优化,它们全部转发到contains方法的不同实现,这也由AbstractList.containsAll以类似于暴力的方式实现。但是,containsAbstractCollection中被覆盖以利用散列,也在HashSet中,它使用索引等。

答案 1 :(得分:0)

您可以遍历一个列表并按值从另一个列表中删除元素,然后检查另一个列表大小是否为 == 0。如果是,则意味着所有第二个列表元素出现在第一个列表中的次数至少与第二个列表。

 public boolean containsAll(List<Character> source, List<Character> target) {
    
    for (Character character : source) {
        target.remove(character);
        if (target.isEmpty()) {
            return true;
        }
    }

    return target.size() == 0;
}

如果列表很大,HashMap 会更有效率

 public static boolean containsAll(List<Character> source, List<Character> target) {
    Map<Character, Long> targetMap = target.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
    for (Character character : source) {
        Long count = targetMap.get(character);
        if (count != null) {
            if (count > 1) {
                targetMap.put(character, --count);
            } else {
                targetMap.remove(character);
            }
        }
    }
    return targetMap.isEmpty();
}