Java 8中的惯用集合迭代

Idiomatic Collection iteration in Java 8

什么是Java 8中集合的惯用迭代,为什么?

1
2
3
4
5
for (String foo : foos) {
  String bar = bars.get(foo);
  if (bar != null)
    System.out.println(foo);
}

1
2
3
4
5
foos.forEach(foo -> {
  String bar = bars.get(foo);
  if (bar != null)
    System.out.println(foo);
});


在这个答案的注释线程中,用户bringer128在C中提到了关于类似问题的这些问题:好的。

  • foreach与someList.foreach()好的。

  • 通用列表:foreach还是list.foreach?好的。

我建议不要将C语言的讨论应用到Java中。当然,讨论是有趣的,问题表面上是相似的。但是,Java和C语言是不同的语言,因此需要考虑不同的语言。好的。

例如,这个答案提到C foreach语句更可取,因为编译器将来可能能够更好地优化循环。对于Java来说,这是不正确的。在Java中,"增强的"循环被定义为获取EDCOX1×1的语法糖,并重复调用它的EDCOX1 2和EDCX1 3个方法。这在很大程度上保证了每个循环迭代至少两个方法调用(尽管JIT有可能内联小方法)。好的。

另一个例子来自这个答案,它提到在C中,由列表的foreach方法调用的委托修改它正在迭代的列表是合法的。在Java中,对于EDCOX1×5的方法,有一种完全禁止对流源的"干扰",而对于增强的for循环,修改基础列表(或任何)的行为是由EDCOX1(1)所决定的。如果在迭代过程中修改底层列表,很多都会很快失败,并抛出ConcurrentModificationException。其他人会默默地给出意想不到的结果。好的。

在任何情况下,不要阅读C语言的讨论,并假设类似的推理适用于Java。好的。

现在,来回答这个问题。-)好的。

我认为现在将一种风格声明为惯用或优于另一种风格还为时过早。Java 8刚刚发布,很少有人有这样的经验。lambda是新的和不熟悉的,这会让许多程序员感到不舒服。因此,他们会坚持他们的尝试和真正的循环。这很明智。不过,在几年后,当每个人都习惯了羔羊肉之后,for循环可能会变得明显过时。时间会证明一切。好的。

(我认为这发生在仿制药上。当他们还是新手的时候,他们很吓人,特别是野猫。然而,如今,非通用代码看起来明显过时,在我看来,它有一股霉味。)好的。

我很早就知道这会怎样。当然,我可能错了。好的。

我会说,对于计算固定的短循环,例如最初发布的问题:好的。

1
2
for (String foo : foos)
    System.out.println(foo);

没关系。这可以改写为好的。

1
foos.forEach(foo -> System.out.println(foo));

甚至好的。

1
foos.forEach(System.out::println);

但实际上,这段代码非常简单,很难说有一种方法明显更好。好的。

在某些情况下,天平会向一个或另一个方向倾斜。如果循环体可以抛出一个选中的异常,那么for循环显然更好。如果循环体是可插入的(例如,将Consumer作为参数传入),或者如果内部迭代具有不同的语义(例如,在对forEach的整个调用期间锁定同步列表),则新的forEach方法具有优势。好的。

更新的示例,好的。

1
2
3
4
5
for (String foo : foos) {
    String bar = bars.get(foo);
    if (bar != null)
        System.out.println(foo);
}

有点复杂,但只是稍微复杂一点。我不会用多行lambda来写这个:好的。

1
2
3
4
5
foos.forEach(foo -> {
    String bar = bars.get(foo);
    if (bar != null)
        System.out.println(foo);
});

在我看来,这比直接for循环没有任何优势,lambda的不同语义是由第一行角上的小箭头表示的。然而,(类似于Bringer128的答案)我将把它从一个大的forEach区块重新转换成一条河流管道:好的。

1
2
3
foos.stream()
    .filter(foo -> bars.get(foo) != null)
    .forEach(System.out::println)

我认为lambda/streams方法在这里开始显示出一点优势,但只是一点,因为这仍然是一个非常简单的例子。使用lambda/streams将一些条件控制逻辑替换为数据筛选操作。这可能对某些操作有意义,但对其他操作没有意义。好的。

