Java8—Stream

1,710 阅读9分钟

这是我参与更文挑战的第 2 天,活动详情查看: 更文挑战

日积月累,水滴石穿 😄

Lambda 表达式是 Stream 的基础,如果不懂 Lambda 表达式,建议先学习 Lambda 表达式,传送门:Java8—Lambda表达式

什么是 Stream

Stream 翻译称为 “流”,是 Java8 的新特性之一。 Stream 将要处理的元素看作是流,这时可以借助 Stream API 对流中的元素进行中间操作,比如:筛选、排序、排序等。

特点

  • 不是数据结构,不会保存数据,只会操作数据。
  • 不会改变数据源,它会将操作后的数据保存到另外一个对象中。
  • 延迟执行,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算。

三个步骤

  • 1、创建Stream: 从数据源中获取一个流,这个数据源可以是集合、数组。
  • 2、中间操作:操作链,可以对流进行数据处理。
  • 3、终止操作:执行终止操作,才会执行中间操作的操作链,终端操作结束后流无法再次使用,会产生一个新的结果。

image.png

接下来就介绍一下三个步骤的基本用法。


具体用法

1、流的常用创建方法

  • 1.1 使用 Collection 系列集合提供的 stream() parallelStream() 方法
public static void main(String[] args) {
    // 使用Collection系列集合提供的 stream() 和 parallelStream() 方法
    ArrayList<String> list = new ArrayList<>();
    //返回一个顺序流
    Stream<String> stream = list.stream();
    //把顺序流转换成并行流
    Stream<String> parallel = list.stream().parallel();
    //返回一个并行流
    Stream<String> stringStream = list.parallelStream();

}
  • 1.2 使用 Arrays的静态方法 stream() 可以获取数组流
 String[] strs = new String[10];
 Stream<String> stream2 = Arrays.stream(strs);
  • 1.3 调用Stream类静态方法 of()
Stream<String> aa = Stream.of("aa", "bbb", "c");
  • 1.4 使用静态方法 Stream.iterate() Stream.generate()创建无限流
//迭代
Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2);
//0 2 4 6 8
iterate.limit(5).forEach(System.out::println);

//生成
Stream<Double> generate = Stream.generate(() -> Math.random());
//84 28 71
generate.limit(3).forEach(System.out::println);

可以看到创建 Stream 流的方式有很多。但不论是哪种创建方式,Stream 不会保存数据,它只会对数据进行计算。

2、流的中间操作

中间操作可以分为两大类:

  • 有状态操作:需要等上一步操作完之后,拿到全部元素后才可操作,如 sorted
  • 无状态操作:该操作的数据不受上一步操作的影响,如 filter map

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,不然中间操作不会执行任何处理!

2.1-筛选与切片

image.png

List<Integer> integers = Arrays.asList(
            1,2,3,4,5,6,7,8,9,10,2,5,7,9,17
    );
    //流的中间操作
    @Test
    public void test1() {
        /**
         * 1.筛选与切片
         *  filter:过滤流中的某些元素
         *  limit(n):获取n个元素
         *  skip(n):跳过n元素,若流中元素不足n个,则返回一个空流,配合limit(n)可实现分页
         *  distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素
         */
        // 创建流
        Stream<Integer> stream = integers.stream();
        // 中间操作
        Stream<Integer> integersStream = stream
                .filter((t) -> t > 5)   //6 7 8 9 10 7 9 17
                .distinct()  //6 7 8 9 10 17
                .skip(2)   //8 9 10 17
                .limit(3);  //8 9 10
        integersStream.forEach(System.out::println);
    }

2.2 映射

image.png

1、map

  • 1、将每个元素转换为大写
