【问题标题】:Is there a new way to compare element of the same list in java 8 with lambda?有没有一种新方法可以将 java 8 中相同列表的元素与 lambda 进行比较?
【发布时间】:2026-02-10 22:10:01
【问题描述】:

有没有办法在 java 8 中比较列表中的元素?

我有兴趣重新编写两个 for 循环。

private static void test(List<Foo> a) {
    for(int i = 0 ; i < a.size(); i++){
        for(int j = 1; j < a.size() ; j++){
            Foo o1= a.get(i);
            Foo o2= a.get(j);
            if (o1.getFooA().equals(o2.getFooA()) && !o1.getFooB().equals(o2.getFooB()) ) {
                    throw new Exception("some message goes here...");
                }
            }

        }
    }

有没有一种很酷的方法可以使用 lambdas/streams 或 java 8 遇到的任何其他功能?

【问题讨论】:

  • 没有什么比这种传统方法使代码更具可读性了。
  • 请注意,由于您的比较是对称的,因此您的比较是不必要的低效,因此比较 a with b 将产生与 b with a 相同的结果,但您的嵌套循环会生成两个比较。因此,它的复杂性可以大大降低。为此,只需让第二个循环从int j = i 开始,而不是int j = 1。因此,如果您有 (1, 1)(1, 2)(2, 1)(2, 2) 之类的配对,则 (2, 1) 配对已过时,因为它已被 (1, 2) 覆盖。另请注意,您可以跳过j == i 的情况,因为比较总是false,所以只需int j = i + 1
  • 嗨@Zabuza,谢谢你的来信,我一定会使用你的建议!您认为您的解决方案是否可以使用 lambda 或任何 java 8 功能来解决?
  • 您当然可以使用流 API 执行此操作,但结果很可能不会比这更具可读性,甚至可能更糟。这不适合流 API,因为您必须同时遍历两个集合。
  • @Zabuza - 这不会改变复杂性。您的优化与原始的比较大约只有一半,但N*NN*N/2 都属于O(N^2) 复杂性类。

标签: java lambda collections java-8 java-stream


【解决方案1】:

不要要求“lambdas/streams”,而是要实际改进。

当你使用

private static void test(List<Foo> a) throws Exception {
    Map<TypeOfFooA, TypeOfFooB> seen = new HashMap<>();
    for(Foo f: a) {
        TypeOfFooB fooB = f.getFooB(), previous = seen.putIfAbsent(f.getFooA(), fooB);
        if(previous != null && !fooB.equals(previous))
            throw new Exception("some message goes here...");
    }
}

您正在一次执行操作,而不是嵌套循环。这使得线性时间复杂度和二次时间复杂度之间存在差异。这比“看起来很酷”更有影响力。

您可以重写它以使用 Stream API,例如

private static void test(List<Foo> a) throws Exception {
    Map<TypeOfFooA, TypeOfFooB> seen = a.stream()
        .collect(Collectors.toMap(Foo::getFooA, Foo::getFooB,
            (previous, fooB) -> {
                if(!fooB.equals(previous))
                    throw new RuntimeException("some message goes here...");
                return previous;
            }));
}

但不推荐。收集到之后并不真正需要的映射,结合使用合并/归约函数进行验证并抛出异常,可能会让读者感到惊讶,它只适用于未经检查的异常。此外,抛出函数既不能访问Foo 实例也不能访问FooA 键,用于构造异常消息。

我建议保持循环。

【讨论】:

    【解决方案2】:

    这是一个优化的,但不是那么明显的解决方案:

    a.stream()
     .collect(toMap(Foo::getFooA, Foo::getFooB, (x, y) -> {
         if (x.equals(y)) {
             return x;
         } else {
             throw new RuntimeException("some message goes here...");
         }
     }));
    

    它利用了HashMapO(1) 访问时间,从而放弃了具有复杂性O(n) 的嵌套循环。


    通常在编程时您应该注意两个方面:可读性和性能。

    您的普通 Java 代码可读性很强,但没有经过优化。在最坏的情况下,它具有复杂性 O(n*n),而使用 Map 您可以将其简化为线性 O(n)

    这种差异对于处理大型列表非常有意义。

    【讨论】:

    • 我使用线性循环解决方案作为起点,也将流变体作为非真正推荐的解决方案,类似于您的变体。所以 +1 用于 O(n) 解决方案,尽管我仍然推荐 (也是 O(n)) 循环变体。
    • 确实,虽然不错,但也不是微不足道的。
    【解决方案3】:

    如果您绝对必须使用 lambdas 作为“酷”因素。这是一种选择:

    private static void test(List<Foo> a) {
        a.forEach(o1 -> {
            if (a.stream().anyMatch(o2 -> o1.getFooA().equals(o2.getFooA()) && !o1.getFooB().equals(o2.getFooB()))) {
                throw new Exception("some message goes here...");
            }
        });
    }
    

    它不是两个嵌套的 for 循环,而是一个带有嵌套“anyMatch”的“forEach”lambda,它做同样的事情。我选择这种方法是因为这样可以更清楚地看到这里发生的事情。

    【讨论】:

    • if (a.anyMatch(o1 -&gt; a.stream().anyMatch(o2 -&gt; o1.getFooA().equals(o2.getFooA()) &amp;&amp; !o1.getFooB().equals(o2.getFooB()))) ) { throw new Exception("some message goes here...") }; 会更好地表明目的
    • 请注意,您不能在 Consumer 中抛出 Exception...
    最近更新 更多