Call a Method on Each Element of a List in Java – 用 Java 在列表的每个元素上调用方法

最后修改: 2024年 1月 14日

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

1. Overview

1.概述

When we work with Java, whether we’re working with pre-Java 8 code or embracing the functional elegance of the Stream API in Java 8 and beyond, calling a method on each element of a list is a fundamental operation.

当我们使用 Java 时,无论是使用 Java 8 之前的代码,还是使用 Java 8 及以后的 Stream API 的优雅功能,在列表的每个元素上调用方法都是一项基本操作。

In this tutorial, we’ll explore the methods and techniques available for calling a method on each list element.

在本教程中,我们将探讨在每个列表元素上调用方法的方法和技巧。

2. Introduction to the Problem

2.问题介绍

As usual, let’s understand the problem quickly through an example. Let’s say we have the Player class:

像往常一样,让我们通过一个例子来快速理解这个问题。假设我们有一个 Player 类:

class Player {
    private int id;
    private String name;
    private int score;

    public Player(int id, String name, int score) {
        this.id = id;
        this.name = name;
        this.score = score;
    }

   // getter and setter methods are omitted
}

Then, let’s initialize a list of Players as our input:

然后,让我们初始化一个 Players 列表作为输入:

List<Player> PLAYERS = List.of(
  new Player(1, "Kai", 42),
  new Player(2, "Eric", 43),
  new Player(3, "Saajan", 64),
  new Player(4, "Kevin", 30),
  new Player(5, "John", 5));

Let’s say we want to execute a method on each player in the PLAYERS list. However, this requirement can have two scenarios:

假设我们想对 PLAYERS 列表中的每个球员执行一个方法。然而,这种需求可能有两种情况:

  • Perform an action on each element and don’t care about the returned value, such as printing each player’s name
  • The method returns a result, effectively transforming the input list to another list, for example, extracting player names into a new List<String> from PLAYERS

In this tutorial, we’ll discuss both scenarios. Furthermore, we’ll see how to achieve them in pre-Java 8 and Java 8+.

在本教程中,我们将讨论这两种情况。此外,我们还将了解如何在 Java 8 之前和 Java 8+ 中实现它们。

Next, let’s see them in action.

接下来,让我们看看它们的实际效果。

3. Traditional Approach (Prior to Java 8)

3.传统方法(Java 8 之前)

Before Java 8, loop was the base technique when we wanted to call a method on each element in a list. However, some external libraries may provide convenient methods that allow us to solve the problem more efficiently.

在 Java 8 之前,当我们希望对列表中的每个元素调用一个方法时,循环是基本技术。不过,一些外部库可能会提供一些方便的方法,让我们可以更高效地解决问题。

Next, let’s take a close look at them.

接下来,让我们仔细看看它们。

3.1. Performing an Action on Each Element

3.1.对每个元素执行操作

Looping through the elements and calling the method can be the most straightforward solution to perform an action on each element:

循环查看元素并调用方法是对每个元素执行操作的最直接解决方案

for (Player p : PLAYERS) {
    log.info(p.getName());
}

If we check the console after running the for loop above, we’ll see the log output. Each player’s name was printed:

如果我们在运行上述 for 循环后查看控制台,就会看到日志输出。每个玩家的名字都已打印出来:

21:14:47.219 [main] INFO ... - Kai
21:14:47.220 [main] INFO ... - Eric
21:14:47.220 [main] INFO ... - Saajan
21:14:47.220 [main] INFO ... - Kevin
21:14:47.220 [main] INFO ... - John

3.2. Transforming to Another List

3.2.转换到另一个列表

Similarly, if we want to extract players’ names by calling player.getName(), we can first declare an empty string list and add each player’s name in a loop:

同样,如果我们想通过调用 player.getName() 来提取球员的名字,我们可以首先声明一个空字符串列表,然后在一个循环中添加每个球员的名字

List<String> names = new ArrayList<>();
for (Player p : PLAYERS) {
    names.add(p.getName());
}
assertEquals(Arrays.asList("Kai", "Eric", "Saajan", "Kevin", "John"), names);

3.3. Using Guava’s transform() Method

3.3.使用 Guava 的 transform() 方法

Alternatively, we can use Guava‘s Lists.transform() method to apply the list transformation.

或者,我们可以使用 GuavaLists.transform() 方法来应用列表转换。

The first step to use the Guava library is to add the dependency to our pom.xml:

使用 Guava 库的第一步是在 pom.xml 中添加依赖关系:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.3-jre</version>
</dependency>

