Java - Java核心 - Java 8新特性 - Lambda

Java - Java核心 - Java 8新特性 - Lambda

1 Lambda 表达式的概念

1.1 概念
  • Java 8 新功能
  • 替代只有一个抽象函数的接口实现
  • 代码简洁(取代匿名内部类)
  • 提升了对集合,框架的迭代,遍历,过滤数据的操作
1.2 特点
  • 函数式编程
  • 参数类型自动推断
  • 代码简洁
1.3 Lambda 表达式的应用场景
  • 任何有函数式接口的地方
1.3.1 什么是函数式接口?
  • 只有一个抽象方法(Object类中的方法除外)的接口是函数式接口
1.3.2 常用的函数式接口

用来接收后续传入的逻辑,但是对输入输出有要求

Supplier

  • 代表一个输出
  • T get() 方法,从中得到一个输出

Consumer

  • 代表一个输入
  • accept(T t)

BiConsumer<T, U>

  • 代表两个输入
  • void accept(T t, U u);

Function<T, R>

  • 代表一个输入,一个输出(一般输入和输出是不同类型的)
  • 输入输出类型相同也可以使用
  • R apply(T t);

UnaryOperator

  • 代表一个输入,一个输出(输入和输出是相同类型)
  • 泛型T 的类型既是输入类型也是输出类型
  • T apply(T t);

BiFunction<T, U, R>

  • 代表两个输入,一个输出(一般输入和输出是不同类型的)
  • R apply(T t, U u);

BinaryOperator

  • 代表两个输入,一个输出(输入和输出是相同类型)
  • T apply(T t, T t1);
  • 两个输入和一个输出都是泛型T 类型

代码示例:

Supplier<String> s1 = () -> "abc";
        System.out.println(s1.get()); //abc

        Consumer<Integer> c1 = i -> System.out.println(i);
        c1.accept(16); //16

        BiConsumer<String,Integer> bc1 = (s,i) -> System.out.println(s+i);
        bc1.accept("aaa",46); //aaa46

        Function<String,Integer> f1 = s -> s.length();
        System.out.println(f1.apply("acacaac")); //7

        Function<String,String> f2 = s -> s.substring(1);
        System.out.println(f2.apply("acedfe")); //cedfe

        UnaryOperator<String> uo1 = s -> s.substring(2);
        System.out.println(uo1.apply("asdsadasdas")); //dsadasdas

        BiFunction<String,String,Integer> bf1 = (s,s2) -> s.length()+s2.length();
        System.out.println(bf1.apply("abc", "acdf")); //7

        BinaryOperator<Integer> bo1 = (i,i1) -> i+i1;
        System.out.println(bo1.apply(10, 20)); //30
1.3.3 表达式写法
() -> {}

() 参数的个数,根据函数式接口里抽象方法参数的个数来决定,只有一个参数时括号可以省略,建议都带上

() 括号中类型可以省略

{} 当逻辑非常简单时return 可以省,如 (x) -> x+1

{} 当方法体中代码只有一行时可以省略{}

{} 当方法中有多行代码时需要使用;作为分隔符,{;}同时出现

{} 方法体中还可以进行定义好的方法的调用,并进行逻辑的处理
1.4 为什么要在 java 8 加入 Lambda 表达式?

(1)在普通Java 代码编写的时候,对于一些逻辑相同的代码如何进行去重操作?

(2)可以根据面向接口的思想,对逻辑重复的代码进行去重优化

// 编写接口用来进行需要的逻辑业务比较
public interface studentFilter {
    boolean compare (Student student);
}

//业务需要比较年龄时,创建年龄比较的实现类
public class AgeFilter implements studentFilter {
    @Override
    public boolean compare(Student student) {
        return student.getAge()>10;
    }
}

//比较成绩
public class ScoreFilter implements studentFilter {
    @Override
    public boolean compare(Student student) {
        return student.getScore()>75d;
    }
}

//编写方法打印去重
public static void printList(List<Student> list){
        for (Student student : list) {
            System.out.println(student);
        }
    }
    
//编写方法进行业务逻辑的处理
public static void studetnFilter(List<Student> list, studentFilter filter){
        List<Student> list1 = new ArrayList<>();
        for (Student student : list) {
            if (filter.compare(student)) list1.add(student);
        }

        printList(list1);
    }
    
