java8新特性——Stream API

时间:2023-03-09 19:26:13
java8新特性——Stream API

  Java8中有两大最为重要得改变,其一时Lambda表达式,另外就是 Stream API了。在前面几篇中简单学习了Lambda表达式得语法,以及函数式接口。本文就来简单学习一下Stream API(java.util.stream.*)。

  Stream 是 Java8中处理集合得关键抽象概念,他可以指定你希望对集合进行得操作,可以执行非常复杂得查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似使用SQL执行得数据库查询。也可以使用S他ream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用得处理数据得方式。

  在Stream操作过程中,可以对数据流做过滤,排序,切片等操作,但是操作之后会产生一个新的流,而数据源则不会发生改变。

一、什么是 Stream

  Stream是数据渠道,用于操作数据源(集合,数组等)所生成得元素序列。而集合讲得是数据,流讲得是计算。

  注意:

    ①. Stream 自己不会存储元素。

    ②. Stream 不会改变源对象。相反,它会返回一个持有结果得新Stream

    ③. Stream 操作时延迟执行得,这意味着它们会等到需要结果时才执行。(延迟加载)

二、Stream 操作的三个步骤

  1). 创建 Stream

    一个数据源(集合,数组),获取一个流。

  2). 中间操作

    一个中间操作链,对数据源的数据进行处理。

  3). 终止操作

    一个终止操作,执行中间操作链,并产生结果。

三、创建Stream 的四种方式

  1). 通过Collection得Stream()方法(串行流)或者 parallelStream()方法(并行流)创建Stream。

   /**
* 创建 Stream的四种方式
* 1.通过Collection得Stream()方法(串行流)
或者 parallelStream()方法(并行流)创建Stream
*/
@Test
public void test1 () { //1. 通过Collection得Stream()方法(串行流)
//或者 parallelStream()方法(并行流)创建Stream
List<String> list = new ArrayList<String>();
Stream<String> stream1 = list.stream(); Stream<String> stream2 = list.parallelStream(); }

  2).通过Arrays中得静态方法stream()获取数组流

     /**
* 创建 Stream的四种方式
* 2. 通过Arrays中得静态方法stream()获取数组流
*/
@Test
public void test2 () { //2. 通过Arrays中得静态方法stream()获取数组流
IntStream stream = Arrays.stream(new int[]{3,5}); }

  3). 通过Stream类中得 of()静态方法获取流

     /**
* 创建 Stream的四种方式
* 3. 通过Stream类中得 of()静态方法获取流
*/
@Test
public void test3 () { //3. 通过Stream类中得 of()静态方法获取流
Stream<String> stream = Stream.of("4645", "huinnj"); }

  4). 创建无限流(迭代、生成)

 /**
* 创建 Stream的四种方式
* 4. 创建无限流(迭代、生成)
*/
@Test
public void test4 () { //4. 创建无限流
//迭代(需要传入一个种子,也就是起始值,然后传入一个一元操作)
Stream<Integer> stream1 = Stream.iterate(2, (x) -> x * 2); //生成(无限产生对象)
Stream<Double> stream2 = Stream.generate(() -> Math.random()); }

四、Stream 中间操作

  多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何得处理!而终止操作时一次性全部处理,称为‘延迟加载’

  1). 筛选与切片

    ①. filter —— 接收Lambda ,从流中排除某些元素。

     /**
* 筛选与切片
* filter —— 接收Lambda ,从流中排除某些元素。
*
*/
@Test
public void test5 () {
//内部迭代:在此过程中没有进行过迭代,由Stream api进行迭代
//中间操作:不会执行任何操作
Stream<Person> stream = list.stream().filter((e) -> {
System.out.println("Stream API 中间操作");
return e.getAge() > 30;
}); //终止操作:只有执行终止操作才会执行全部。即:延迟加载
stream.forEach(System.out :: println); }

  执行上面方法,得到下面结果。

Person [name=张三, sex=男, age=76]
Stream API 中间操作
Stream API 中间操作
Person [name=王五, sex=男, age=35]
Stream API 中间操作
Stream API 中间操作
Person [name=钱七, sex=男, age=56]
Stream API 中间操作
Person [name=翠花, sex=女, age=34]

  我们,在执行终止语句之后,一边迭代,一边打印,而我们并没有去迭代上面集合,其实这是内部迭代,由Stream API 完成。

  下面我们来看看外部迭代,也就是我们人为得迭代。

   @Test
public void test6 () {
//外部迭代
Iterator<Person> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
} }

    ②. limit —— 截断流,使其元素不超过给定数量。

     /**
* limit —— 截断流,使其元素不超过给定数量。
*/
@Test
public void test7 () {
//过滤之后取2个值
list.stream().filter((e) -> e.getAge() >30 ).
limit(2).forEach(System.out :: println); }

  在这里,我们可以配合其他得中间操作,并截断流,使我们可以取得相应个数得元素。而且在上面计算中,只要发现有2条符合条件得元素,则不会继续往下迭代数据,可以提高效率。

  2). 跳过元素

    skip(n),返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空,与limit(n)互补。

     /**
* skip(n)—— 跳过元素,返回一个扔掉了前n个元素的流。
* 若流中元素不足n个,则返回一个空,与limit(n)互补。
*/
@Test
public void test8 () {
//跳过前2个值
list.stream().skip(2).forEach(System.out :: println); }

  3).  筛选

    distinct 通过流所生成元素的hashCode()和equals()去除重复元素

     /**
* distinct —— 筛选,通过流所生成元素的hashCode()和equals()去除重复元素
*/
@Test
public void test9 () { list.stream().distinct().forEach(System.out :: println); }

  注意:distinct 需要实体中重写hashCode()和 equals()方法才可以使用

  4). 映射

    ① . map ,将元素转换成其他形式或者提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。

     /**
* map —— 映射 ,将元素转换成其他形式或者提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
*/
@Test
public void test10 () {
//将流中每一个元素都映射到map的函数中,每个元素执行这个函数,再返回
List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
list.stream().map((e) -> e.toUpperCase()).forEach(System.out::printf); //获取Person中的每一个人得名字name,再返回一个集合
List<String> names = this.list.stream().map(Person :: getName).
collect(Collectors.toList());
}

    ② . flatMap —— 接收一个函数作为参数,将流中的每个值都换成一个流,然后把所有流连接成一个流

     /**
* flatMap —— 接收一个函数作为参数,将流中的每个值都换成一个流,然后把所有流连接成一个流
*/
@Test
public void test11 () {
StreamAPI_Test s = new StreamAPI_Test();
List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
list.stream().flatMap((e) -> s.filterCharacter(e)).forEach(System.out::println); //如果使用map则需要这样写
list.stream().map((e) -> s.filterCharacter(e)).forEach((e) -> {
e.forEach(System.out::println);
});
} /**
* 将一个字符串转换为流
* @param str
* @return
*/
public Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for (Character ch : str.toCharArray()) {
list.add(ch);
}
return list.stream();
}

  其实map方法就相当于Collaction的add方法,如果add的是个集合得话就会变成二维数组,而flatMap 的话就相当于Collaction的addAll方法,参数如果是集合得话,只是将2个集合合并,而不是变成二维数组。

  5). 排序

     sorted有两种方法,一种是不传任何参数,叫自然排序,还有一种需要传Comparator 接口参数,叫做定制排序。

     /**
* sorted有两种方法,一种是不传任何参数,叫自然排序,还有一种需要传Comparator 接口参数,叫做定制排序。
*/
@Test
public void test12 () {
// 自然排序
List<Person> persons = list.stream().sorted().collect(Collectors.toList()); //定制排序
List<Person> persons1 = list.stream().sorted((e1, e2) -> {
if (e1.getAge() == e2.getAge()) {
return 0;
} else if (e1.getAge() > e2.getAge()) {
return 1;
} else {
return -1;
}
}).collect(Collectors.toList());
}

