什么是Stream
Java 8引入了一个新的概念 - Stream(流),它是一种处理集合数据的新方式,具有简洁、可读性强、并行处理等优点。
Stream 是一个来自数据源的元素队列并支持聚合操作,它可以将多个操作组合起来,以管道的方式处理集合。在使用 Stream 时,首先需要指定一个数据源,比如一个 List、Set 或者数组等。然后可以对这个数据源执行一系列操作,这些操作分为两种类型:
- Intermediate:中间操作,会返回一个 Stream 对象,并且可以对该 Stream 进行下一步的操作。常用的中间操作有 filter、map、sorted 等。
- Terminal:终端操作,会返回一个非 Stream 的结果,常用的终端操作有 forEach、reduce、collect 等。
Stream 的使用流程大致如下:
1. 创建一个 Stream
2. 通过一系列的中间操作对 Stream 进行操作(过滤、排序、转换等)
3. 执行一个终端操作获取结果
上代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.filter(n -> n % 2 == 0)
.mapToInt(Integer::intValue)
.sum();
System.out.println(sum); // 输出 6
这段代码创建了一个包含 1 到 5 的整数列表,然后通过 stream() 方法将其转换成一个 Stream。接着使用 filter 方法过滤出偶数,然后使用 mapToInt 方法将 Integer 类型转换成 int 类型,最后使用 sum 方法计算所有元素的和并输出结果。
Stream 的优点在于可以让代码更简洁、可读性更好、通过各种Api的组合完成对集合数据的复杂操作、并且可以通过并行处理来提高程序性能。但是需要注意的是,Stream 并不是在所有情况下都比传统的集合操作更好,具体使用时需要根据具体场景进行取舍。
Api学习
stream()
Stream API中的stream()方法用于将集合对象转换为Stream对象。其语法如下:
Stream<T> stream()
其中,T是集合中元素的类型。stream()方法返回一个Stream对象,其中包含了集合中所有元素的信息。
下面是一个示例代码,演示如何使用stream()方法将一个List对象转换为Stream对象:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream();
在上面的代码中,首先通过Arrays.asList()方法创建了一个包含五个整数的List对象numbers。接着,使用stream()方法将numbers转换为Stream对象。
需要注意的是,由于Stream对象是惰性求值的,因此在调用stream()方法时并不会立即开始遍历集合。只有在使用Stream对象中的方法时,才会开始遍历集合并执行相应的操作。因此,可以通过链式调用Stream对象中的多个方法,来实现一系列操作的连续执行。例如开头的那段代码,对就是这个:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.filter(n -> n % 2 == 0)
.mapToInt(Integer::intValue)
.sum();
filter()
Stream API中的filter()方法用于根据指定的条件过滤集合中的元素,并返回一个包含满足条件的元素的新的Stream对象。其语法如下:
Stream<T> filter(Predicate<? super T> predicate)
其中,T是集合中元素的类型,Predicate是一个函数式接口,用于表示一个接受一个参数并返回一个布尔值的函数。在调用filter()方法时,需要将一个Predicate对象作为参数传入,表示要对集合中的元素进行过滤的条件。
下面是一个示例代码,演示如何使用filter()方法过滤List中的偶数:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
在上面的代码中,首先使用stream()方法将List对象转换为Stream对象。然后,使用filter()方法过滤出其中的偶数。在这个例子中,使用lambda表达式 `n -> n % 2 == 0` 作为Predicate对象,表示只要集合中的元素模2等于0,就保留该元素。
需要再一次强调的是,Stream对象是惰性求值的,因此调用filter()方法时并不会立即开始遍历集合,而是在调用collect()方法时才开始遍历并执行相应的操作。如果没有进行collect()操作,filter()方法的调用不会对原始集合产生任何影响。
collect()
Stream API中的collect()方法用于将Stream中的元素收集到一个集合中。
collect()方法的语法如下:
<R, A> R collect(Collector<? super T, A, R> collector)
其中,T是Stream中的元素类型,A是用于中间结果累积的类型,R是collect()方法的返回值类型。collector是一个Collector类型的参数,用于定义收集逻辑,包括如何累积中间结果,如何合并中间结果,以及如何将中间结果转换为最终结果。
Stream API提供了许多预定义的Collector类型,包括toList()、toSet()、toMap()等等,可以根据需求选择合适的Collector类型。除了预定义的Collector类型,也可以自定义Collector类型,实现更复杂的收集逻辑。
又是这一段代码,它演示如何使用collect()方法将一个Stream中的元素收集到一个List中:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
在上面的代码中,首先通过Arrays.asList()方法创建了一个包含五个整数的List对象numbers。接着,使用stream()方法将numbers转换为Stream对象,然后使用filter()方法过滤出其中的偶数。最后,使用collect()方法将过滤后的结果收集到一个List对象中。
需要注意的是,使用collect()方法收集Stream中的元素时,需要注意线程安全问题。如果在多线程环境中使用Stream API,建议使用线程安全的集合类,或者使用collect()方法的并发版本。例如,可以使用Collectors.toConcurrentMap()方法将Stream中的元素收集到一个线程安全的ConcurrentMap中。
map()
Stream API中的map()方法是一个用于将Stream中的元素映射为另外一种形式的方法。其接受一个函数作为参数,这个函数将被应用到Stream中的每个元素上,产生一个新的Stream,这个Stream中包含了映射后的元素。
map()方法的语法如下:
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
其中,T是原Stream中的元素类型,R是map()方法的返回值类型。mapper是一个Function类型的参数,接受一个T类型的参数,返回一个R类型的结果。
上代码,目的是用map()方法将一个String类型的List中的每个元素转换为大写形式:
List<String> words = Arrays.asList("hello", "world", "java", "stream");
List<String> upperWords = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
在上面的代码中,首先通过Arrays.asList()方法创建了一个包含四个String元素的List对象words。接着,使用stream()方法将words转换为Stream对象,然后使用map()方法将每个String元素转换为大写形式。最后,使用collect()方法将转换后的结果收集到一个List对象中。
需要注意的是,在map()方法中,参数mapper可以是一个lambda表达式,也可以是一个方法引用,如上面示例代码中的String::toUpperCase。使用方法引用能够更加简洁地实现转换逻辑。