在 Java 中對列表的每個元素呼叫方法
1. 概述
當我們使用 Java 時,無論是使用 Java 8 之前的程式碼還是擁抱 Java 8 及更高版本中 Stream API 的功能優雅,對清單的每個元素呼叫方法都是一項基本操作。
在本教程中,我們將探索可用於在每個清單元素上呼叫方法的方法和技術。
2.問題介紹
像往常一樣,讓我們透過一個例子來快速理解這個問題。假設我們有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
}
然後,讓我們初始化一個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));
假設我們要對PLAYERS
清單中的每位玩家執行一個方法。然而,這個要求可能有兩種情況:
- 對每個元素執行一個動作,不關心回傳值,例如印出每個玩家的名字
- 此方法傳回一個結果,有效地將輸入列表轉換為另一個列表,例如,從
PLAYERS
中提取玩家名稱到新的List<String>
在本教程中,我們將討論這兩種情況。此外,我們將了解如何在 Java 8 之前的版本和 Java 8+ 中實作它們。
接下來,讓我們看看他們的實際行動。
3. 傳統方法(Java 8之前)
在 Java 8 之前,當我們想要對清單中的每個元素呼叫方法時,循環是基本技術。然而,一些外部庫可能提供方便的方法,使我們能夠更有效地解決問題。
接下來,讓我們仔細看看它們。
3.1.對每個元素執行操作
循環遍歷元素並呼叫方法可能是對每個元素執行操作的最直接的解決方案:
for (Player p : PLAYERS) {
log.info(p.getName());
}
如果我們在運行上面的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.轉換到另一個列表
類似地,如果我們想要透過呼叫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.使用Guava的transform()
方法
或者,我們可以使用 Guava 的Lists.transform()
方法來套用清單轉換。
使用 Guava 庫的第一步是將依賴項新增到我們的pom.xml
中:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
最新的 Guava 版本可以在這裡查看。
然後,我們可以使用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);
如上面的程式碼所示,我們將一個匿名Function<Player,
String> 物件傳遞給 transform() 方法。當然,我們必須實作Function<F, T>
介面中的apply()
方法來執行從F
( Player
)到T
( String
)的轉換邏輯。
4. Java 8 及更高版本:Stream API
Stream API 是在 Java 8 中引入的,提供了一種使用集合的便捷方法。接下來,讓我們來看看我們的問題在 Java 8+ 中是如何解決的。
4.1.對每個元素執行操作
在 Java 8 中, forEach()
方法是Iterable
介面中的一個新方法:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
正如我們所看到的, forEach()
包裝了循環實作並使呼叫方的程式碼更易於閱讀。
由於Iterable
是Collection
介面的超類型,因此forEach()
在所有Collection
類型**中都可用**,例如List
和Set
。
forEach()
方法需要一個Consumer
物件作為參數。最好對每個清單的元素執行操作。例如,讓我們運行這行程式碼:
PLAYERS.forEach(player -> log.info(player.getName()));
我們看到列印了預期的輸出:
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
在上面的程式碼中,我們將 lambda 表達式作為Consumer
物件傳遞給forEach()
方法。
4.2.轉換到另一個列表
要透過套用特定函數並將這些修改過的元素收集到新列表中來轉換列表中的元素,
可以使用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);
我們可以看到,與 Guava 的Lists.transform(),
Stream.map()
方法更加流暢且更容易理解。
值得注意的是,我們傳遞給map()
方法的「 Player::getName
」是一個方法參考。如果我們用以下 lambda 表達式替換方法來引用:“ player -> player.getName()
”,效果也一樣.
5. 結論
在本文中,我們探討了對清單的每個元素呼叫方法的兩個場景。我們深入研究了應對這項挑戰的各種解決方案,考慮了 Java 8 之前的版本和 Java 8 及更高版本。
與往常一樣,範例的完整原始程式碼可在 GitHub 上取得。