使用流

Posted mashiso

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用流相关的知识,希望对你有一定的参考价值。

一、筛选和切片

1、用谓词筛选

Streams接口支持filter方法 ,该操作会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。

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

  

2、筛选各异的元素

流还支持一个叫作distinct的方法,它会返回一个元素各异(根据流所生成元素的hashCodeequals方法实现)的流 。

例如:以下代码会筛选出列表中所有的偶数,并确保没有重复 。

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

  

3、截短流

流支持limit(n)方法,该方法会返回一个不超过给定长度的流。 如果流是有序的,则最多会返回前n个元素。
比如,你可以建立一个List,选出热量
超过300卡路里的头三道菜: 

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

  

4、跳过元素

流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。 

请注意,limit(n)skip(n)是互补的!

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

  

二、映射

一个非常常见的数据处理套路就是从某些对象中选择信息。Stream API也通过mapflatMap方法提供了类似的工具。

1、对流中每一个元素应用函数

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

映射,是因为它和转换类似,但区别在于它是创建一个新版本而不是去修改。

List<String> dishNames = menu.stream()
    .map(Dish::getName)
    .collect(toList());

  

需求:给定一个单词列表,你想要返回另一个列表,显示每个单词中有几个字母。 

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

  

2、流的扁平化

对于一张单词表,如何返回一张列表,列出里面各不相同的字符呢?例如,给定单词列表["Hello","World"],你想要返回列表["H","e","l", "o","W","r","d"] 。

第一个版本:

words.stream()
    .map(word -> word.split(""))
    .distinct()
    .collect(toList());

 这个方法的问题在于,传递给map方法的Lambda为每个单词返回了一个String[]String列表)。因此,map返回的流实际上是 Stream<String[]> 类型的 。你真正想要的是用Stream<String>来表示一个字符流。 

技术分享图片

第二个版本:尝试使用mapArrays.stream()

首先,你需要一个字符流,而不是数组流。有一个叫作Arrays.stream()的方法可以接受一个数组并产生一个 。

words.stream()
    .map(word -> word.split(""))
    //让每个数组变成一个单独的流
   .map(Arrays::stream) .distinct() .collect(toList());

  

第三版本:使用flatMap  

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

  

使用flatMap方法的效果是,各个数组并不是分别映射成一个流 ,而是映射成流的内容。

一言以蔽之,flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。   

技术分享图片

三、查找和匹配

另一个常见的数据处理套路是看看数据集中的某些元素是否匹配一个给定的属性。 StreamAPI通过allMatch anyMatchnoneMatchfindFirstfindAny方法提供了这样的工具 。

1、检查谓词是否至少匹配一个元素

anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”。

if(menu.stream().anyMatch(Dish::isVegetarian)){
    System.out.println("The menu is (somewhat) vegetarian friendly!!");
}

anyMatch方法返回一个boolean,因此是一个终端操作。

2、检查谓词是否匹配所有元素

allMatch方法的工作原理和anyMatch类似,但它会看看流中的元素是否都能匹配给定的谓语。

boolean isHealthy = menu.stream().allMatch(d -> d.getCalories() < 1000);

 

noneMatch

allMatch相对的是noneMatch。它可以确保流中没有任何元素与给定的谓词匹配。

boolean isHealthy = menu.stream().noneMatch(d -> d.getCalories() >= 1000);

  

3、查找元素

findAny方法将返回当前流中的任意元素。它可以与其他流操作结合使用。

Optional<Dish> dish =
    menu.stream()
        .filter(Dish::isVegetarian)
        .findAny();

Optional简介

Optional<T>类( java.util.Optional)是一个容器类,代表一个值存在或不存在。

Optional里面几种可以迫使你显式地检查值是否存在或处理值不存在的情形的方法也不错。

? isPresent()将在Optional包含值的时候返回true, 否则返回false

? ifPresent(Consumer<T> block)会在值存在的时候执行给定的代码块。我们在第3介绍了Consumer函数式接口;它让你传递一个接收T类型参数,并返回voidLambda表达式。

? T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。

? T orElse(T other)会在值存在时返回值,否则返回一个默认值。

menu.stream()
    .filter(Dish::isVegetarian)
    .findAny()
    .ifPresent(d -> System.out.println(d.getName());

  

4、查找第一个元素

有些流有一个出现顺序encounter order)来指定流中项目出现的逻辑顺序(比如由List排序好的数据列生成的流)。对于这种流,你可能想要找到第一个元素。为此有一个findFirst方法,它的工作方式类似于findany 

List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree =
    someNumbers.stream()
        .map(x -> x * x)
        .filter(x -> x % 3 == 0)
        .findFirst();     // 9

  

四、归纳

1、元素求和

for-each版本:

int sum = 0;
for (int x : numbers) {
    sum += x;
}

  

int sum = numbers.stream().reduce(0, (a, b) -> a + b);  

 

reduce接受两个参数:

1、一个初始值,这里是0;

2、一个BinaryOperator<T>来将两个元素结合起来产生一个新值,这里我们用的是lambda (a, b) -> a + b。 

Java 8中, Integer类现在有了一个静态的sum方法来对两个数求和, 

int sum = numbers.stream().reduce(0, Integer::sum);

  

无初始值

reduce还有一个重载的变体,它不接受初始值,但是会返回一个Optional对象 :

Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));

为什么它返回一个Optional<Integer>呢?考虑流中没有任何元素的情况。 reduce操作无法返回其和,因为它没有初始值。这就是为什么结果被包裹在一个Optional对象里,以表明和

可能不存在。

2、最大值和最小值

Optional<Integer> max = numbers.stream().reduce(Integer::max);

 

五、总结:

你现在已经看到了很多流操作,可以用来表达复杂的数据处理查询。

技术分享图片

 






















以上是关于使用流的主要内容,如果未能解决你的问题,请参考以下文章

此应用小部件片段中所有意图 (PendingIntents) 的逻辑流

从流输入中解析没有根元素的 XML 片段列表

条件片段和导航重用

如何清除片段中的参数?

是否可以动态编译和执行 C# 代码片段?

16个必备的JavaScript代码片段