//测试代码
studetnFilter(list,new AgeFilter());
studetnFilter(list,new ScoreFilter());

(3)再进一步优化,减少接口的实现类,通过匿名内部类来实现

// 只需要定义一个接口无需根据业务编写实现类

//使用匿名内部类对不同的业务进行逻辑处理
studetnFilter(list, new studentFilter() {
            @Override
            public boolean compare(Student student) {
                return student.getAge()>10;
            }
        });

        studetnFilter(list, new studentFilter() {
            @Override
            public boolean compare(Student student) {
                return student.getScore()>75;
            }
        });

(4)匿名内部类代码还是比较繁重,可以使用Lambda 表达式来进行优化

// 将 new studentFilter 省略直接在其后编写逻辑
studetnFilter(list,student -> student.getAge()>10);
studetnFilter(list,student -> student.getScore()>75d);
1.5 方法的引用
1.5.1 方法引用的概念
  • 直接访问类或实例已经存在的方法或构造方法
  • 引用而不执行方法的方式
  • 抽象方法的实现恰好可以调用另一个方法来实现,有可能可以使用方法的引用
1.5.2 方法引用的分类
类型 语法 对于Lambda表达式
静态方法引用 类名::staticMethod (args) -> 类名.staticMethod(args)
实例方法引用 inst::instMethod (args) -> inst.instMethod(args)
对象方法引用 类名::instMethod (inst,args) -> 类名.instMethod(args)
构造方法引用 类名::new (args) -> new 类名(args)
1.5.3 方法引用的使用场景

(1)静态方法引用:

  • 如果函数式接口的实现恰好可以通过调用一个静态方法来实现,就可以使用静态方法引用

(2)实例方法引用:

  • 如果函数式接口的实现恰好可以通过调用一个实例的实例方法来实现,就可以使用实例方法引用
  • inst 可以为创建出来的实例对象,也可以是表达式,如 new xxx()

(3)对象方法引用:

  • 抽象方法第一个参数类型刚好是实例方法的类型
  • 抽象方法剩余参数(除第一个参数)恰好可以当作实例方法的参数
  • 函数式接口的实现可以由满足以上条件的实例方法来调用实现,就可以使用对象方法引用

(4)构造方法引用:

  • 如果函数式接口的实现恰好可以通过一个类的构造方法来实现,就可以使用构造方法引用
1.6 Lambda 表达式不能访问非 final的局部变量
// 其实这就要说到Jvm内存模型和线程了,因为实例变量存在堆中,而局部变量是在栈上分配,lambda 表达(匿名内部类) 会在另一个线程中执行。如果在线程中要直接访问一个局部变量,可能线程执行时该局部变量已经被销毁了,而 final 类型的局部变量在 Lambda 表达式(匿名类) 中其实是局部变量的一个拷贝

2 Lambda 表达式详解

2.1 如何系统的寻找新特性?

(RFC Paper) --> JCP JSP Lambda 335 --> JLS (Language Specification)

JCP:https://www.jcp.org/en/home/index

2.2 Expression 规则
(int x, int y) -> x+y; 
(x,y) -> x+y;
(x, int y) -> x+y; //错误,不能混合推断的类型和自己声明的类型
(x, final y) -> x+y //错误,不能给推断类型添加修饰符
2.3 Parameters
  • 只有声明的参数才能有访问修饰符
2.4 Lambda Body
  • expression
  • block
2.5 Stream 流
2.5.1 简介
  • 2种stream流
    • 串行流 Stream()
    • 并行流 parallelStream()
  • 简洁遍历
    • forEach()
      • Stream 内部提供遍历方式,简化集合遍历代码
  • 简洁过滤
    • filter()
      • 数据过滤,可与其他API共同使用实现
      • Predicate
  • 简洁映射
    • map()
      • 数据映射,可和其他API共同使用(通过一定的规则映射)
      • Function<T, T>
  • 简洁排序
    • sorted()
      • Comparator
  • 简洁收集
    • Collectors
      • 实现归约操作,将流转换成集合和聚合元素
      • 可返回列表或字符串
  • 简洁统计
