JDK版本特性(二)StreamAPI

发布时间 2023-12-18 13:10:28作者: Tod4

Stream API


1 概述

  • Stream是java8中处理集合的关键抽象概念
  • 它可以指定对集合进行的操作,比如执行肥非常复杂的查找、过滤和映射等操作
  • 还可以使用Steam API来进行并行操作

2 Steam实例化

2.1 集合创建

​ 实现自接口collection.stream()

    @Test
    public void genByCollection() {
        var list = new ArrayList<Integer>();
        Stream<Integer> stream = list.stream();
    }
2.2 数组创建

​ 来自Arrays工具类的stream方法

    @Test
    public void genByArray() {
        var arr = new int[]{1, 2, 3};
        IntStream stream = Arrays.stream(arr);

        Stream<String> stream2 = Arrays.stream(new String[]{"1", "2"});

        var es = new Employee[3];
        es[0] = new Employee();
        es[1] = new Employee();
        es[2] = new Employee();

        Stream<Employee> stream1 = Arrays.stream(es);
        
    }
2.3 Stream创建
@Test
public void genByStream() {
    Stream<Integer> integerStream = Stream.of(4, 5, 6);
}
2.4 无限流
@Test
public void test() {
    // 输出前十个偶数
    Stream.iterate(0, t -> t + 2)
            // 中间操作
            .limit(10)
            // 终止操作
            .forEach(System.out::println);

    // 生成5个随机数
    // 提供一个supplier接口
    Stream.generate(Math::random)
            // 中间操作
            .limit(5)
            // 终止操作
            .forEach(System.out::println);
}

3 Stream中间操作

3.1 常见中间流操作
  • 过滤流 filter:

  • 截断流 limit:

  • 跳跃流 skip:

  • 筛选流 distinct:去除重复元素,根据对象的HashCode

    流经过终止操作后就会被关闭,不能够再回到中间操作

/**
 * 中间操作测试
 */
@Test
public void test1() {
    var list = EmployeeData.getEmployees();
    // 过滤流
    list.stream()
            .filter(e -> e.getAge() % 2 == 1)
            .forEach(System.out::println);

    // 截断流
    list.stream()
            .limit(4)
            .forEach(System.out::println);

    // 跳跃流
    list.stream()
            .skip(3)
            .forEach(System.out::println);

    list.add(new Employee("1", 1));
    list.add(new Employee("1", 1));
    list.add(new Employee("1", 1));
    
    // 筛选流
    list.stream().distinct()
            .forEach(System.out::println);

}
3.2 映射
  • map(Function f):接收一个函数作为参数,将流中的每个值都经过该函数处理后转化为一个流,然后嵌套到最外层的流里面

            var list = Arrays.asList("aa", "bb", "cc");
    		//aa
    		//bb
    		//cc
            Stream<Stream<Character>> characterStream =
                    list.stream().map(StreamMapTest::fromStringToStream);
    
  • flatMap(Function f):接收一个函数作为参数,将流中的每个值都转化为一个流,然后把所有的流连接成一个流,类似于对流的一个扁平(flat)处理

            characterStream.forEach(s -> {
                s.forEach(System.out::print);
                System.out.println();
            });
    		
            Stream<Character> flatStream
                    = list.stream().flatMap(StreamMapTest::fromStringToStream);
            // aabbcc
    		flatStream.forEach(System.out::print);
    
  • mapToDouble(Function f):接收一个函数作为参数,将流中的每个值都处理转化为一个Double流

  • mapToInt(Function f):接收一个函数作为参数,将流中的每个值都处理转化为一个Int流

  • mapToLong(Function f):接收一个函数作为参数,将流中的每个值都处理转化为一个Long流

4 终止操作

4.1 match & find 匹配与查找
  • allMatch:true | false

  • anyMatch:true | false

  • noneMatch:true | false

  • count:返回流的元素的个数

  • findFirst:返回Optional(防止空指针)

  • findAny:返回Optional(防止空指针)

  • max:返回Optional(防止空指针)

  • min:返回Optional(防止空指针)

        @Test
        public void test() {
            var list = EmployeeData.getEmployees();
            var res1 = list.stream().allMatch(e -> e.getAge() > 18);
            // false
            System.out.println(res1);
    
            var res2 = list.stream().anyMatch(e -> e.getAge() > 18);
            // false
            System.out.println(res2);
    
            // true
            var res3 = list.stream().noneMatch(e -> e.getAge() > 18);
            System.out.println(res3);
    
            var res4 = list.stream().count();
            // 7
            System.out.println(res4);
    
            Optional<Employee> first = list.stream().findFirst();
            // Optional[Employee(name=0001, age=1)]
            System.out.println(first);
    
            Optional<Employee> any = list.parallelStream().findAny();
            // Optional[Employee(name=0005, age=5)]
            System.out.println(any);
    
            // Optional[Employee(name=0007, age=7)]
            Optional<Employee> max = list.stream().max(Comparator.comparingInt(Employee::getAge));
            System.out.println(max);
    
            // Optional[Employee(name=0001, age=1)]
            Optional<Employee> min = list.stream().min(Comparator.comparingInt(Employee::getAge));
            System.out.println(min);
        }
    