The latest Guava version can be checked here.

最新的 Guava 版本可在此处查看。

Then, we can use the Lists.transform() method:

然后,我们可以使用 Lists.transform() 方法:

List<String> names = Lists.transform(PLAYERS, new Function<Player, String>() {
    @Override
    public String apply(Player input) {
        return input.getName();
    }
});

assertEquals(Arrays.asList("Kai", "Eric", "Saajan", "Kevin", "John"), names);

As the code above shows, we passed an anonymous Function<Player, String> object to the transform() method. Of course, we must implement the apply() method in the Function<F, T> interface to perform the transformation logic from F (Player) to T (String).

如上面的代码所示,我们向 transform() 方法传递了一个匿名的 Function<Player, String> 对象。当然,我们必须实现 Function<F, T> 接口中的 apply() 方法,以执行从 F (Player) 到 T (String) 的转换逻辑

4. Java 8 and Beyond: the Stream API

4.Java 8 及以后:流 API

The Stream API was introduced in Java 8, providing a convenient way to work with collections. Next, let’s see how our problem can be solved in Java 8+.

Java 8 引入了流 API,为处理集合提供了一种便捷的方法。接下来,让我们看看如何在 Java 8+ 中解决我们的问题。

4.1. Performing an Action on Each Element

4.1.对每个元素执行操作

In Java 8, the forEach() method is a new method in the Iterable interface:

在 Java 8 中,forEach() 方法是 Iterable 接口中的一个新方法:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

As we can see, forEach() wraps the loop implementation and makes the code on the caller’s side easier to read.

我们可以看到,forEach() 封装了循环的实现,使调用方的代码更易于阅读

As Iterable is the supertype of the Collection interface, forEach() is available in all Collection types, such as List and Set.

由于 IterableCollection 接口的超类型,因此 forEach() 可用于所有 Collection 类型,例如 ListSet

The forEach() method expects a Consumer object as the argument. It’s ideal to perform an action on each list’s element. For example, let’s run this line of code:

forEach()方法希望将 Consumer 对象作为参数。在每个列表元素上执行一个操作是最理想的。例如,让我们运行这行代码:

PLAYERS.forEach(player -> log.info(player.getName()));

We see the expected output get printed:

我们看到打印出了预期的输出结果:

21:14:47.223 [main] INFO ... - Kai
21:14:47.223 [main] INFO ... - Eric
21:14:47.224 [main] INFO ... - Saajan
21:14:47.224 [main] INFO ... - Kevin
21:14:47.224 [main] INFO ... - John

In the code above, we passed a lambda expression as the Consumer object to the forEach() method.

在上面的代码中,我们将 lambda 表达式作为 Consumer 对象传递给 forEach() 方法。

4.2. Transforming to Another List

4.2.转换到另一个列表

To transform elements within a list by applying a specific function and gathering these modified elements into a new list, the Stream.map() method can be employed. Of course, we must first call stream() to convert our list to a Stream, and collect() the transformed elements:

要通过应用特定函数来转换列表中的元素,并将这些修改后的元素收集到一个新的列表中,可以使用 Stream.map() 方法。当然,我们必须首先调用 stream() 将列表转换为 Stream,然后调用 collect() 转换后的元素:

List<String> names = PLAYERS.stream()
  .map(Player::getName)
  .collect(Collectors.toList());
assertEquals(List.of("Kai", "Eric", "Saajan", "Kevin", "John"), names);

As we can see, compared to Guava’s Lists.transform(), the Stream.map() approach is more fluent and easier to understand.

我们可以看到,与 Guava 的 Lists.transform() 相比,Stream.map() 更流畅,也更容易理解。

It’s worth noting that the “Player::getName” we passed to the map() method is a method reference. It works just as well if we replace the method reference with this lambda expression: “player -> player.getName().

值得注意的是,我们传递给 map() 方法的”Player::getName“是一个 方法引用。如果我们用以下 lambda 表达式替换方法引用,效果也一样好:”player -> player.getName().

5. Conclusion

5.结论

In this article, we explored two scenarios for invoking a method on each element of a list. We delved into various solutions addressing this challenge, considering both pre-Java 8 and Java 8 and later versions.

在本文中,我们探讨了在列表的每个元素上调用方法的两种情况。我们深入探讨了应对这一挑战的各种解决方案,同时考虑了 Java 8 以前的版本和 Java 8 及以后的版本。

As always, the complete source code for the examples is available over on GitHub.

与往常一样,这些示例的完整源代码可在 GitHub 上获取。