乐趣区

关于java8:从零开始学习Java8-Stream看这篇就够了


为何须要引入流

在咱们平时的开发中简直每天都会有到 List、Map 等汇合 API,若是问 Java 什么 API 应用最多,我想也应该是汇合了。举例:如果我有个汇合 List,外面元素有1,7,3,8,2,4,9,须要找出外面大于 5 的元素,具体实现代码:

public List<Integer> getGt5Data() {List<Integer> data = Arrays.asList(1, 7, 3, 8, 2, 4, 9);
    List<Integer> result = new ArrayList<>();
    for (Integer num : data) {if (num > 5) {result.add(num);
        }
    }
    return result;
}

这个实现让咱们感觉到了汇合的操作不是太完满,如果是数据库的话,咱们只须要简略的在 where 前面加一个条件大于 5 就能够失去咱们想要的后果,为什么 Java 的汇合就没有这种 API 呢?
其次,如果咱们遇到有大汇合须要解决,为了进步性能,咱们可能须要应用到多线程来解决,然而写并行程序的复杂度有进步了不少。

基于以上的问题,所有 Java8 推出了 Stream


Stream 简介

Stream 有哪些特点:

  • 元素的序列:与汇合一样能够拜访外面的元素,汇合讲的是数据,而流讲的是操作,比方:filter、map
  • 源: 流也须要又一个提供数据的源,程序和生成时的程序统一
  • 数据的操作:流反对相似于数据库的操作,反对程序或者并行处理数据;下面的例子用流来实现会更加的简洁
public List<Integer> getGt5Data() {return Stream.of(1, 7, 3, 8, 2, 4, 9)
            .filter(num -> num > 5)
            .collect(toList());
}
  • 流水线操作:很多流的办法自身也会返回一个流,这样能够把多个操作连接起来,造成流水线操作
  • 外部迭代:与以往的迭代不同,流应用的外部迭代,用户只须要专一于数据处理
  • 只能遍历一次:遍历实现之后咱们的流就曾经生产完了,再次遍历的话会抛出异样

应用 Stream

Java8 中的 Stream 定义了很多办法,根本能够把他们分为两类:两头操作、终端操作;要应用一个流个别都须要三个操作:

  1. 定义一个数据源
  2. 定义两头操作造成流水线
  3. 定义终端操作,执行流水线,生成计算结果

构建流

  1. 应用 Stream.of 办法构建一个流
Stream.of("silently","9527","silently9527.cn")
        .forEach(System.out::println);
  1. 应用数组构建一个流
int[] nums = {3, 5, 2, 7, 8, 9};
Arrays.stream(nums).sorted().forEach(System.out::println);
  1. 通过文件构建一个流

应用 java.nio.file.Files.lines 办法能够轻松构建一个流对象

Files.lines(Paths.get("/Users/huaan9527/Desktop/data.txt"))
                .forEach(System.out::println);

两头操作

两头操作会返回另外一个流,这样能够让多个操作连接起来造成一个流水线的操作,只有不触发终端操作,那么这个两头操作都不会理论执行。

filter

该操作承受一个返回 boolean 的函数,当返回 false 的元素将会被排除掉

举例:如果咱们 100 个客户,须要筛选出年龄大于 20 岁的客户

List<Customer> matchCustomers = allCustomers.stream()
                .filter(customer -> customer.getAge()>20)
                .collect(toList());
distinct

该操作将会排除掉反复的元素

List<Integer> data = Stream.of(1, 7, 3, 8, 2, 4, 9, 7, 9)
        .filter(num -> num > 5)
        .distinct()
        .collect(toList());
limit

该办法限度流只返回指定个数的元素

List<Integer> data = Stream.of(1, 7, 3, 8, 2, 4, 9, 7, 9)
        .filter(num -> num > 5)
        .limit(2)
        .collect(toList());
skip

扔掉前指定个数的元素;配合 limit 应用能够达到翻页的成果

List<Integer> data = Stream.of(1, 7, 3, 8, 2, 4, 9, 7, 9)
        .filter(num -> num > 5)
        .skip(1)
        .limit(2)
        .collect(toList());
map

该办法提供一个函数,流中的每个元素都会利用到这个函数上,返回的后果将造成新类型的流持续后续操作。
举例:如果咱们 100 个客户,须要筛选出年龄大于 20 岁的客户,打印出他们的名字

allCustomers.stream()
            .filter(customer -> customer.getAge() > 20)
            .map(Customer::getName)
            .forEach(System.out::println);

在调用 map 之前流的类型是Stream<Customer>,执行完 map 之后的类型是Stream<String>

flatMap

如果咱们须要把客户的名字中的每个字符打印进去,代码如下:

List<Customer> allCustomers = Arrays.asList(new Customer("silently9527", 30));
allCustomers.stream()
        .filter(customer -> customer.getAge() > 20)
        .map(customer -> customer.getName().split(""))
        .forEach(System.out::println);

执行本次后果,你会发现没有达到冀望的后果,打印的后果

[Ljava.lang.String;@38cccef

这是因为调用 map 之后返回的流类型是 Stream<String[]>,所有 forEach 的输出就是String[];这时候咱们须要应用 flatMap 把String[] 中的每个元素都转换成一个流,而后在把所有的流连接成一个流,批改后的代码如下

List<Customer> allCustomers = Arrays.asList(new Customer("silently9527", 30));
allCustomers.stream()
        .filter(customer -> customer.getAge() > 20)
        .map(customer -> customer.getName().split(""))
        .flatMap(Arrays::stream)
        .forEach(System.out::println);

执行后果:

sorted

对所有的元素进行排序

List<Integer> numbers = Arrays.asList(1, 7, 3, 8, 2, 4, 9);
numbers.stream().sorted(Integer::compareTo).forEach(System.out::println);

终端操作

终端操作会执行所有的两头操作生成执行的后果,执行的后果不在是一个流。

anyMatch

如果流中有一个元素满足条件将返回 true

if (allCustomers.stream().anyMatch(customer -> "silently9527".equals(customer.getName()))) {System.out.println("存在用户 silently9527");
}
allMatch

确保流中所有的元素都能满足

if (allCustomers.stream().allMatch(customer -> customer.getAge() > 20)) {System.out.println("所有用户年龄都大于 20");
}
noneMatch

与 allMatch 操作相同,确保流中所有的元素都不满足

if (allCustomers.stream().noneMatch(customer -> customer.getAge() < 20)) {System.out.println("所有用户年龄都大于 20");
}
findAny

返回流中的任意一个元素,比方返回大于 20 岁的任意一个客户

Optional<Customer> optional = allCustomers.stream()
        .filter(customer -> customer.getAge() > 20)
        .findAny();
findFirst

返回流中的第一个元素

Optional<Customer> optional = allCustomers.stream()
        .filter(customer -> customer.getAge() > 20)
        .findFirst();
reduce

承受两个参数:一个初始值,一个 BinaryOperator<T> accumulator 将两个元素合并成一个新的值
比方咱们对一个数字 list 累加

List<Integer> numbers = Arrays.asList(1, 7, 3, 8, 2, 4, 9);
Integer sum = numbers.stream().reduce(0, (a, b) -> a + b);

下面的代码,咱们能够简写

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

找出流中的最大值、最小值 min、max

numbers.stream().reduce(Integer::max)
numbers.stream().reduce(Integer::min)
count

统计流中元素的个数

numbers.stream().count()

数据收集器 collect

在 Java8 中曾经预约义了很多收集器,咱们能够间接应用,所有的收集器都定义在了 Collectors 中,基本上能够把这些办法分为三类:

  • 将元素归约和汇总成一个值
  • 分组
  • 分区

归约和汇总

先看下咱们之前求最大值和最小值采纳收集器如何实现

  1. 找出年龄最大和最小的客户
Optional<Customer> minAgeCustomer = allCustomers.stream().collect(minBy(Comparator.comparing(Customer::getAge)));
Optional<Customer> maxAgeCustomer = allCustomers.stream().collect(maxBy(Comparator.comparing(Customer::getAge)));
  1. 求取年龄的平均值
Double avgAge = allCustomers.stream().collect(averagingInt(Customer::getAge));
  1. 进行字符串的连贯

把客户所有人的名字连接成一个字符串用逗号分隔

allCustomers.stream().map(Customer::getName).collect(joining(","));

分组

在数据库的操作中,咱们能够轻松的实现通过一个属性或者多个属性进行数据分组,接下来咱们看看 Java8 如何来实现这个性能。

  1. 依据客户的年龄进行分组
Map<Integer, List<Customer>> groupByAge = allCustomers.stream().collect(groupingBy(Customer::getAge));

Map 的 key 就是分组的值年龄,List<Customer>就是雷同年龄的用户

  1. 咱们须要先依照用户的地区分组,在按年龄分组
Map<String, Map<Integer, List<Customer>>> groups = allCustomers.stream()
                .collect(groupingBy(Customer::getArea, groupingBy(Customer::getAge)));

在绝对于一般的分组,这里多传了第二个参数又是一个groupingBy;实践上咱们能够通过这个形式扩大到 n 层分组

  1. 分组后再统计数量
Map<String, Long> groupByCounting = allCustomers.stream()
            .collect(groupingBy(Customer::getArea, counting()));
  1. 以用户所在地辨别组后找出年龄最大的用户
Map<String, Optional<Customer>> optionalMap = allCustomers.stream()
                .collect(groupingBy(Customer::getArea, maxBy(Comparator.comparing(Customer::getAge))));

这时候返回的 Map 中的 value 被 Optional 包裹,如果咱们须要去掉 Optional,能够应用collectingAndThen

Map<String, Customer> customerMap = allCustomers.stream()
        .collect(groupingBy(Customer::getArea,
                collectingAndThen(maxBy(Comparator.comparing(Customer::getAge)), Optional::get)
        ));

写在最初(看完不点赞,你们想白嫖我吗)

  • 首先感激大家能够急躁地读到这里。点关注,不迷路
  • 当然,文中或者会存在或多或少的有余、谬误之处,有倡议或者意见也十分欢送大家在评论交换。
  • 最初,白嫖不好,创作不易 ,心愿敌人们能够 点赞评论关注 三连,因为这些就是我分享的全副能源起源????

原创不易 转载请注明出处:https://silently9527.cn/archives/66

参数材料《Java8 实战》

退出移动版