List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
IntSummaryStatistics statistics = list1.stream().mapToInt(x -> x).summaryStatistics();
System.out.println(statistics.getAverage()); //4.5
System.out.println(statistics.getMax()); //8
System.out.println(statistics.getMin()); //1
System.out.println(statistics.getSum()); //36
System.out.println(statistics.getCount()); //8
  • 流聚合
    • flatMap()
      • 将流一对多转换,将元素放到一个新流
//flatMap 实现流的聚合,比如将三个List进行合并
//flatMap 可以将下面三个集合进行过滤处理后再聚合,得到偶数
List<Integer> il1 = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> il2 = Arrays.asList(6, 7, 8, 9, 10);
List<Integer> il3 = Arrays.asList(11, 12, 13, 14, 15);
List<List<Integer>> listList = Arrays.asList(il1, il2, il3);
List<Integer> list4 = listList.stream().flatMap(x -> x.stream().filter(x1 -> x1 % 2 == 0)).collect(Collectors.toList());
  • Reduce 累加器
		// reduce 详解
        //(1) Optional<T> reduce(BinaryOperator<T> accumulator); 两个输入一个输出(同类型)
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        System.out.println(list.stream().reduce((a, b) -> a + b).get());
        //第一个参数a为执行结果的缓存,第二个参数b为stream依次的元素,首次执行第一个元素a为stream中第一个元素
        System.out.println(list.stream().reduce((a, b) -> {
            System.out.println("a" + a + "b" + b);
            return a + b;
        }).get());

        //(2) T reduce(T identity, BinaryOperator<T> accumulator);
        // 与上一个方法相似,区别是首次执行第一个元素为指定identity
        // 另一个区别stream存在为空的情况,上一种方式结果使用Optional来包装,get获取Integer类型允许为空
        // 而这种方式赋予了初值即使stream为空也可以将初始值赋给结果,结果为Integer
        Integer integer = list.stream().reduce(0, (a, b) -> a + b);

        //(3)<U> U reduce(U identity,
        //                 BiFunction<U, ? super T, U> accumulator,
        //                 BinaryOperator<U> combiner);
        /*
            该方法代码类似于:
            U result = identity;
            for (T element : this stream)
                result = accumulator.apply(result,element);
            return result;
         */
        // 以上两种方法缺点是输入和输出的类型必须相同,第三种方法
        // 第一个参数为结果的初值(如果要转int初值可以设置为int),第二表达式里第一个参数为设置的初值,第二个参数为steam里的值
        // 第三个表达式在串行流中随便设置均可通过编译
        List<String> list1 = Arrays.asList("abc", "cba", "nbba", "asdsa", "sads");
        Integer integer1 = list1.stream().reduce(0, (a, b) -> a + b.length(), (c, d) -> c*d);
        System.out.println(integer1);

        // 并行流执行 reduce第三种方式 
        //如果想要保证相同代码逻辑串行并行结果一直java api规定了identity对于combiner来说是个恒等式
        // (3.1) 准则1 :即任意的u combiner(identity,u) equals u
        // (3.2) 准则2 : combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
        // 并行流执行下,第二个表达式里第一个参数a为设置初值,第二个参数b为流中每个元素
        // 并行时内部遵循fork-join模式,比如3个线程T1,T2,T3,会分为(T1,T2)和(T3),然后再combine结果
        // join 时采用的是 result且该result会一直使用
        /*
        下面例子中,打印出结果为:
        c5d4
        c3d3
        c4d20
        c9d80
        720
        第一组(5,4,4) 第二组(3,3)
        执行过程为 5*4 -> 4*20 -> 80
                 3*3 -> 9
                 combiner:80*9
         */
        Integer integer2 = list1.parallelStream().reduce(0, (a, b) -> a + b.length(), (c, d) -> {
            System.out.println("c"+c+"d"+d);
            return c*d;
        });
        System.out.println(integer2);
  • MapReduce 计算
		//MapReduce
        int loop = 10000000;
        ArrayList<Person> persons = new ArrayList<>(loop);
        Person person = new Person();
        for (int i = 0; i < loop; i++) {
            person.setName("李"+i);
            person.setScore(new Random().nextInt(100));
            persons.add(person);
        }

        // 普通迭代来计算总分
        long start1 = System.currentTimeMillis();
        int total = 0;
        for (Person person1 : persons) {
            total+=person1.getScore();
        }
        long end1 = System.currentTimeMillis();
        System.out.println(total);
        System.out.println(end1-start1);

        //串行的MapReduce计算
        long start2 = System.currentTimeMillis();
        Integer total1 = persons.stream().map((x) -> x.getScore()).reduce((x, y) -> x + y).get();
        long end2 = System.currentTimeMillis();
        System.out.println(total1);
        System.out.println(end2-start2);

        //并行计算MapReduce
        long start3 = System.currentTimeMillis();
        Integer total2 = persons.parallelStream().map((x) -> x.getScore()).reduce((y,z) -> y+z).get();
        long end3 = System.currentTimeMillis();
        System.out.println(total2);
        System.out.println(end3-start3);

        // 结果分析
        /*
            for循环   120000000
            47
            串行  120000000
            235
            并行  120000000
            169
         */
