Java Stream常用操作

134 阅读3分钟

1 筛选和切片

该功能的使用场景主要是用来筛选流中的元素。

1.1 用谓词进行筛选

谓词:一个返回boolean的函数

List<Dish> vegetarianMenu = menu.stream()
            .filter(Dish::isVegetarian)
            .collect(toList());

1.2 筛选各异的元素

List<Integer> numbers = Arrays.asList(1,, 2, 1, 3, 2, 2, 4);
numbers.stream()
    .filter(i -> i % 2 == 0)
    .distinct()
    .forEach(System.out::println);

1.3 截断流

流支持limit(n)方法,该方法会返回一个不超过给定长度的流。例如下面的方法就是获取Calory大于300的菜谱。

List<Dish> dishes = menu.stream()
    .filter(d -> d.getCalories() > 300)
    .limit(3)
    .collect(toList());

1.4 跳过元素

流还支持skip(n)方法,返回一个扔掉了前n个元素的流。

List<Dish> dishes = menu.stream()
    .filter(d -> d.getCalories() > 300)
    .skip(2)
    .collect(toList());

2 映射

将流中的元素变成其他的值类型

2.1 对流中的每个元素应用函数

流支持map方法,该方法会接受一个函数作为参数,并将其应用到流中的每个元素上,然后将其映射为一个新的元素。

例如如下的代码段,给定一个单词列表,返回一个显示每个单词中含有几个字母。

List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List<Integer> wordLengths = words.stream()
    .map(String::length)
    .collect(toList());

2.2 流的扁平化

针对一张单词列表,如何返回一张列表,列出里面各不相同的字符呢?例如,给定单词列表["Hello","World"],想要返回["H","e","l","l","o","W","o","r","l","d"]。最初的想法,可能使用如下的代码。

String[] words = {"Hello", "World"};
words.stream()
        .map(word -> word.split(""))
        .distinct()
        .collect(Collectors.toList());

但这种方式的问题在于,传递给map方法的Lambada为每个单词返回了一个String[]。因此,map返回的流实际上是Stream<String[]>类型。下图表述了上述代码存在的问题。

image.png 可以使用flatMap方法来解决该问题。具体代码如下

List<String> uniqueCharacters = words.stream()
	.map(w -> w.split(""))
	.flatMap(Arrays::stream)
	.distinct()
	.collect(Collectors.toList());

在增加flatMap后,返回的流就是Stream<String>类型。具体可以参考下图。

image.png

测验1: 给定一个数字列表,例如[1,2,3,4],返回每个数的平方构成的列表。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> result = numbers
                .stream()
                .map(i -> i * i)
                .collect(Collectors.toList());

测验2:给定两个数字表,返回所有的数对。例如,给定列表[1, 2, 3]和列表[3, 4],应 该返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]

List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<Integer> numbers2 = Arrays.asList(3, 4);
List<int[]> result = numbers1
        .stream()
        .flatMap(i -> numbers2.stream().map(j -> new int[]{i, j}))
        .collect(Collectors.toList());

测验3: 扩展前一个例子,只返回总和能被3整除的数对

List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<Integer> numbers2 = Arrays.asList(3, 4);
List<int[]> result = numbers1
        .stream()
        .flatMap(i -> numbers2.stream().filter(j -> (i + j) % 3 == 0).map(j -> new int[]{i, j}))
        .collect(Collectors.toList());

3 用流收集数据

3.1 规约

使用流来实现数组求和。注意,下面这种方式在日常工作中是比较少用的,可以直接使用Stream的sum()方法。

List<Integer> numbers1 = Arrays.asList(1, 2, 3);
Integer reduce = numbers1.stream().reduce(0, (a, b) -> a + b);
Integer reduce = numbers1.stream().reduce(0, Integer::sum);

获取菜单中热量最高的菜

List<Dish> menu = new ArrayList<>();
Optional<Dish> maxCalories = menu.stream()
        .collect(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)));
Optional<Dish> maxCalories = menu.stream()
        .max(Comparator.comparingInt(Dish::getCalories));

3.2 汇总

获取菜单中热量总和

List<Dish> menu = new ArrayList<>();
Integer sum = menu.stream().collect(Collectors.summingInt(Dish::getCalories));

获取菜单热量的平均值

List<Dish> menu = new ArrayList<>();
Double avg = menu.stream().collect(Collectors.averagingInt(Dish::getCalories));

一站式的解决方式

List<Dish> menu = new ArrayList<>();
IntSummaryStatistics summaryStatistics = menu.stream().collect(Collectors.summarizingInt(Dish::getCalories));
summaryStatistics.getSum();
summaryStatistics.getAverage();
summaryStatistics.getCount();
summaryStatistics.getMax();
summaryStatistics.getMin();

3.3 分组

根据菜单类型分组

Map<Dish.Type, List<Dish>> dishesByType =
        menu.stream().collect(Collectors.groupingBy(Dish::getType));

稍微复杂点的分组

Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream().collect(
        Collectors.groupingBy(dish -> {
            if (dish.getCalories() <= 400) return CaloricLevel.DIET;
            else if (dish.getCalories() <= 700) return
                    CaloricLevel.NORMAL;
            else return CaloricLevel.FAT;
        }));