4.2 reduce 规约
  • reduce(T identity, BinaryOperator accumulator)可以将流中的元素反复结合起来,得到一个值,并返回计算的值加上初始值identity

            List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
            Integer reduce
                    = list.stream().reduce(10, Integer::sum);
            // 65
            System.out.println(reduce);
    
  • eg:获取全部员工年龄加和

            var list2 = EmployeeData.getEmployees();
            var sum = list2.stream()
                    .map(Employee::getAge)
                    .reduce(0, Integer::sum);
            System.out.println(sum);
    
4.3 收集
  • collect(Collector c):将流转化为其他形式,接收一个Collector接口实现,用于给Stream中元素做汇总的方法

  • Collector接口中的方法决定了如何对流执行收集的操作(如收集到List、Set、Map等)

        @Test
        public void test2() {
            // 查找年龄大于5的员工,并返回为一个list或者set
            var list = EmployeeData.getEmployees();
            var resList = list.stream().filter(e -> e.getAge() > 5)
                    .collect(Collectors.toList());
            System.out.println(resList);
    
            var resSet = list.stream().filter(e -> e.getAge() > 5)
                    .collect(Collectors.toSet());
            System.out.println(resSet);
        }
    
  • Collectors的相关方法有:

    • toList():把流中的元素收集到list
    • toSet():把流中的元素收集到Set
    • toCollection():
    • counting()::
    • summingInt():
    • averagingInt():
    • summarizingInt():返回值为IntSummaryStatistics,收集流中Integer属性的统计值,如平均值

5 Optional类

​ Optional通过检查空值的方式防止代码污染,避免空指针异常

5.1 Optional的创建
  • Optional.of(T t):创建一个Optional实例,t必须非空,否则会报空指针

  • Optional.empty():创建一个空Optional

  • Optional.ofNullable(T t):t可以为空,如果为空返回的是一个value为null的Optional.Empty常量

            Girl girl = new Girl();
            girl = null;
            // nullPointerException
            var optionalGirl
                    = Optional.of(girl);
    
  • Optional.orElse(T t):为空的时候指定返回值,避免了空指针问题

        public String getGirlName(Boy boy) {
            var optionalBoy = Optional.ofNullable(boy);
            // boy1一定非空
            var boy1 = optionalBoy.orElse(new Boy(new Girl("girl1")));
    
            var girl = boy1.getGirl();
            var optionalGirl = Optional.ofNullable(girl);
            var girl1 = optionalGirl.orElse(new Girl("girl2"));
            return girl1.getName();
        }
    
  • Optional.get():获取Optional封装的值,如果为空则会抛出异常

  • boolean isPresnt():判断是否包含对象

  • void ifPresent(Consumer<? extends T> consumer):如果有值,就将该值作为参数就执行Consumer接口的实现代码

  • T orElseGet(Supplier<? extends T> other):如果没有值,则执行Supplier接口返回的值作为该值返回

  • T orElseThrow(Supplier<? extends X> eceptionSupplier):如果存在值则将值返回,否则抛出supplier接口得到的异常

4 动态代理

  • 代理模式的原理

    使用一个代理对象将对象包装起来,然后使用该代理对象包装原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及何时将方法调用转到原始对象上

  • 静态代理举例:

    thread就是一个代理对象,t则是被代理对象,当执行start方法的时候,实际上就是执行的MyThread被代理类的run方法

    public class MyThread implements Runnable{
        @Override
        public void run() {
    
        }
    
        @Test
        public void test() {
            MyThread t = new MyThread();
            Thread thread = new Thread(t);
            thread.start();
        }
    }
    
  • 静态代理的缺点:

    • 每个代理类只能为一个接口服务,程序开发必然会产生过多的代理
    • 代理类和目标类(被代理类)都是在编译期间确定下来,不利于程序的扩展
    • 最好能够通过一个代理类实现全部的代理功能——动态代理
  • 动态代理指的是客户通过代理类来调用其他对象方法,并且在程序运行时根据需要动态创建目标类的代理对象

  • 动态代理需要解决的问题:

    • 如何根据加载到内存的被代理类,动态地创建一个代理类及其对象(通过Proxy.newProxyInstance)
    • 当通过代理类的对象调用方法a的时候,如何动态地去调用被代理的同名方法a