2.5.2 概念

Java 8 对集合提供流式计算方式

元素集看作流在管道中传输,并在管道节点上处理(筛选,排序,聚合等)

Stream API 基本返回其本身,可以实现链式编程,将操作串联成管道,如流式风格(fluent style),实现操作优化,如延迟执行(laziness)或短路(short-circuiting)

示意图:

数据源 -> stream -> (filter/sorted/map/collect) -> 结果集

3 Stream 详解

3.1 什么是Stream API 和 为什么引入 Stream API?

Stream API用来处理数组和集合的API

3.1.1 为什么引入?
  • 函数式编程代码简介且意图明确,可以替代for循环
  • 多核友好,通过parallel() 方法编写并行程序
3.2 Stream 的特性
  • 不是数据结构,没有内部存储
  • 不支持索引访问
  • 延迟执行(懒执行)
  • 支持并行
  • 容易生成数组或集合(List,Set)
  • 支持过滤,查找,转换,汇总,聚合等操作
3.3 Stream 运行机制

Stream 分为源(source),中间操作,终止操作

  • 流的源可以是一个集合,一个数组,一个生成器方法,一个I/O通道等
  • 一个流可以有零个或多个中间操作,每个中间操作都会返回新的流供下一个操作使用,一个流只有一个终止操作
  • 只有遇到终止操作,源才开始执行遍历操作
3.4 Stream的创建方式
  • 通过数组
String[] arr = {"a","b","c","d"};
Stream<String> stream1 = Stream.of(arr);
stream1.forEach((s)->System.out.println(s));
//stream1.forEach(System.out::println);
  • 通过集合
List<String> list = Arrays.asList("1","2","3","4");
Stream<String> stream2 = list.stream(); //拿到的是集合中的每一个元素
Stream<List<String>> stream3 = Stream.of(list); //将整个集合当作一个元素
  • Stream.generate 方法
Stream<Integer> stream4 = Stream.generate(() -> 1);
stream4.limit(5).forEach(System.out::println);
  • Stream.iterate 方法
//seed 为初始值,需要加以限定终止循环
Stream<Integer> stream5 = Stream.iterate(1, (x) -> x+1);
stream5.limit(5).forEach(System.out::println);
//可以通过iterate 方式实现循环 Stream.iterate(1,x -> x+1)
  • 其他API
String s = "abcdef";
IntStream stream6 = s.chars();
//直接循环遍历打印结果为int的ACSS码值,可以指定其类型拿到字符类型
stream6.forEach((s1) -> System.out.println((char)s1));
3.5 Stream 常用中间操作
  • 过滤 filter
  • 去重 distinct
  • 排序 sorted
  • 截取 limit,skip
  • 转换 map/flatMap
  • 其他 peek
3.6 Stream常见终止操作
  • 循环 forEach
  • 计算 min/max/count/average
  • 匹配 anyMatch/allMatch/noneMatch/findFirst/findAny
  • 汇聚 reduce
  • 收集器 toArray collect
