Stream API之常用操作

1 collect(toList())

    collect(toList())方法由Stream里的值生成一个列表,是一个及早求值操作。

    Stream的of方法使用一组初始值生成新的Stream。事实上,collect的用法不仅限于此,它是一个非常通用的强大结构,下面是使用collect方法的一个例子:

List<String> collected = Stream.of("a", "b", "c").collect(Collectors.toList());
assertEquals(Arrays.asList("a", "b", "c"), collected);

    这段程序展示了如何使用collect(toList())方法从Stream中生成一个列表。如上文所述,由于很多Stream操作都是惰性求值,因此调用Stream上一系列方法之后,还需要最后再调用一个类似collect的及早求值方法。

    这个例子也展示了本文中所有示例代码的通用格式。首先由列表生成一个Stream,然后进行一些Stream上的操作,继而是collect操作,由Stream生成列表,最后使用断言判断结果是否和预期一致。

    形象一点儿的话,可以将Stream想象成汉堡,将最前和最后对Stream操作的方法想象成两片面包,这两片面包帮助我们认清操作的起点和终点。


2 map

    如果有一个函数可以将一种类型的值转换成另外一种类型,map操作就可以使用该函数,将一个流中的值转换成一个新的流。

    读者可能已经注意到,以前编程时或多或少使用过类似map的操作。比如编写一段Java代码,将一组字符串转换成对应的大写形式。在一个循环中,对每个字符串调用toUppercase方法,然后将得到的结果加入一个新的列表。代码如下所示:

List<String> collected = new ArrayList<>();
for (String string : asList("a", "b", "hello")) {
    String uppercaseString = string.toUpperCase();
    collected.add(uppercaseString);
}
assertEquals(Arrays.asList("A", "B", "HELLO"), collected);

    如果你经常实现上面代码中这样的for循环,就不难猜出map是Stream上最常用的操作之一。下面展示了如何使用新的流框架将一组字符串转换成大写形式:

List<String> collected = Stream.of("a", "b", "hello").map(string -> string.toUpperCase()).collect(Collectors.toList());
assertEquals(Arrays.asList("A", "B", "HELLO"), collected);

    传给map的Lambda表达式只接受一个String类型的参数,返回一个新的String。参数和返回值不必属于同一种类型,但是Lambda表达式必须是Function接口的一个实例,Function接口是只包含一个参数的普通函数接口。


3 filter

    遍历数据并检查其中的元素时,可尝试使用Stream中提供的新方法filter。

    假设要找出一组字符串中以数字开头的字符串,比如字符串“1abc”和“abc”,其中“1abc”就是符合条件的字符串。可以使用一个for循环,内部用if条件语句判断字符串的第一个字符来解决这个问题,代码如下所示:

List<String> beginningWithNumbers = new ArrayList<>();
for (String value : asList("a", "1abc", "abc1")) {
    if (isDigit(value.charAt(0))) {
        beginningWithNumbers.add(value);
    }
}
assertEquals(Arrays.asList("1abc"), beginningWithNumbers);

    你可能已经写过许多类似的代码:这被称为filter模式。该模式的核心思想是保留Stream中的一些元素,而过滤掉其他的。下面的代码展示了如何使用函数式风格编写相同的代码:

List<String> beginningWithNumbers = Stream.of("a", "1abc", "abc1").filter(value -> isDigit(value.charAt(0))).collect(Collectors.toList());
assertEquals(Arrays.asList("1abc"), beginningWithNumbers);

    和map很像,filter接受一个函数作为参数,该函数用Lambda表达式表示。该函数和前面示例中if条件判断语句的功能一样,如果字符串首字母为数字,则返回true。若要重构遗留代码,for循环中的if条件语句就是一个很强的信号,可用filter方法替代。

    由于此方法和if条件语句的功能相同,因此其返回值肯定是true或者false。经过过滤,Stream中符合条件的,即Lambda表达式值为true的元素被保留下来。该Lambda表达式的函数接口正是之前文章中介绍过的Predicate。


4 flatMap

    flatMap方法可用Stream替换值,然后将多个Stream连接成一个Stream。    

    前面已介绍过map操作,它可用一个新的值代替Stream中的值。但有时,用户希望让map操作有点变化,生成一个新的Stream对象取而代之。用户通常不希望结果是一连串的流,此时flatMap最能派上用场。

    我们看一个简单的例子。假设有一个包含多个列表的流,现在希望得到所有数字的序列。该问题的一个解法如下面所示:

List<Integer> together = Stream.of(asList(1, 2), asList(3, 4)).flatMap(numbers -> numbers.stream()).collect(Collectors.toList());
assertEquals(Arrays.asList(1, 2, 3, 4), together);

    调用stream方法,将每个列表转换成Stream对象,其余部分由flatMap方法处理。flatMap方法的相关函数接口和map方法的一样,都是Function接口,只是方法的返回值限定为Stream类型罢了。


5 max和min

    Stream上常用的操作之一是求最大值和最小值。Stream API中的max和min操作足以解决这一问题。下面代码是查找专辑中最短曲目所用的代码,展示了如何使用max和min操作。为了方便检查程序结果是否正确,代码片段中罗列了专辑中的曲目信息:

List<Track> tracks = asList(new Track("Bakai", 524), new Track("Violets for Your Furs", 378), new Track("Time Was", 451));
Track shortestTrack = tracks.stream().min(Comparator.comparing(track -> track.getLength())).get();
assertEquals(tracks.get(1), shortestTrack);

    查找Stream中的最大或最小元素,首先要考虑的是用什么作为排序的指标。已查找专辑中的最短曲目为例,排序的指标就是曲目的长度。

    为了让Stream对象按照曲目长度进行排序,需要传给它一个Comparator对象。Java 8提供了一个新的静态方法comparing,使用它可以方便地实现一个比较器。放在以前,我们需要比较两个对象的某项属性的值,现在只需要提供一个存取方法就够了。本例中使用getLength方法。

    花点时间研究一下comparing方法是值得的。实际上这个方法接受一个函数并返回另一个函数。我知道,这听起来像句废话,但是却很有用。这个方法本该早已加入Java标准库,但由于匿名内部类可读性差且书写冗长,一直未能实现。现在有了Lambda表达式,代码变得简洁易懂。

    此外,还可以调用空Stream的max方法,返回Optional对象。Optional对象有点陌生,它代表一个可能存在也可能不存在的值。如果Stream为空,那么该值不存在,如果不为空,则该值存在。通过调用get方法可以取出Optional对象中的值。

猜你喜欢

转载自blog.csdn.net/weixin_30342639/article/details/88671029