@Test
public void test2() {
    /**
     * 1.映 射
     *  map(Function f):接收一个函数作为参数,该函数会被应用到每个
     * 元素上,并将其映射成一个新的元素。
     */
    // 将每个元素转换为大写
   List<String> list = Arrays.asList("a,a=","b,b=","cc=","d,d=");
List<String> collect = list.stream()
        .map(str -> str.toUpperCase()).collect(Collectors.toList());
    // str -> str.toUpperCase() 可以简写为 String::toUpperCase
System.out.println(collect);

//结果:[A,A=, B,B=, CC=, D,D=]
    }
  • 2、提取员工名称
 List<Employee> employees = Arrays.asList(
            new Employee("张三",18,9999.99),
            new Employee("李四",38,5555.55),
            new Employee("王五",50,6666.66),
            new Employee("赵六",16,3333.33),
            new Employee("田七",8,7777.77)
    );

 //提取员工名称
List<String> collect1 = employees.stream()
                //.map(employee -> employee.getName())
                //类::实例方法名
                .map(Employee::getName)
                .collect(Collectors.toList());
        System.out.println(collect1);

结果:[张三, 李四, 王五, 赵六, 田七]

2、mapToInt

@Test
public void test3() {
    /**
     * mapToInt((ToIntFunction f)
     * 接收一个函数作为参数,该函数会被应用到每个元素上,
     * 产生一个新的 IntStream
     */
//输出每个元素的长度
    List<String> list = Arrays.asList("a,a","b,b","cc","d,d");
    IntStream intStream = list.stream()
            .mapToInt(str -> {
                return str.length();
            });

    intStream.forEach(System.out::println);
}
结果:
3
3
2
3

mapToDoublemapToLong 其余两种就不在介绍。

3、flatMap

@Test
public void test4() {
    /**
     * flatMap(Function f)
     * 接收一个函数作为参数,将流中的每个值都换成另一个流,
     * 然后把所有流连接成一个流
     */
    // 将每个元素根据,进行分割
 List<String> list = Arrays.asList("a,a=","b,b=","cc=","d,d=");
    Stream<String> stringStream = list.stream()
            .flatMap(str -> {
                String[] split = str.split(",");
                Stream<String> stream = Arrays.stream(split);
                return stream;
            });
    List<String> collect = stringStream.collect(Collectors.toList());
    System.out.println(collect);
}
结果:[a, a=, b, b=, cc=, d, d=]

2.3 排序

image.png

1、sorted

@Test
public void test5() {
    /**
     * sorted()
     * 产生一个新流,其中按自然顺序排序
     */
    List<String> list = Arrays.asList("bb","cc","ee","aa");
    list.stream().sorted().forEach(System.out::println);
}

结果:
aa
bb
cc
ee

2、sorted(Comparator com)

List<Employee> employees = Arrays.asList(
        new Employee("张三",18,9999.99),
        new Employee("李四",38,5555.55),
        new Employee("王五",50,6666.66),
        new Employee("赵六",15,3333.33),
        new Employee("田七",18,7777.77)
);

 @Test
public void test6() {
    /**
     * sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
     */
    //如果年龄一致,就按照姓名排序,如果不一致,就按照年龄升序排序
    employees.stream().sorted((e1,e2)->{
        if(e1.getAge().equals(e2.getAge())){
            return e1.getName().compareTo(e2.getName());
        }else{
            return e1.getAge().compareTo(e2.getAge());
        }
    }).forEach(System.out::println);
}
结果:
name='赵六', age=15, salary=3333.33
name='张三', age=18, salary=9999.99
name='田七', age=18, salary=7777.77
name='李四', age=38, salary=5555.55
name='王五', age=50, salary=6666.66

3、流的终止操作

  • 终端操作会从流水线中产生结果。其结果可以是任何不是流的值,例 如:List、Integer,String。
  • 流进行了终止操作后,不能再次使用。

1、匹配和查找

image.png

allMatch

判断数据流中所有元素是否与 aa 相等,全部相等则返回true

List<String> list = Arrays.asList("aa","bb","cc","dd");

@Test
public void test1(){
    boolean aa = list.stream()
            .allMatch(obj -> obj.equals("aa"));
    System.out.println(aa);
}
结果:false

接下来将 List 的值全部修改为 “aa”

 List<String> list = Arrays.asList("aa","aa","aa","aa");

@Test
public void test1(){
    boolean aa = list.stream()
            .allMatch(obj -> obj.equals("aa"));
    System.out.println(aa);
}
结果:true
anyMatch

判断数据流中所有元素是否有一个与 aa 相等,相等则返回 true

 List<String> list = Arrays.asList("aa","bb","cc","dd");
 @Test
public void test2(){
    boolean aa = list.stream()
            .anyMatch(obj -> obj.equals("aa"));
    System.out.println(aa);
}
结果:true
noneMatch

判断数据流中所有元素是否全部与 aa1 相等,一个都没有匹配成功则返回 true。

List<String> list = Arrays.asList("aa","bb","cc","dd");
 @Test
public void test3(){
    boolean aa = list.stream()
            .noneMatch(obj -> obj.equals("aa1"));
    System.out.println(aa);
}
结果:true
findFirst

返回第一个元素

List<String> list = Arrays.asList("aa","bb","cc","dd");
 @Test
public void test4(){
    Optional<String> first = list.stream()
            .findFirst();
    System.out.println(first.get());
}
结果:aa
findAny

返回当前流中的任意元素

    List<String> list = Arrays.asList("bb","aa","cc","dd","d","c","da");
 @Test
    public void test5(){
        Optional<String> first = list.stream()
                .findAny();
        System.out.println(first.get());
    }
结果:bb

由于是顺序流所以每次返回的都是第一个元素。使用并行流:

@Test
    public void test5(){
        Optional<String> first = list.parallelStream()
                .findAny();
        System.out.println(first.get());
    }
结果:d
count

返回流中元素总数

List<Integer> numberList = Arrays.asList(1,23,33,4,5,66,21);
@Test
public void test6(){
    long count = numberList.stream()
            .count();
    System.out.println(count);
}
结果:7
max(Comparator c)

返回流中最大值

@Test
public void test7(){
    Optional<Integer> max = numberList.stream()
            .max(Integer::compareTo);
    System.out.println(max.get());
}
结果:66
min(Comparator c)

返回流中最小值

 @Test
public void test8(){
    Optional<Integer> max = numberList.stream()
            .min(Integer::compareTo);
    System.out.println(max.get());
}
结果:1
forEach(Consumer c)

内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代)。

@Test
public void test5() {
    /**
     * sorted()
     * 产生一个新流,其中按自然顺序排序
     */
    List<String> list = Arrays.asList("bb","cc","ee","aa");
    list.stream().sorted().forEach(System.out::println);

}

4、规约

  • T reduce(T i, BinaryOperator<T> a):可以将流中元素反复结合起来,得到一个值。返回 T
 List<Integer> numberList2 = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    @Test
    public void test9(){
        Integer reduce = numberList2.stream()
                .reduce(0, (x1, x2) -> x1 + x2);
        System.out.println(reduce);
    }
结果:55

第一个参数是起始值,如果起始值为10呢?

 @Test
    public void test9(){
        Integer reduce = numberList2.stream()
                .reduce(10, (x1, x2) -> x1 + x2);
        System.out.println(reduce);
    }
结果:65

第一次执行时,会将 i(也就是 10 ) 的值作为 a 函数的第一个参数,第二个参数为流中元素的第一个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第二个元素;依次类推。

  • Optional<T> reduce(BinaryOperator<T> a):可以将流中元素反复结合起来,得到一个值,返回 Optional<T>
@Test
public void test10(){
    Optional<Integer> reduce = numberList2.stream()
            .reduce(Integer::sum);
    System.out.println(reduce.get());

结果:55
}

流程与第一个方法是一致的,只是第一次执行时,第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素。

5、收集

image.png

Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map中)。 另外, Collectors 类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表: image.png image.png

