跳转至

Java foreach 与 Stream:深入解析与最佳实践

简介

在 Java 编程中,遍历集合和数组是常见的操作。foreach 循环和 Stream API 为我们提供了强大且便捷的方式来处理这些操作。foreach 循环是 Java 5 引入的语法糖,简化了传统的 for 循环遍历集合或数组的操作。而 Stream API 则是 Java 8 引入的新特性,它提供了一种函数式编程风格来处理集合数据,支持并行处理,极大地提高了数据处理的效率和代码的可读性。本文将详细介绍它们的基础概念、使用方法、常见实践以及最佳实践。

目录

  1. foreach 基础概念
  2. foreach 使用方法
    • 遍历数组
    • 遍历集合
  3. Stream 基础概念
  4. Stream 使用方法
    • 创建 Stream
    • 中间操作
    • 终端操作
  5. 常见实践
    • 过滤数据
    • 映射数据
    • 聚合数据
  6. 最佳实践
    • 选择合适的遍历方式
    • 避免不必要的装箱和拆箱
    • 并行流的合理使用
  7. 小结
  8. 参考资料

foreach 基础概念

foreach 循环,也称为增强型 for 循环,是 Java 为简化集合和数组遍历而引入的语法糖。它的语法结构更加简洁,无需手动维护索引,适用于顺序遍历集合或数组中的每一个元素。

foreach 使用方法

遍历数组

int[] numbers = {1, 2, 3, 4, 5};
for (int number : numbers) {
    System.out.println(number);
}

在上述代码中,for (int number : numbers) 声明了一个 int 类型的变量 number,它依次代表数组 numbers 中的每一个元素。循环体中可以对 number 进行各种操作,这里只是简单地打印出来。

遍历集合

import java.util.ArrayList;
import java.util.List;

public class ForeachExample {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");

        for (String fruit : fruits) {
            System.out.println(fruit);
        }
    }
}

这段代码创建了一个 List 集合,并使用 foreach 循环遍历打印出集合中的每一个元素。

Stream 基础概念

Stream 是 Java 8 引入的一种抽象概念,它代表了一系列支持顺序和并行聚合操作的元素。Stream 并不存储数据,而是提供了一种声明式的方式来处理集合或数组中的数据,通过一系列的中间操作和终端操作来实现数据的过滤、映射、聚合等功能。

Stream 使用方法

创建 Stream

  1. 从集合创建
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamExample {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");

        Stream<String> fruitStream = fruits.stream();
    }
}
  1. 从数组创建
int[] numbers = {1, 2, 3, 4, 5};
Stream<Integer> numberStream = java.util.Arrays.stream(numbers).boxed();

中间操作

中间操作会返回一个新的 Stream,可以链式调用多个中间操作。常见的中间操作有 filtermapsorted 等。 1. filter 操作

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamFilterExample {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");

        Stream<String> filteredStream = fruits.stream()
              .filter(fruit -> fruit.startsWith("A"));
    }
}

上述代码使用 filter 操作过滤出以字母 A 开头的水果。

  1. map 操作
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamMapExample {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");

        Stream<Integer> lengthStream = fruits.stream()
              .map(String::length);
    }
}

这里的 map 操作将每个水果字符串映射为其长度。

终端操作

终端操作会触发 Stream 的处理,并返回一个结果。常见的终端操作有 forEachcollectcount 等。 1. forEach 终端操作

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamForEachExample {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");

        fruits.stream()
              .forEach(System.out::println);
    }
}
  1. collect 终端操作
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamCollectExample {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");

        List<String> filteredFruits = fruits.stream()
              .filter(fruit -> fruit.length() > 5)
              .collect(Collectors.toList());
    }
}

上述代码使用 collect 操作将过滤后的水果收集到一个新的 List 中。

常见实践

过滤数据

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class FilteringExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);
        numbers.add(5);

        List<Integer> evenNumbers = numbers.stream()
              .filter(number -> number % 2 == 0)
              .collect(Collectors.toList());
        System.out.println(evenNumbers);
    }
}

这段代码过滤出列表中的偶数。

映射数据

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class MappingExample {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>();
        words.add("hello");
        words.add("world");

        List<Integer> wordLengths = words.stream()
              .map(String::length)
              .collect(Collectors.toList());
        System.out.println(wordLengths);
    }
}

此代码将每个单词映射为其长度。

聚合数据

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class AggregationExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);
        numbers.add(5);

        int sum = numbers.stream()
              .mapToInt(Integer::intValue)
              .sum();
        System.out.println(sum);
    }
}

这里计算了列表中所有数字的总和。

最佳实践

选择合适的遍历方式

如果只是简单地顺序遍历集合或数组,并且不需要进行复杂的数据处理,foreach 循环是一个不错的选择,它的语法简单易懂。但如果需要进行复杂的数据过滤、映射、聚合等操作,Stream API 更能体现其优势,它的声明式风格使代码更加简洁和易读。

避免不必要的装箱和拆箱

在使用 Stream 处理基本数据类型时,尽量使用对应的 IntStreamLongStreamDoubleStream 等,避免自动装箱和拆箱带来的性能开销。例如,处理 int 类型数据时,使用 IntStream 而不是 Stream<Integer>

并行流的合理使用

虽然并行流可以提高数据处理的效率,但并不是在所有情况下都适用。并行流会带来线程创建、调度和数据合并等额外开销,对于数据量较小或处理逻辑简单的情况,并行流可能反而会降低性能。在使用并行流之前,需要进行性能测试,确保其能带来实际的性能提升。

小结

foreach 循环和 Stream API 都是 Java 中处理集合和数组数据的强大工具。foreach 循环语法简单,适用于基本的顺序遍历操作;而 Stream API 提供了丰富的函数式操作,更适合复杂的数据处理任务。通过了解它们的基础概念、使用方法、常见实践以及最佳实践,开发者可以根据具体的需求选择合适的方式,编写出高效、简洁且易于维护的代码。

参考资料