Guide to the Java 8 forEach – Java 8 forEach指南

最后修改: 2016年 11月 9日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Introduced in Java 8, the forEach loop provides programmers with a new, concise and interesting way to iterate over a collection.

在Java 8中引入的forEach循环为程序员提供了一种新的、简洁的、有趣的方式来迭代一个集合。

In this tutorial, we’ll see how to use forEach with collections, what kind of argument it takes, and how this loop differs from the enhanced for-loop.

在本教程中,我们将看到如何在集合中使用forEach,它需要什么样的参数,以及这种循环与增强的for-loop有何不同。

If you need to brush up some Java 8 concepts, our collection of articles can help.

如果您需要温习一些 Java 8 概念,我们的文章集可以帮助您。

2. Basics of forEach

2、forEach的基础知识

In Java, the Collection interface has Iterable as its super interface. And this interface has a new API starting with Java 8:

在Java中,Collection接口有Iterable作为其超级接口。而这个接口从Java 8开始有一个新的API。

void forEach(Consumer<? super T> action)

Simply put, the Javadoc of forEach states that it “performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception.”

简单地说,forEachJavadoc指出,它 “对Iterable的每个元素执行给定的操作,直到所有元素都被处理或该操作抛出一个异常。”

And so, with forEach, we can iterate over a collection and perform a given action on each element, like any other Iterator.

因此,通过forEach,我们可以对一个集合进行迭代,并对每个元素执行指定的操作,就像其他Iterator

For instance, consider a for-loop version of iterating and printing a Collection of Strings:

例如,考虑一个for-loop版本的迭代并打印一个CollectionStrings

for (String name : names) {
    System.out.println(name);
}

We can write this using forEach:

我们可以用forEach来写这个。

names.forEach(name -> {
    System.out.println(name);
});

3. Using the forEach Method

3.使用forEach方法

We use forEach to iterate over a collection and perform a certain action on each element. The action to be performed is contained in a class that implements the Consumer interface and is passed to forEach as an argument.

我们使用forEach来迭代一个集合,并对每个元素执行某种操作。要执行的操作包含在一个实现Consumer接口的类中,并作为一个参数传递给forEach

The Consumer interface is a functional interface (an interface with a single abstract method). It accepts an input and returns no result.

Consumer接口是一个功能性接口(一个具有单一抽象方法的接口)。它接受一个输入,不返回任何结果。

Here’s the definition:

定义是这样的。

@FunctionalInterface
public interface Consumer {
    void accept(T t);
}

Therefore, any implementation, for instance, a consumer that simply prints a String:

因此,任何实现,例如,一个简单打印String的消费者。

Consumer<String> printConsumer = new Consumer<String>() {
    public void accept(String name) {
        System.out.println(name);
    };
};

can be passed to forEach as an argument:

可以作为一个参数传递给forEach

names.forEach(printConsumer);

But that is not the only way to create an action via a consumer and use forEach API.

但这并不是通过消费者创建一个动作并使用forEach API的唯一方法。

Let’s see the three most popular ways we use the forEach method.

让我们看看我们使用forEach方法的三种最流行的方式。

3.1. Anonymous Consumer Implementation

3.1.匿名的消费者实施

We can instantiate an implementation of the Consumer interface using an anonymous class and then apply it as an argument to the forEach method:

我们可以使用一个匿名类实例化一个Consumer接口的实现,然后将其作为参数应用到forEach方法。

Consumer<String> printConsumer= new Consumer<String>() {
    public void accept(String name) {
        System.out.println(name);
    }
};
names.forEach(printConsumer);

This works well. But if we analyze the example, we’ll see that the useful part is actually the code inside the accept() method.

这很好用。但如果我们分析一下这个例子,就会发现,有用的部分实际上是accept()方法里面的代码。

Although Lambda expressions are now the norm and an easier way to do this, it’s still worth knowing how to implement the Consumer interface.

尽管Lambda表达式现在已经成为常态,而且是一种更容易的方式,但仍然值得了解如何实现Consumer接口。

3.2. Lambda Expression

3.2.兰姆达表达式

The major benefit of Java 8 functional interfaces is that we can use Lambda expressions to instantiate them and avoid using bulky anonymous class implementations.

Java 8功能接口的主要好处是,我们可以使用Lambda表达式来实例化它们,避免使用庞大的匿名类实现。

Since Consumer Interface is a functional interface, we can express it in Lambda:

由于 Consumer Interface是一个功能接口,我们可以用Lambda来表达它。

