如何比较数组列表与现代Java的相等性?

How to compare equality of lists of arrays with modern Java?

我有两个数组列表。

如何在不使用外部库的情况下轻松地将这些与Java 8及其特性进行比较?我正在寻找一个比这种蛮力代码更好(更高级、更短、更高效)的解决方案(未经测试的代码,可能包含拼写错误等,而不是问题的关键):

1
2
3
4
5
6
7
8
9
10
11
12
13
boolean compare(List<String[]> list1, List<String[]> list2)
{
    // tests for nulls etc omitted
    if(list1.size() != list2.size()) {
       return false;
    }
    for(i=0; i<list1.size(); ++i) {
        if(!Arrays.equals(list1.get(i), list2.get(i))) {
            return false;
        }
    }
    return true;
}

或者,如果没有更好的方法,那也是一个有效的答案。

奖励:如果Java 9提供了一个更好的方式,Java 8可以提供什么,请随意提及。

编辑:在看了这些评论,并看到这个问题是如何变得相当热门之后,我认为"更好的"应该包括在检查数组内容之前,首先检查所有数组的长度,因为如果内部数组很长的话,这有可能更快地发现不等式。


for循环至少可以简化,导致:

1
2
3
return (list1.size()==list2.size() &&
        IntStream.range(0, list1.size())
                 .allMatch(i -> Arrays.equals(list1.get(i), list2.get(i)));


使用来自https://stackoverflow.com/a/23529010/755183的zip函数(源自lambda b93),代码可能看起来像:

1
2
3
boolean match = a.size() == b.size() &&
                zip(a.stream(), b.stream(), Arrays::deepEquals).
                allMatch(equal -> equal)

。更新

为了首先检查数组的大小,然后检查内容,这可能是一个需要考虑的解决方案

1
2
3
4
5
final boolean match = a.size() == b.size()
                   && zip(a.stream(), b.stream(), (as, bs) -> as.length == bs.length).
                      allMatch(equal -> equal)
                   && zip(a.stream(), b.stream(), Arrays::deepEquals).
                      allMatch(equal -> equal);


1)基于Java 8流的解决方案:

1
2
3
List<List<String>> first = list1.stream().map(Arrays::asList).collect(toList());
List<List<String>> second = list2.stream().map(Arrays::asList).collect(toList());
return first.equals(second);

2)更简单的解决方案(在Java 5 +中工作):

1
return Arrays.deepEquals(list1.toArray(), list2.toArray());

3)关于您的新要求(首先检查包含的字符串数组长度),您可以编写一个对转换的列表执行相等检查的通用助手方法:

1
2
3
4
5
<T, U> boolean equal(List<T> list1, List<T> list2, Function<T, U> mapper) {
    List<U> first = list1.stream().map(mapper).collect(toList());
    List<U> second = list2.stream().map(mapper).collect(toList());
    return first.equals(second);
}

那么解决方案可能是:

1
2
return equal(list1, list2, s -> s.length)
    && equal(list1, list2, Arrays::asList);


如果列表是随机访问列表(因此对get的调用是快速的,通常是恒定的时间),则可以使用流,从而导致:

1
2
//checks for null and size before
boolean same = IntStream.range(0, list1.size()).allMatch(i -> Arrays.equals(list1.get(i), list2.get(i)));

但是,您可以将一些不属于的实现(如LinkedList)作为参数提供。在这种情况下,最好的方法是显式使用迭代器。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
boolean compare(List<String[]> list1, List<String[]> list2) {

    //checks for null and size

    Iterator<String[]> iteList1 = list1.iterator();
    Iterator<String[]> iteList2 = list2.iterator();

    while(iteList1.hasNext()) {
        if(!Arrays.equals(iteList1.next(), iteList2.next())) {
            return false;
        }
    }
    return true;
}


您可以使用迭代器在一个列表上进行流式处理,并与另一个列表的每个元素进行比较:

1
2
3
Iterator<String[]> it = list1.iterator();
boolean match = list1.size() == list2.size() &&
                list2.stream().allMatch(a -> Arrays.equals(a, it.next()));

在第一个列表中使用迭代器而不是get(index)方法更好,因为列表是否为RandomAccess并不重要。

注意:这只适用于顺序流。使用并行流将导致错误的结果。

编辑:根据上次编辑的问题,最好提前检查每对数组的长度,我想只要稍微修改一下我以前的代码就可以做到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Iterator<String[]> itLength = list1.iterator();
Iterator<String[]> itContents = list1.iterator();

boolean match =
        list1.size() == list2.size()
    &&
        list2.stream()
            .allMatch(a -> {
                String[] s = itLength.next();
                return s == null ? a == null :
                       a == null ? s == null :
                       a.length == s.length;
            })
    &&
        list2.stream()
            .allMatch(a -> Arrays.equals(a, itContents.next()));

这里我使用了两个迭代器,并对list2进行了两次流式处理,但是在检查第一对数组的内容之前,我看不到其他方法来检查所有长度。检查长度是空安全的,而检查内容则委托给Arrays.equals(array1, array2)方法。