随着事情变得越来越复杂,这两种方法之间的差别开始变得越来越明显。这些简单的例子很简单,很明显它们是做什么的。现实世界中的例子可能要复杂得多。考虑JDK的方法class.getEnclosingMethod中的代码(滚动到第1023-1052行):好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Class<?> enclosingCandidate = enclosingInfo.getEnclosingClass();
// ...
for(Method m: enclosingCandidate.getDeclaredMethods()) {
    if (m.getName().equals(enclosingInfo.getName()) ) {
        Class<?>[] candidateParamClasses = m.getParameterTypes();
        if (candidateParamClasses.length == parameterClasses.length) {
            boolean matches = true;
            for(int i = 0; i < candidateParamClasses.length; i++) {
                if (!candidateParamClasses[i].equals(parameterClasses[i])) {
                    matches = false;
                    break;
                }
            }

            if (matches) { // finally, check return type
                if (m.getReturnType().equals(returnType) )
                    return m;
            }
        }
    }
}

throw new InternalError("Enclosing method not found");

(为了示例,省略了一些安全检查和注释。)好的。

这里我们有一对嵌套的for循环,其中有几个层次的条件逻辑和一个布尔标志。把这段代码通读一段时间,看看你是否能理解它的作用。好的。

使用lambda和流,可以如下重写此代码:好的。

1
2
3
4
5
6
return Arrays.stream(enclosingInfo.getEnclosingClass().getDeclaredMethods())
             .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName()))
             .filter(m -> Arrays.equals(m.getParameterTypes(), parameterClasses))
             .filter(m -> Objects.equals(m.getReturnType(), returnType))
             .findFirst()
             .orElseThrow(() -> new InternalError("Enclosing method not found");

在经典版本中,循环控制和条件逻辑都是关于搜索数据结构中的匹配项。它有点扭曲,因为如果检测到不匹配,它会在内部循环的早期中断,但是如果找到匹配,它会在方法的早期返回。但是,一旦你盯着这段代码看足够长的时间,你就会发现它正在搜索符合一系列条件的第一个元素,并返回它;如果找不到,它就会抛出一个错误。一旦你意识到这一点,lambda/streams方法就会突然出现。它不仅短得多,而且更容易理解它在做什么。好的。

当然,也有一些循环会有奇怪的条件和副作用,不能轻易地转化为流。但是有很多for循环只是搜索数据结构、有条件地处理元素、返回第一个匹配项、累积匹配项集合或累积转换元素。这些操作自然会被改写成流,我敢说,是以惯用的方式。好的。好啊。


在通用idiomatic for the form is moreλ单环whereas the statement,不让更多的lambda -多语言意识的for循环。(这ignores泛函组合进入黑莓风格if possible)。P></

一个更多的风格你不值得一提:reference method is theP></

1
foos.forEach(System.out::println);

编辑:P></

当你寻找答案,你会发现更多的通用;因为,在lambda是在Java list.foreach method is used,the less在实践。P></

"我知道为什么在响应非λis for黑莓idiomatic多语言?"这是黑莓茶,逆向,多语言,语言中最idiomatic lambda are not。to be used for lambda趋势成分,我知道如果我从你的to take the example问题进入了它的功能和风格。P></

1
2
// Thanks to @skiwi for fixing this code
foos.stream().filter(foo -> bars.get(foo) != null).forEach(System.out::println);

在the above example,using声明会多做努力的lambda easier Rather比读。P></


You should be using the New only if it / s的流list' forEach真的让你的代码更简洁,其他棒especially for version with the old,executed linearly队列,得到。P></

你会rewrite the following statement to,with which does make streams感:P></

1
2
3
foos.stream()
        .filter(foo -> (bars.get(foo) != null))
        .forEach(System.out::println);

This is a泛函的方法,我们将:P></

  • 把你的ListStream进入。
  • 对象你这样的the filter of which元素保留在bars.get(foo)is not null,which is of type Predicate
  • 然后你呼叫System.out::printlnon the Stream,which of which is to bar -> System.out.println(bar)resolves,Consumer型。
  • 我在正规的话:黑莓P></

  • obtain a流。
  • 在unwanted filter元素保留了希望,the ones。
  • 从消费的元素流。