(argument) -> { //body }

Therefore, our printConsumer is simplified:

因此,我们的printConsumer被简化。

name -> System.out.println(name)

And we can pass it to forEach:

我们可以把它传递给forEach

names.forEach(name -> System.out.println(name));

Since the introduction of Lambda expressions in Java 8, this is probably the most common way to use the forEach method.

自从Java 8中引入Lambda表达式后,这可能是使用forEach方法的最常见方式。

Lambdas do have a very real learning curve, so if you’re getting started, this write-up goes over some good practices of working the new language feature.

Lambdas确实有一个非常真实的学习曲线,所以如果你正在开始学习,这篇文章介绍了使用这个新语言功能的一些良好实践。

3.3. Method Reference

3.3.方法参考

We can use method reference syntax instead of the normal Lambda syntax, where a method already exists to perform an operation on the class:

我们可以使用方法引用语法,而不是普通的Lambda语法,在这里已经存在一个方法来对类进行操作。

names.forEach(System.out::println);

4. Working With forEach

4.使用forEach工作

4.1. Iterating Over a Collection

4.1.在一个集合上进行迭代

Any iterable of type Collection list, set, queue etc. — has the same syntax for using forEach.

任何集合类型的迭代器–列表集合队列等–都有相同的语法来使用forEach./strong>。

Therefore, as we have seen, we can iterate elements of a list this way:

因此,正如我们所看到的,我们可以通过这种方式来迭代列表中的元素。

List<String> names = Arrays.asList("Larry", "Steve", "James");

names.forEach(System.out::println);

And a set is similar:

而一套是类似的。

Set<String> uniqueNames = new HashSet<>(Arrays.asList("Larry", "Steve", "James"));

uniqueNames.forEach(System.out::println);

Finally, let’s look at a Queue that is also a Collection:

最后,让我们看看一个也是QueueCollection

Queue<String> namesQueue = new ArrayDeque<>(Arrays.asList("Larry", "Steve", "James"));

namesQueue.forEach(System.out::println);

4.2. Iterating Over a Map Using Map’s forEach

4.2.使用地图的forEach在地图上进行迭代

Maps are not Iterable, but they do provide their own variant of forEach that accepts a BiConsumer.

地图不是Iterable,但它们确实提供了自己的forEach的变体,接受一个BiConsumer

Java 8 introduces a BiConsumer instead of Consumer in Iterable’s forEach so that an action can be performed on both the key and value of a Map simultaneously.

Java 8在Iterable的forEach中引入了BiConsumer,而不是Consumer,因此可以同时对Map的键和值执行操作。

Let’s create a Map with these entries:

让我们用这些条目创建一个Map

Map<Integer, String> namesMap = new HashMap<>();
namesMap.put(1, "Larry");
namesMap.put(2, "Steve");
namesMap.put(3, "James");

Next, let’s iterate over namesMap using Map’s forEach:

接下来,让我们使用Map的forEachnamesMap进行迭代。

namesMap.forEach((key, value) -> System.out.println(key + " " + value));

As we can see here, we’ve used a BiConsumer to iterate over the entries of the Map:

正如我们在这里看到的,我们使用了一个BiConsumer来遍历Map的条目。

(key, value) -> System.out.println(key + " " + value)

4.3. Iterating Over a Map by Iterating entrySet

4.3.通过迭代 entrySetMap上迭代

We can also iterate the EntrySet of a Map using Iterable’s forEach.

我们也可以使用Iterable的forEachMapEntrySet进行迭代。

Since the entries of a Map are stored in a Set called EntrySet, we can iterate that using a forEach:

由于Map的条目存储在一个名为EntrySet集中,我们可以使用forEach来迭代。

namesMap.entrySet().forEach(entry -> System.out.println(
  entry.getKey() + " " + entry.getValue()));

5. Foreach vs For-Loop

5.Foreach与For-Loop

From a simple point of view, both loops provide the same functionality: loop through elements in a collection.

从一个简单的角度来看,这两个循环都提供了相同的功能:在一个集合中循环浏览元素。

The main difference between them is that they are different iterators. The enhanced for-loop is an external iterator, whereas the new forEach method is internal.

它们之间的主要区别在于,它们是不同的迭代器。增强的for-loop是一个外部迭代器,而新的forEach方法是内部的。

5.1. Internal Iterator — forEach

5.1.内部迭代器 – forEach

This type of iterator manages the iteration in the background and leaves the programmer to just code what is meant to be done with the elements of the collection.

这种类型的迭代器在后台管理迭代,让程序员只需编写要对集合中的元素进行处理的代码。

The iterator instead manages the iteration and makes sure to process the elements one-by-one.

而迭代器则管理迭代,确保逐一处理元素。

Let’s see an example of an internal iterator:

让我们看看一个内部迭代器的例子。

names.forEach(name -> System.out.println(name));

In the forEach method above, we can see that the argument provided is a lambda expression. This means that the method only needs to know what is to be done, and all the work of iterating will be taken care of internally.

在上面的forEach方法中,我们可以看到提供的参数是一个lambda表达式。这意味着该方法只需要知道要做什么,而所有的迭代工作都将在内部进行处理。

5.2. External Iterator — for-loop

5.2.外部迭代器 – for-loop

External iterators mix what and how the loop is to be done.

外部迭代器混合了whathow的循环。

Enumerations, Iterators and enhanced for-loop are all external iterators (remember the methods iterator(), next() or hasNext()?). In all these iterators, it’s our job to specify how to perform iterations.

枚举迭代器和增强的for-loop都是外部迭代器(还记得iterator()next()hasNext()这些方法吗)。在所有这些迭代器中,我们的工作是指定如何进行迭代。

Consider this familiar loop:

考虑一下这个熟悉的循环。

for (String name : names) {
    System.out.println(name);
}

Alhough we are not explicitly invoking hasNext() or next() methods while iterating over the list, the underlying code that makes this iteration work uses these methods. This implies that the complexity of these operations is hidden from the programmer, but it still exists.

尽管我们在迭代列表时没有明确地调用hasNext()next()方法,但使这种迭代工作的底层代码使用了这些方法。这意味着这些操作的复杂性对程序员是隐藏的,但它仍然存在。

Contrary to an internal iterator in which the collection does the iteration itself, here we require external code that takes every element out of the collection.

与内部迭代器相反,在这里,集合自己进行迭代,我们需要外部代码,从集合中取出每个元素。

6. Conclusion

6.结论

In this article, we showed that the forEach loop is more convenient than the normal for-loop.

在这篇文章中,我们展示了forEach循环比普通的for-loop更方便。

We also saw how the forEach method works and what kind of implementation can receive as an argument in order to perform an action on each element in the collection.

我们还看到了forEach方法是如何工作的,以及为了对集合中的每个元素执行操作,可以接收什么样的实现作为一个参数。

Finally, all the snippets used in this article are available in our GitHub repository.

最后,本文中使用的所有片段都可以在我们的GitHub资源库中找到。