Java forEach
上次修改:2025 年 6 月 17 日
在本文中,我们将演示如何有效地利用 Java 的 forEach
方法。我们将探讨它与 Consumer 的应用,并提供使用 forEach
迭代列表、Map 和 Set 集合的实际示例,展示其在处理各种数据结构方面的多功能性。
forEach
方法对 Iterable
对象中的每个元素执行指定的动作,直到所有元素都处理完毕或发生异常为止。Java 8 中引入的这种方法为开发人员提供了一种现代、简洁的替代传统循环结构的方法,简化了迭代集合的过程。
void forEach(Consumer<? super T> action);
上面的代码片段说明了 forEach
方法的语法,它接受一个 Consumer
对象来定义要对每个元素执行的动作。
Consumer 接口
Consumer
接口是 Java 中的一个函数式接口,其特征在于一个抽象方法。它接受一个输入参数,不返回任何结果,使其非常适合于处理数据而不产生输出的操作,例如打印或修改元素。
@FunctionalInterface public interface Consumer { void accept(T t); }
此代码定义了 Consumer
接口,突出了其 accept
方法,该方法处理类型为 T
的输入参数。
void main() { List<String> items = new ArrayList<>(); items.add("coins"); items.add("pens"); items.add("keys"); items.add("sheets"); items.forEach(new Consumer<String>() { @Override public void accept(String name) { System.out.println(name); } }); }
在此示例中,我们将演示如何使用 forEach
方法迭代字符串列表。在这里,我们显式地实现 Consumer
接口来打印每个元素。这种冗长的方法可以使用 Java 的 lambda 表达式进行简化,如下所示。
Lambda 表达式
Java 中的 Lambda 表达式提供了一种简洁的方式来实现函数式接口,例如 Consumer
,方法是在内联定义它们的行为。它们使用 ->
运算符构造,该运算符将参数与表达式的主体分开,从而提高代码的可读性并减少样板代码。
void main() { List<String> items = new ArrayList<>(); items.add("coins"); items.add("pens"); items.add("keys"); items.add("sheets"); items.forEach((String name) -> { System.out.println(name); }); }
此示例重新审视了先前的列表迭代,现在使用 lambda 表达式。通过用 lambda 替换显式的 Consumer
实现,代码变得更简洁易读,同时保持了打印每个字符串的相同功能。
在 Map 上使用 forEach
下一个示例演示了 forEach
方法在 Map(一种存储键值对的集合类型)上的应用。
void main() { Map<String, Integer> items = new HashMap<>(); items.put("coins", 3); items.put("pens", 2); items.put("keys", 1); items.put("sheets", 12); items.forEach((k, v) -> { System.out.printf("%s : %d%n", k, v); }); }
在这里,我们定义了一个包含字符串和整数对的 Map。使用带有 lambda 表达式的 forEach
方法,我们迭代 Map,以格式化的方式打印每个键值对。lambda 表达式接受两个参数:键 (k
) 和值 (v
)。
在下面的示例中,我们显式地定义了 Consumer
并利用 Map.Entry
迭代 Map 的条目,提供了一种替代方法。
void main() { HashMap<String, Integer> map = new HashMap<>(); map.put("cups", 6); map.put("clocks", 2); map.put("pens", 12); Consumer<Map.Entry<String, Integer>> action = entry -> { System.out.printf("key: %s", entry.getKey()); System.out.printf(" value: %s%n", entry.getValue()); }; map.entrySet().forEach(action); }
此示例通过其 entrySet
方法访问 Map 的条目集来迭代 Map。我们定义了一个 Consumer
来处理每个 Map.Entry
对象,分别打印键和值,从而演示了一种更显式的迭代技术。
Java forEach 在 Set 上
以下示例展示了 forEach
方法在 Set 上的使用,Set 是一种确保其元素之间唯一性的集合。
void main() { Set<String> brands = new HashSet<>(); brands.add("Nike"); brands.add("IBM"); brands.add("Google"); brands.add("Apple"); brands.forEach((e) -> System.out.println(e)); }
在这种情况下,我们创建了一组品牌名称。使用带有 lambda 表达式的 forEach
方法,我们迭代 Set 并打印每个元素,利用这种方法的简单性来显示唯一值。
在数组上使用 forEach
此示例说明了如何将 forEach
方法应用于数组,这需要首先将数组转换为流。
void main() { int[] nums = {3, 4, 2, 1, 6, 7}; Arrays.stream(nums).forEach((e) -> System.out.println(e)); }
在这里,我们处理一个整数数组。通过使用 Arrays.stream
方法,我们将数组转换为流,从而可以使用 forEach
迭代并打印每个元素。这种方法弥合了数组和 Java 8 中引入的基于流的操作之间的差距。
过滤列表
forEach
方法可以与过滤操作结合使用,仅处理集合中的特定元素,从而增强其效用。
void main() { List<String> items = new ArrayList<>(); items.add("coins"); items.add("pens"); items.add("keys"); items.add("sheets"); items.stream().filter(item -> item.length() == 4).forEach(System.out::println); }
在此示例中,我们过滤字符串列表,仅包含那些具有四个字符的字符串,然后使用 forEach
打印结果。应用于列表流的 filter
方法与 forEach
无缝协作,演示如何在处理数据之前对其进行优化。
IntConsumer、LongConsumer、DoubleConsumer
自 Java 8 以来,原始数据类型的专用 Consumer 接口 - IntConsumer
、LongConsumer
和 DoubleConsumer
- 已可用,通过避免自动装箱来优化性能。
void main() { int[] inums = { 3, 5, 6, 7, 5 }; IntConsumer icons = i -> System.out.print(i + " "); Arrays.stream(inums).forEach(icons); System.out.println(); long[] lnums = { 13L, 3L, 6L, 1L, 8L }; LongConsumer lcons = l -> System.out.print(l + " "); Arrays.stream(lnums).forEach(lcons); System.out.println(); double[] dnums = { 3.4d, 9d, 6.8d, 10.3d, 2.3d }; DoubleConsumer dcons = d -> System.out.print(d + " "); Arrays.stream(dnums).forEach(dcons); System.out.println(); }
此示例演示了如何使用 IntConsumer
、LongConsumer
和 DoubleConsumer
来迭代整数、长整数和双精度数的数组。每个 Consumer 都使用 lambda 表达式定义,以打印带有空格分隔符的元素,展示了它们对原始流的应用。
使用 forEach 链接操作
forEach
方法可以与其他流操作(例如映射和过滤)结合使用,以在处理元素之前执行复杂的转换。这种方法允许在 Java 中进行优雅的函数式编程,使开发人员能够以可读且高效的方式链接操作。
void main() { List<String> words = new ArrayList<>(); words.add("apple"); words.add("banana"); words.add("cherry"); words.add("date"); words.stream() .map(String::toUpperCase) .filter(word -> word.length() > 5) .forEach(System.out::println); }
在此示例中,我们从水果名称列表开始。使用流,我们链接了三个操作:首先,map
方法使用方法引用 (String::toUpperCase
) 将每个字符串转换为大写;接下来,filter
方法仅选择长度超过五个字符的字符串;最后,forEach
方法将结果元素打印到控制台。这表明 forEach
如何充当流管道中的终端操作,有效地处理转换后的数据并展示 Java 的函数式编程功能。
将 forEach 与记录一起使用
forEach
方法具有高度的适应性,可以迭代自定义类型的集合,例如 Java 记录。Java 16 中引入的记录提供了一种简洁的方式来定义不可变的数据类。此示例说明了 forEach
如何处理记录集合,从而简化了结构化数据的处理。
record Person(String name, int age) { @Override public String toString() { return name + " (" + age + ")"; } } void main() { List<Person> people = new ArrayList<>(); people.add(new Person("Alice", 25)); people.add(new Person("Bob", 30)); people.add(new Person("Clara", 28)); people.forEach(person -> System.out.println(person)); }
在这种情况下,我们定义了一个具有两个组件的 Person
记录:name
和 age
。记录会自动提供构造函数、getter 和标准方法(如 equals
和 hashCode
),但我们覆盖 toString
以自定义输出格式。
然后,我们创建一个 Person
记录列表,并使用带有 lambda 表达式的 forEach
方法迭代该集合,打印每个人的详细信息。此示例重点介绍了 forEach
如何与记录无缝集成,利用其简洁的语法和不变性来有效地处理自定义数据类型。
来源
在本文中,我们已经彻底探讨了 Java forEach
方法。我们介绍了 Consumer 的概念,并演示了它们在列表、Map 和 Set 中与 forEach
的使用,从而全面理解了这个强大的迭代工具。
作者
列出所有Java教程。