七日书摘

Java 8 学习指南 之 forEach

1 概述

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

在本文中,我们将看到如何将forEach与集合一起使用,它采用何种参数以及此循环与增强的for循环的不同之处。

阅读文章的过程中如果有任何疑问,欢迎添加笔者为好友,拉您进【七日书摘】微信交流群,一起交流技术,一起打造高质量的职场技术交流圈子,抱团取暖,共同进步。

2 基础知识

public interface Collection<E> extends Iterable<E>

Collection 接口实现了 Iterable 接口,而 Iterable 接口在 Java 8开始具有一个新的 API:

void forEach(Consumer<? super T> action)//对 Iterable的每个元素执行给定的操作,直到所有元素都被处理或动作引发异常。

使用forEach,我们可以迭代一个集合并对每个元素执行给定的操作,就像任何其他迭代器一样。

例如,迭代和打印字符串集合for循环版本:

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

我们可以使用forEach写这个 :

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

3.使用forEach方法

3.1 匿名类

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

所述消费者接口是一个功能接口(具有单个抽象方法的接口)。它接受输入并且不返回任何结果。

Consumer 接口定义如下:

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

任何实现,例如,只是打印字符串的消费者:

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

可以作为参数传递给forEach

names.forEach(printConsumer);

但这不是通过消费者和使用forEach API 创建操作的唯一方法。让我们看看我们将使用forEach方法的另外2种最流行的方式:

3.2 Lambda表达式

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

由于 Consumer 接口属于函数式接口,我们可以通过以下形式在Lambda中表达它:

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

3.3 方法参考

我们可以使用方法引用语法而不是普通的Lambda语法,其中已存在一个方法来对类执行操作:

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

4.forEach在集合中的使用

4.1.迭代集合

任何类型Collection的可迭代 - 列表,集合,队列 等都具有使用forEach的相同语法。

因此,正如我们已经看到的,迭代列表的元素:

List<String> names = Arrays.asList("Larry", "Steve", "James");
 
names.forEach(System.out::println);

同样对于一组:

Set<String> uniqueNames = new HashSet<>(Arrays.asList("Larry", "Steve", "James"));
 
uniqueNames.forEach(System.out::println);

或者让我们说一个队列也是一个集合

Queue<String> namesQueue = new ArrayDeque<>(Arrays.asList("Larry", "Steve", "James"));
 
namesQueue.forEach(System.out::println);

4.2.迭代Map - 使用Map的forEach

Map没有实现Iterable接口,但它提供了自己的forEach 变体,它接受BiConsumer。*

Map<Integer, String> namesMap = new HashMap<>();
namesMap.put(1, "Larry");
namesMap.put(2, "Steve");
namesMap.put(3, "James");
namesMap.forEach((key, value) -> System.out.println(key + " " + value));

4.3.迭代一个Map - 通过迭代entrySet

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

5. Foreach vs For-Loop

从一个简单的角度来看,两个循环都提供相同的功能 — 遍历集合中的元素。

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

5.1 内部迭代器– forEach

这种类型的迭代器在后台管理迭代,程序员仅对要使用集合元素执行操作进行编码。
相反,迭代器管理迭代并确保逐个处理元素。
下面是一个内部迭代器的示例:

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

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

5.2. 外部迭代器 – for-loop

外部迭代器混合了循环的实现和方式。
枚举、迭代器和增强的 for 循环都是外部迭代器(还记得iterator()、next() 或 hasNext() 这些方法吗?),在所有这些迭代器中,我们的工作是指定如何执行迭代。
想想这个熟悉的循环:

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

虽然我们在遍历列表时没有显式调用 hasNext() 或next() 方法,但是使用此迭代工作的底层代码使用这些方法。这意味着对程序员来说并不用知道这些操作的复杂性,但它仍然存在。

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

英文原文:https://www.baeldung.com/foreach-java

------完------

推荐阅读:

Java NIO 简明教程

更多学习讨论欢迎进入七日书摘官方群:

参考:
本文参考 JavaGuide 整理的 Java8foreach指南;

当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »