08.Stream API 15个常用方法【重要】

185 阅读9分钟

08.Stream API 15个常用方法【重要】

集合处理数据的弊端

当我们需要对集合的元素进行操作的时候,除了必须的添加、删除、获取外,最典型的就是集合的遍历。

下面代码针对于我们不同的需求,总是会一次次的循环遍历。

【案例】传统方式实现:查询集合中符合条件的元素并遍历出来

package com.lzh;

import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @Author:kaiyang.cui
 * @Package:com.lzh
 * @Project:jdk8
 * @name:StreamAPITests
 * @Date:2023/4/3 下午4:31
 * @Filename:StreamAPITests
 * @Description:StreamAPITests
 * @Version:1.0
 */
public class StreamAPITests {

    @Test
    public void test1() {

        // 定义一个List集合
        List<String> list = Arrays.asList("安欣", "高启强", "高启盛","小兰","高大");

        List<String> listSave = new ArrayList<>();
        // 获取所有 姓高的信息
        for (String s : list) {
            if(s.startsWith("高")){
                listSave.add(s);
            }
        }

        //2. 在listSave集合中再筛选获取名称长度为2的用户
        List<String> listSave2 = new ArrayList<>();
        for (String s : listSave) {
            if(s.length() == 2){
                listSave2.add(s);
            }
        }

        // 3. 输出所有符合条件的用户信息
        for (String s : listSave2) {
            System.out.println(s);
        }

    }

}

Result:

高大

使用JDK Stream API优雅的改写

为了解决新增的需求,使用传统的集合操作会一遍一遍循环,这时我们希望有更加高效的处理方式,JDK8 Stream应运而生。

【案例】JDK8 Stream API实现:查询集合中符合条件的元素并遍历出来

@Test
@DisplayName("JDK8 Stream API实现:查询集合中符合条件的元素并遍历出来")
public void test2() {

    // 定义一个List集合
    List<String> list = Arrays.asList("安欣", "高启强", "高启盛","小兰","高大");

    list.stream()
            .filter(s -> s.startsWith("高"))
            .filter(s -> s.length() == 2)
            .forEach(s -> System.out.println(s));
}
高大

【案例】JDK8 Stream API实现+ 方法引用版:查询集合中符合条件的元素并遍历出来

@Test
@DisplayName("JDK8 Stream API实现+ 方法引用版:查询集合中符合条件的元素并遍历出来")
public void test3() {

    // 定义一个List集合
    List<String> list = Arrays.asList("安欣", "高启强", "高启盛","小兰","高大");

    list.stream()
            .filter(s -> s.startsWith("高"))
            .filter(s -> s.length() == 2)
            .forEach(System.out::println);
}

上面的StreamAPI代码的含义:获取流-过滤高,过滤长度,然后逐一打印。

StreamAPI核心思想

image.png

image.png

image.png

Stream API 能让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去除重复、统计、匹配和规约。

  • 根据Collection获取
  • 通过Stream的of方法

根据Collection获取

Collection 集合获取流。

首先,java.util.Collection接口中加入了default方法 stream,也就是说Collection接口下的所有的实现都可以通过stream方法来获取。 image.png

@Test
@DisplayName("根据Collection获取Stream流")
public void test4() {
    List<String> list = new ArrayList<>();
    list.stream();

    Set<String> set = new HashSet<>();
    set.stream();

    Vector<String> vector = new Vector<>();
    vector.stream();

}

但是map接口并没有实现Collection接口,这时怎么办?

这时我们可以根据Map获取对应的key value的集合。

@Test
@DisplayName("Map获取流的方式")
public void test5() {
   Map<String, Object> map = new HashMap<>();
    Stream<String> stream = map.keySet().stream();

    Stream<Object> stream1 = map.values().stream();

    Stream<Map.Entry<String, Object>> stream2 = map.entrySet().stream();

}

通过Stream的of方法

数组获取流。

我们在实际开发中不可避免的还是会操作到数组中的数据,由于数组对象不可能添加默认方法,所以Stream接口中提供了静态方法of

@Test
@DisplayName("将一个字符串数组转换成一个Stram流")
public void test6() {
    Stream<String> a1 = Stream.of("a1", "b1", "c1", "d1");


    String[] arr1 = {"a1", "b1", "c1", "d1"};
    Stream<String> arr11 = Stream.of(arr1);

    Integer[] arr2 = {1,2,3,4,5,6,7,8};
    Stream<Integer> arr21 = Stream.of(arr2);
    arr21.forEach(System.out::println);

    // 基本数据类型是不可以的,不可以得到想要的遍历结果,应该是用int的包装类Integer
    int[] arr3 = {1,2,3,4,5,6,7,8};
    Stream.of(arr3).forEach(System.out::println);
}

Result:

1
2
3
4
5
6
7
8
[I@77846d2c

Stream 中常用的方法

image.png

**终结方法:**返回值不再是Stream类型的方法,不再支持链式调用。如:count和forEach。

**非终结方法:**返回值类型仍然是Stream类型的方法,支持链式调用。

注意事项

  1. Stream只能操作一次
  2. Stream方法返回的是新的流
  3. Stream不调用终结方法,中间的操作不会执行

【案例】演示:必须要有终结方法才可以完成流的操作

@Test
@DisplayName("演示:必须要有终结方法才可以完成流的操作")
public void test7() {
    Stream<String> stream = Stream.of("a1", "a2", "a2");
    stream.filter(s -> {
        System.out.println("--------------------------------");
        return s.contains("a");
    }).forEach(System.out::println);
}

Stream所有的方法

image.png

1. forEach方法

forEach用来遍历流中的数据

【案例】演示:forEach方法

@Test
@DisplayName("演示:forEach方法")
public void test8() {
    String str[] = {"a1","a2","a3"};
    Stream.of(str).forEach(System.out::println);

}

2.count方法

Stream流中的count方法用来统计其中的元素的个数。

image.png

count方法返回值是long类型的,代表元素的个数。

【案例】演示:count方法

@Test
@DisplayName("演示:count方法")
public void test9() {
    String str[] = {"a1","a2","a3"};
    long count = Stream.of(str).count();
    System.out.println("元素个数是count = " + count);
}

Result:

元素个数是count = 3

3. filter方法

filter方法的作用是用来过滤数据的,返回符合条件的数据。

image.png

filter方法的返回值是一个字集Stream流,返回的是新的流,不调用终结方法,中间的操作不会执行!!

该接口接收一个predicate函数式参数,作为筛选条件。

image.png

【案例】演示:filter方法

@Test
@DisplayName("演示:filter方法")
public void test10() {
    String str[] = {"a1","a2","a3","b1"};
    Stream.of(str).filter(s->s.contains("1")).forEach(System.out::println);
}

Result:

a1
b1

4.limit方法

limit方法可以对流进行截取处理,支持前n个数据。

参数是一个long类型的数值,如果集合当前长度大于参数就进行截取,否则不操作,报错

image.png

image.png

【案例】演示:limit方法截取数组中的元素的前3个

@Test
@DisplayName("演示:limit方法截取数组中的元素的前3个")
public void test11() {
    String str[] = {"a1","a2","a3","b1"};
    Stream.of(str).limit(3).forEach(System.out::println);
}

5.skip方法

image.png

如果希望跳过前面几个元素,可以使用skip方法获取一个截取之后的新流。

image.png

【案例】演示:skip方法跳过数组中的元素前3个

@Test
@DisplayName("演示:skip方法跳过数组中的元素前3个")
public void test12() {
    String str[] = {"a1","a2","a3","b1"};
    Stream.of(str).skip(3).forEach(System.out::println);
}

Result:

b1

6.map方法

image.png

如果我们需要将流中的元素映射到另一个流中,可以使用map方法。

image.png

该接口需要一个Function函数式接口参数,可以将当前流中的T类型的数据转换给R类型的数据。

【案例】演示:map方法将字符串转换为整形

@Test
@DisplayName("演示:map方法将字符串转换为整形")
public void test13() {
    String str[] = {"1","2","3","4"};
    Stream.of(str).map(msg -> Integer.parseInt(msg)).forEach(System.out::println);
}

Result:

1
2
3
4

改进方法引用:类::静态方法

@Test
@DisplayName("演示:map方法将字符串转换为整形")
public void test13() {
    String str[] = {"1","2","3","4"};
    Stream.of(str).map(Integer::parseInt).forEach(System.out::println);
}

7.sorted方法

image.png

如果需要将数据排序,可以使用sorted方法。

【案例】演示:sorted方法升序排序(自然排序)

@Test
@DisplayName("演示:sorted方法升序排序")
public void test13() {
    String str[] = {"3","4","3","1"};
    Stream.of(str).map(Integer::parseInt).sorted().forEach(System.out::println);
}

演示:sorted方法降序排序

根据比较器指定排序规则。

@Test
@DisplayName("演示:sorted方法降序排序")
public void test14() {
    String str[] = {"3","4","3","1"};
    Stream.of(str).map(Integer::parseInt).sorted().sorted((o1,o2)->{return o2-o1;}).forEach(System.out::println);
}

Result:

4
3
3
1

8. distinct方法

如果要去掉重复数据,可以使用distinct方法

image.png

image.png

【案例】演示:distinct方法去重

@Test
@DisplayName("演示:distinct方法去重")
public void test15() {
    String str[] = {"3","4","3","1"};
    Stream.of(str).map(Integer::parseInt).distinct().forEach(System.out::println);
}

Result:

3
4
1

Stream 流中的distinct方法对于基本的数据类型是可以直接去重的,但是对于自定义类型,我们需要重写equals和hashCode方法。

【案例】演示:distinct方法去重-自定义类型

@Test
@DisplayName("演示:distinct方法去重-自定义类型")
public void test16() {
    Stream.of(
            new Person("张三",18),
            new Person("张三",18),
            new Person("李四",18),
            new Person("张三",19),
            new Person("王五",18)

    ).distinct().forEach(System.out::println);
}

Result:

Person(name=张三, age=18)
Person(name=李四, age=18)
Person(name=张三, age=19)
Person(name=王五, age=18)

9.match方法

如果需要判断数据是否匹配指定的条件,可以使用match相关的方法。

match相关方法都是终结方法

anyMatch---元素是否有任意一个满足条件

image.png

@Test
@DisplayName("演示:anyMatch")
public void test18() {
    String str[] = {"1", "2", "3", "4", "5", "6","0","-1"};
    boolean b = Stream.of(str).map(Integer::parseInt).anyMatch(s -> s > 0);
    System.out.println("我只希望数组中有一个大于0就行 : " + b);
}
我只希望数组中有一个大于0就行 : true

allMatch---元素是否都满足条件

image.png

allMatch是终结方法。

【案例】演示:allMatch

@Test
@DisplayName("演示:allMatch")
public void test18() {
    String str[] = {"1", "2", "3", "4", "5", "6","0","-1"};
    boolean b = Stream.of(str).map(Integer::parseInt).allMatch(s -> s > 0);
    System.out.println("数组都大于0吗? = " + b);
}
数组都大于0吗? = false

noneMatch---元素是否都不满足条件

image.png

【案例】演示:noneMatch

如果数组中所有的元素都小于-2为true,有一个不小于-2 就为false

@Test
@DisplayName("演示:noneMatch")
public void test18() {
    String str[] = {"1", "2", "3", "4", "5", "6","0","-1"};
    boolean b = Stream.of(str).map(Integer::parseInt).noneMatch(s -> s < -2);
    System.out.println(b);
}

Result:

true

10.find方法

image.png

如果我们需要,找到某些数据,可以使用find方法来实现。

findFirst - findAny

【案例】演示:findFirst---findAny方法

@Test
@DisplayName("演示:findFirst---findAny方法")
public void test19() {
    String str[] = {"3", "4", "5", "6","0","-1"};
    Integer integer = Stream.of(str).map(Integer::parseInt).filter(x -> x % 2 == 0).findFirst().get();
    System.out.println("integer = " + integer);

    Integer integer2 = Stream.of(str).map(Integer::parseInt).filter(x -> x % 2 == 0).findAny().get();

    System.out.println("integer2 = " + integer2);

}

11.max和min方法

获取最大值和最小值

image.png

image.png

【案例】演示:max-min方法

@Test
@DisplayName("演示:max-min方法")
public void test20() {
    String str[] = {"3", "4", "5", "6","0","-1"};
    Integer max = Stream.of(str).map(Integer::parseInt).max((o1, o2) -> o1 - o2).get();
    System.out.println("max = " + max);

    Integer min = Stream.of(str).map(Integer::parseInt).max((o1, o2) -> o2 - o1).get();
    System.out.println("min = " + min);
}

12.reduce方法

image.png

如果需要将所有数据归纳得到一个数据,可以使用reduce方法。

1 T reduce(T identity, BinaryOperator<T> accumulator)

image.png

image.png

image.png

【案例】:演示:T reduce(T identity, BinaryOperator<T> accumulator)

identity是初始化默认值,第一次的时候会将默认值赋值给x,之后每次会将上一次的操作结果赋值给x,y就是每次从原始数组中获取的元素。

@Test
@DisplayName("演示:T reduce(T identity, BinaryOperator<T> accumulator);求和")
public void test21() {
    String str[] = {"1", "2", "3", "4"};
    Integer reduce = Stream.of(str).map(Integer::parseInt).reduce(0, (x, y) -> {
        System.out.println("x =" + x +",y =" + y);
        return x + y;
    });
    System.out.println("reduce = " + reduce);

}

Result:

x =0,y =1
x =1,y =2
x =3,y =3
x =6,y =4
reduce = 10

【案例】获取最大值

@Test
@DisplayName("演示:T reduce(T identity, BinaryOperator<T> accumulator); 求数组中最大值")
public void test22() {
    String str[] = {"1", "2", "3", "4"};
    Integer max = Stream.of(str).map(Integer::parseInt).reduce(0, (x, y) -> {
        return x>y ? x:y;
    });
    System.out.println("max = " + max);

}
max = 4

image.png

image.png

13.map和reduce组合使用

在实际开发中,我们经常会将map和reduce一块来使用。

@Test
@DisplayName("演示:T reduce(T identity, BinaryOperator<T> accumulator); 求数组中age年龄的总和")
public void test23() {
    Integer reduce = Stream.of(
            new Person("张三", 18),
            new Person("张三", 18),
            new Person("李四", 18),
            new Person("张三", 19),
            new Person("王五", 18)

    ).map(p -> p.getAge()).reduce(0, (x, y) -> x + y);

    System.out.println("reduce = " + reduce);
}
reduce = 91

【案例】演示:T reduce(T identity, BinaryOperator<T> accumulator); 方法引用版:求数组中age年龄的总和

咱就是说优雅用不过时!!!!!!!!!

@Test
@DisplayName("演示:T reduce(T identity, BinaryOperator<T> accumulator); 方法引用版:求数组中age年龄的总和")
public void test23() {
    Integer reduce = Stream.of(
            new Person("张三", 18),
            new Person("张三", 18),
            new Person("李四", 18),
            new Person("张三", 19),
            new Person("王五", 18)

    ).map(Person::getAge).reduce(0, Integer::sum);

    System.out.println("reduce = " + reduce);
}

方法引用版:求数组中age年龄最大值

@Test
@DisplayName("演示:T 方法引用版:求数组中age年龄最大值")
public void test24() {
    Integer ageMax = Stream.of(
            new Person("张三", 18),
            new Person("张三", 18),
            new Person("李四", 18),
            new Person("张三", 30),
            new Person("王五", 18)

    ).map(Person::getAge).reduce(0, Integer::max);

    System.out.println("ageMax = " + ageMax);
}

map 实现数据类型的转换,符合reduce对数据的要求。 reduce实现数据的处理。

@Test
@DisplayName("演示:方法引用版:统计字符a 出现的次数")
public void test25() {
    String str[] = {"a","b","c","d","e","f","a"};
    Integer result = Stream.of(str).map(ch -> "a".equals(ch) ? 1 : 0).reduce(0, Integer::sum);
    System.out.println("a出现的次数是 = " + result);
}

Result:

a出现的次数是 = 2

14.mapToInt方法

image.png

如果需要将Stream中的Integer类型转换成int类型,可以使用mapToInt方法来实现

@Test
@DisplayName("演示:mapToInt方法")
public void test26() {
    Integer arr[] = {1,2,3,4,5};
    Stream.of(arr).filter(x -> x>0).forEach(System.out::println);

    System.out.println("-----------");

    // 为了提高程序代码的效率,我们可以先将流中Integer数据转换为int数据,然后再操作
    IntStream intStream = Stream.of(arr).mapToInt(Integer::intValue);
    intStream.filter(i->i>3).forEach(System.out::println);
}
1
2
3
4
5
-----------
4
5

15.concat方法

如果有两个流,希望合并成一个流,可以使用Stream流的静态方法。

image.png

@Test
@DisplayName("演示:concat方法合并成一个流")
public void test27() {
    Stream<String> a = Stream.of("a", "b", "c");


    Stream<String> b = Stream.of( "d", "e", "f");

    Stream.concat(a, b).forEach(System.out::println);

}
a
b
c
d
e
f