3.7 Stream API 使用练习
		// 求集合中所有偶数
        Stream<Integer> stream1 = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
        stream1.filter(x -> x%2==0).forEach(System.out::println);

        // 求集合中所有偶数的和
        Stream<Integer> stream2 = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
        System.out.println(stream2.filter(x -> x%2==0).mapToInt(x -> x).sum());

        // 求集合中的最大值
        Stream<Integer> stream3 = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
        System.out.println(stream3.max((o1, o2) -> o1-o2).get());
        //Comparator 传入两个数,- 小,0 等, + 大
        //得到的结果为Optional 类型,get 方法拿到其中的值

        // 求集合中的最小值
        Stream<Integer> stream4 = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
        System.out.println(stream4.min((o1, o2) -> o1 - o2).get());

        // findAny 和 findFirst
        List<Integer> list = Arrays.asList(1,2,3,4,5,6);

        //findAny 返回随机结果,串行一般第一个,并行不一定,这段代码如果找不到数据就会抛异常
        System.out.println(list.stream().filter(x -> x % 2 == 0).findAny().get());
        //findAny 这段代码如果找不到数据会返回value orElse(value)是设置找不到数据后的默认值

        //findFirst
        System.out.println(list.stream().filter(x -> x % 6 == 0).findFirst().orElse(0));

        //延迟执行 只有中间操作没有终止操作不会执行
        list.stream().filter(x -> { System.out.println("测试");return x%2==0;}).findFirst().get();
        //测试执行两次,1先filter判断执行一次,2再filter执行并结束
        // list = Arrays.asList(1,3,5,6),将会执行4次测试
        //System.out.println("测试");return x%2==0;}) 测试一次不执行

        //不使用min/max 方法实现查找最大值最小值
        //sorted 默认是按自然顺序排序
        Integer min = list.stream().sorted().findFirst().get();
        Integer max = list.stream().sorted((x1, x2) -> x2 - x1).findFirst().get();

        //对子字符串进行自然排序和自定义排序规则
        List<String> list2 = Arrays.asList("java","c#","python","scala","jeva");
        list2.stream().sorted().forEach(System.out::println);
        list2.stream().sorted((s1,s2) -> s1.length()-s2.length()).forEach(System.out::println);

        //将集合中的元素进行过滤再返回一个集合
        List<String> collect = list2.stream().filter((x) -> x.length() > 4).collect(Collectors.toList());
        collect.stream().forEach(System.out::println);

        //去重操作,可使用distinct或Set集合两种方式实现
        List<Integer> list3 = Arrays.asList(1,2,2,3,4,5,5,6);
        list3.stream().distinct().forEach(System.out::println);
        list3.stream().collect(Collectors.toSet()).forEach(System.out::println);

        //打印20-30的集合数据
        Stream<Integer> stream = Stream.iterate(20, x -> x + 1);
        stream.limit(11).forEach(System.out::println);

        //取其中的一部分值 skip取15-30 要取到15需跳过14次
        Stream.iterate(1,x -> x+1).limit(50).skip(14).limit(16).forEach(System.out::println);

        //将一个String 字符串中的数字取出来并求其和
        String s = "11,22,33,44,55";
        Stream.of(s.split(",")).mapToInt(x -> Integer.valueOf(x)).sum();
        Stream.of(s.split(",")).mapToInt(Integer::valueOf).sum();
        Stream.of(s.split(",")).map(x -> Integer.valueOf(x)).mapToInt(x -> x).sum();
        Stream.of(s.split(",")).map(Integer::valueOf).mapToInt(x -> x).sum();

        //创建自定义对象
        String s1 = "zhangfei,liubei,guanyu";
        Stream.of(s1.split(",")).map(x -> new Person(x)).forEach(System.out::println);
        Stream.of(s1.split(",")).map(Person::new).forEach(System.out::println);
        Stream.of(s1.split(",")).map(x -> Person.build(x)).forEach(System.out::println);
        Stream.of(s1.split(",")).map(Person::build).forEach(System.out::println);

        //peek 对字符串中数字求和并打印每一个字符
        Stream.of(s.split(",")).peek(System.out::println).mapToInt(Integer::valueOf).sum();

        // 匹配所有和匹配任意 allMatch/anyMatch
        list.stream().allMatch(x -> x%2==0); //false
        list.stream().anyMatch(x -> x%2==0); //true
posted @ 2021-01-01 13:57  Pengc931482  阅读(104)  评论(0)    收藏  举报