五、Stream 终止操作

  1). 查找与匹配

    首先我们先创建一个集合。

         List<Person> persons = Arrays.asList(
new Person("张三", "男", 76, Status.FREE),
new Person("李四", "女", 12, Status.BUSY),
new Person("王五", "男", 35, Status.BUSY),
new Person("赵六", "男", 3, Status.FREE),
new Person("钱七", "男", 56, Status.BUSY),
new Person("翠花", "女", 34, Status.VOCATION),
new Person("翠花", "女", 34, Status.FREE),
new Person("翠花", "女", 34, Status.VOCATION)
);

    ①. allMatch —— 检查是否匹配所有元素。

     /**
* allMatch —— 检查是否匹配所有元素。
* 判断所有状态是否都是FREE
*/
@Test
public void test13 () {
boolean b = persons.stream().allMatch((e) -> Status.FREE.equals(e.getStatus()));
System.out.println(b);
}

    ②. anyMatch —— 检查是否至少匹配所有元素。

     /**
* anyMatch —— 检查是否至少匹配所有元素。
* 判断是否有一个是FREE
*/
@Test
public void test14 () {
boolean b = persons.stream().anyMatch((e) -> Status.FREE.equals(e.getStatus()));
System.out.println(b);
}

    ③. noneMatch —— 检查是否没有匹配所有元素。

     /**
* noneMatch —— 检查是否没有匹配所有元素。
* 判断是否没有FREE
*/
@Test
public void test15 () {
boolean b = persons.stream().noneMatch((e) -> Status.FREE.equals(e.getStatus()));
System.out.println(b);
}

    ④. findFirst —— 返回第一个元素。

     /**
* findFirst —— 返回第一个元素。
*
*/
@Test
public void test16 () {
Optional<Person> person = persons.stream().findFirst();
System.out.println(person); person.orElse(new Person("王五", "男", 35, Status.BUSY));
}

  注意:上面findFirst 返回的是一个Optional的对像,他将我们的Person封装了一层,这是为了避免空指针。而且这个对象为我们提供了一个orElse方法,就是当我们得到的这个对象为空时,我们可以传入一个新得对象去替代它。

    ⑤. findAny —— 返回当前流中任意元素。

     /**
* findAny —— 返回当前流中任意元素。
*/
@Test
public void test17 () {
Optional<Person> person = persons.stream().findAny();
System.out.println(person); person.orElse(new Person("王五", "男", 35, Status.BUSY));
}

    ⑥. count —— 返回流中元素总个数。

     /**
* count —— 返回流中元素总个数。
*/
@Test
public void test18 () {
long count = persons.stream().count();
System.out.println(count); }

    ⑦. max —— 返回流中最大值。

     /**
* max —— 返回流中最大值。
*/
@Test
public void test18 () {
Optional<Person> person = persons.stream().max((e1, e2) -> Double.compare(e1.getAge(), e2.getAge()));
System.out.println(person); }

    ⑧. min —— 返回流中最小值。

     /**
* min —— 返回流中最小值。
*/
@Test
public void test20 () {
Optional<Person> person = persons.stream().min((e1, e2) -> Double.compare(e1.getAge(), e2.getAge()));
System.out.println(person); }

  2). 归约(可以将流中元素反复结合在一起,得到一个值)

    ①. reduce(T identitty,BinaryOperator)首先,需要传一个起始值,然后,传入的是一个二元运算。

     /**
* reduce(T identitty,BinaryOperator)首先,需要传一个起始值,然后,传入的是一个二元运算。
*/
@Test
public void test21 () {
List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = list.stream().reduce(0, (x, y) -> x + y);
}

    ②. reduce(BinaryOperator)此方法相对于上面方法来说,没有起始值,则有可能结果为空,所以返回的值会被封装到Optional中。

     /**
* reduce(BinaryOperator)此方法相对于上面方法来说,没有起始值,则有可能结果为空,所以返回的值会被封装到Optional中
*/
@Test
public void test22 () {
List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum = list.stream().reduce(Integer :: sum);
}

  备注:map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名。

  3). 收集collect(将流转换为其他形式。接收一个Collector接口得实现,用于给其他Stream中元素做汇总的方法)

    Collector接口中方法得实现决定了如何对流执行收集操作(如收集到List,Set,Map)。但是Collectors实用类提供了很多静态方法,可以方便地创建常见得收集器实例。

    ①. Collectors.toList() 将流转换成List

     /**
* Collectors.toList() 将流转换成List
*/
@Test
public void test23() {
List<String> names = this.list.stream().map(Person :: getName).collect(Collectors.toList());
}

    ②. Collectors.toSet()将流转换为Set

     /**
* Collectors.toSet() 将流转换成Set
*/
@Test
public void test24() {
Set<String> names = this.list.stream().map(Person :: getName).collect(Collectors.toSet());
}

    ③. Collectors.toCollection()将流转换为其他类型的集合

     /**
* Collectors.toCollection()将流转换为其他类型的集合
*/
@Test
public void test25() {
LinkedList<String> names = this.list.stream().map(Person :: getName).collect(Collectors.toCollection(LinkedList :: new));
}

    

    ④. Collectors.counting()  元素个数

     /**
* Collectors.counting() 总数
*/
@Test
public void test26() {
List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Long count = list.stream().collect(Collectors.counting());
}

    ⑤. Collectors.averagingDouble()、Collectors.averagingDouble()、Collectors.averagingLong() 平均数,这三个方法都可以求平均数,不同之处在于传入得参数类型不同,返回值都为Double

     /**
* Collectors.averagingInt() 、
* Collectors.averagingDouble()、
* Collectors.averagingLong() 平均数,
* 者三个方法都可以求平均数,不同之处在于传入得参数类型不同,
* 返回值都为Double
*/
@Test
public void test27() {
List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Double avg = list.stream().collect(Collectors.averagingInt((x) -> x));
}

    ⑥. Collectors.summingDouble()、Collectors.summingDouble()、Collectors.summingLong() 求和,不同之处在于传入得参数类型不同,返回值为Integer, Double, Long

     /**
* Collectors.summingInt() 、
* Collectors.summingDouble()、
* Collectors.summingLong() 求和,
* 者三个方法都可以求总数,不同之处在于传入得参数类型不同,
* 返回值为Integer, Double, Long
*/
@Test
public void test28() {
List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = list.stream().collect(Collectors.summingInt((x) -> x));
}

  ⑦. Collectors.maxBy() 求最大值

     /**
* Collectors.maxBy() 求最大值
*/
@Test
public void test29() {
List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> max = list.stream().collect(Collectors.maxBy((x, y) ->Integer.compare(x, y)));
}

  ⑧. Collectors.minBy() 求最小值

/**
* Collectors.minBy() 求最小值
*/
@Test
public void test29() {
List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> min = list.stream().collect(Collectors.minBy((x, y) ->Integer.compare(x, y)));
}

  ⑨. Collectors.groupingBy()分组 ,返回一个map

     /**
* Collectors.groupingBy()分组 ,返回一个map
*/
@Test
public void test30() {
Map<String, List<Person>> personMap = list.stream().collect(Collectors.groupingBy(Person :: getSex));
}

      Collectors.groupingBy()还可以实现多级分组

     /**
* Collectors.groupingBy()多级分组 ,返回一个map
*/
@Test
public void test31() {
Map<String, Map<Status, List<Person>>> personMap = list.stream().collect(Collectors.groupingBy(Person :: getSex, Collectors.groupingBy(Person :: getStatus)));
}

  ⑩. Collectors.partitioningBy() 分区,参数中传一个函数,返回true,和false 分成两个区

     /**
* Collectors.partitioningBy() 分区,参数中传一个函数,返回true,和false 分成两个区
*/
@Test
public void test32() {
Map<Boolean, List<Person>> personMap = list.stream().collect(Collectors.partitioningBy((x) -> x.getAge() > 30));
}

  上面就是Stream的一些基本操作,只要勤加练习就可以灵活使用,而且效率大大提高。