toList

  • 获取到员工的姓名并放入List集合中
 List<Employee> employees = Arrays.asList(
            new Employee("张三",18,9999.99),
            new Employee("李四",38,5555.55),
            new Employee("王五",50,6666.66),
            new Employee("赵六",15,3333.33),
            new Employee("田七",18,7777.77)
    );
    
    @Test
    public void test14(){
        List<String> collect = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toList());
        collect.forEach(System.out::println);
    }
结果:
张三
李四
王五
赵六
田七

toCollection

  • 获取到员工的姓名并放入 HashSet集合中
  @Test
    public void test15(){
        HashSet<String> hs = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toCollection(HashSet::new));
        hs.forEach(System.out::println);
    }

counting、summingDouble、averagingDouble、maxBy

分别为:计数、求和、平均值、最值

@Test
    public void test16(){
        //元素的个数
        Long l = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.counting());
        System.out.println("个数" + l);
        //求和
        Double sum = employees.stream()
                .collect(Collectors.summingDouble(Employee::getAge));
        System.out.println("求和" + sum);
        //平均值
        Double avg = employees.stream()
                .collect(Collectors.averagingDouble(Employee::getAge));
        System.out.println("平均值" + avg);
        //获得年龄最大的员工信息
        Optional<Employee> collect = employees.stream()
                .collect(Collectors.maxBy((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())));
        System.out.println("获得年龄最大的员工信息" + collect.get());
    }
结果:
个数5
求和139.0
平均值27.8
获得年龄最大的员工信息name='王五', age=50, salary=6666.66

summarizingInt

收集流中 Integer属性的统计值。如:平均值

    @Test
    public void test19(){
        IntSummaryStatistics collect = employees.stream()
                .collect(Collectors.summarizingInt(Employee::getAge));
        System.out.println("个数" + collect.getCount());
        System.out.println("求和" + collect.getSum());
        System.out.println("最大值" + collect.getMax());
        System.out.println("平均值" + collect.getAverage());
        System.out.println("最小值" + collect.getMin());
    }
结果:
个数5
求和139
最大值50
平均值27.8
最小值15

groupingBy

将 stream 根据某个值分为多个Map

//根据年龄进行分组
@Test
public void test17(){
   Map<Integer, List<Employee>> collect = employees.stream()
           .collect(Collectors.groupingBy(Employee::getAge));
           
   for(Map.Entry<Integer,List<Employee>> map : collect.entrySet()){
       Integer key = map.getKey();
       List<Employee> value = map.getValue();
       for (Employee employee : value) {
           System.out.println("key=" +key + ",value = "+employee);
       }
   }
}
结果:
key=50,value = name='王五', age=50, salary=6666.66
key=18,value = name='张三', age=18, salary=9999.99
key=18,value = name='田七', age=18, salary=7777.77
key=38,value = name='李四', age=38, salary=5555.55
key=15,value = name='赵六', age=15, salary=3333.33
  • partitioningBy:将 stream 按条件分为两个 Map,truefalse,例如:薪资是否大于7000
@Test
    public void test18(){
        Map<Boolean, List<Employee>> collect = employees.stream()
                .collect(Collectors.partitioningBy(e -> e.getSalary() > 7000));
                
        for(Map.Entry<Boolean,List<Employee>> map : collect.entrySet()){
            Boolean key = map.getKey();
            List<Employee> value = map.getValue();
            for (Employee employee : value) {
                System.out.println("key=" +key + ",value = "+employee);
            }
        }
    }
结果:
key=false,value = name='李四', age=38, salary=5555.55
key=false,value = name='王五', age=50, salary=6666.66
key=false,value = name='赵六', age=15, salary=3333.33
key=true,value = name='张三', age=18, salary=9999.99
key=true,value = name='田七', age=18, salary=7777.77

joining

连接流中每个字符串

 @Test
    public void test20(){
        String collect = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.joining(",","这些人:","他们好棒"));
        System.out.println("str:" + collect);
    }
结果:
str:这些人:张三,李四,王五,赵六,田七他们好棒
  • 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。