• Java8-新特性及Lambda表达式


    1、Java8新特性内容概述

    1.1、简介

    Java 8(又称为jdk1.8)是Java语言开发的一个主要版本

    Java 8是oracle公司于2014年3月发布,可以看成是自Java 5以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性

    1.2、新特性思维导图总结

    在这里插入图片描述

    1.3、新特性好处

    • 速度更快
    • 代码更少==(增加了新的语法:lambda表达式)==
    • 强大的StreamAPI
    • 最大化减少空指针异常:Optional
    • Nashorn引擎,允许在JVM上运行JS应用

    2、Lambda表达式概述

    2.1、为什么使用

    lambda是一个匿名函数,我们可以把lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提示。

    2.2、语法

    Lamdba表达式:在Java8语言中引入的一种新的语法元素和操作符。这个操作符为->,该操作符被称为Lambda操作符或箭头操作符。它将Lambda分为两个部分:

    • 左侧:指定了Lambda表达式需要的参数列表
    • 右侧:指定了Lambda体,是抽象方法的实现逻辑,也即Lambda表达式要执行的功能

    举例:

    (o1,o2) -> Integer.compare(o1,o2)

    格式:

    • ->:lambda操作符 或 箭头操作符
    • 左边:lambda形参列表(其实就是接口中的抽象方法的形参列表)
    • 右边:lambda体(其实就是重写的抽象方法的方法体)

    3、lambda表达式快速使用

    大致分为6种情况讲解

    • 无参无返回值

      Runnable run = () -> {
          System.out.println(111);
      };
      
      • 1
      • 2
      • 3
    • 需要一个参数,但没有返回值

      Consumer<String> con = (String x) -> {
          System.out.println(x);
      };
      
      • 1
      • 2
      • 3
    • 数据类型可以省略,因为可由编译器推断出来,称为类型推断

      Consumer<String> con = (x) -> {
          System.out.println(x);
      };
      
      • 1
      • 2
      • 3
    • 若只需要一个参数时,参数的小括号可以省略

      Consumer<String> con = str -> {
          System.out.println(str);
      };
      
      • 1
      • 2
      • 3
    • 若需要两个以上的参数,多条执行语句,并且可以有返回值

      Comparator<Integer> con = (x,y) -> {
          System.out.println(x);
          System.out.println(y);
          return Integer.compare(x,y);
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • 当Lambda体只有一条语句时,return与大括号若有,都可以省略

      Comparator<Integer> con = (x,y) -> Integer.compare(x,y);
      
      • 1

    无参无返回值

    因为Runnable接口里的唯一接口方法run()方法就是无参无返回值的方法,所以我们可以使用Lambda表示

    public static void main(String[] args){
        Runnable runn = () -> {
            System.out.println("hello world!");
        };
    
        runn.run(); //hello world!
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    需要一个参数,但没有返回值

    Consumer接口里的唯一接口方法accept就是接收一个参数,无返回值的方法,所以可以使用Lambda表示

    public static void main(String[] args){
        Consumer<String> con = (String s) ->{
            System.out.println(s);
        };
        con.accept("hello world!"); // hello world!
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    数据类型可以省略

    //Java可以类型推断,前面定义泛型,后面参数就不可能是其他类型了,所以可以省略
    Consumer<String> con = (s) ->{
        System.out.println(s);
    };
    con.accept("hello world!"); // hello world!
    
    • 1
    • 2
    • 3
    • 4
    • 5

    若只需要一个参数时,参数的小括号可以省略

    // 因为我们只有一个参数 s,所以小括号也可以省略,只有在参数个数等于1的时候,才能省略,小于或者大于都不能省略!!!
    Consumer con = s ->{
        System.out.println(s);
    };
    con.accept("hello world!"); // hello world!
    
    • 1
    • 2
    • 3
    • 4
    • 5

    若需要两个以上的参数,多条执行语句,并且可以有返回值

    public static void main(String[] args){
        Comparator<Integer> com = (x,y) -> {
            System.out.println(x); // 1
            System.out.println(y); // 5
            return Integer.compare(x,y);
        };
        int i = com.compare(1, 5);
        System.out.println(i); // -1
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    当Lambda体只有一条语句时,return与大括号若有,都可以省略

    Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
    int i = com.compare(1, 5);
    System.out.println(i); // -1
    
    • 1
    • 2
    • 3

    4、函数式接口

    4.1、如何理解函数式接口

    • Java从诞生起就一直倡导一切皆对象,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的星期和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,也即Java不但可以支持OOP还可以支持OOF(面向函数编程)
    • 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Labda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口
    • 简单的说,在java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示
    • 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写

    4.2、什么是函数式(Function)接口

    • 只包含一个抽象方法的接口,成为函数式接口
    • 你可以通过Lambda表达式来创建该接口的对象。(若Lambda表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)
    • 我们可以在一个接口上使用==@FunctionalInterface==注解,这样做可以检查它是否是一个函数式接口。同时javadoc也会包含一条声明,说明这个接口是一个函数式接口
    • 在java.util.function包下定义了Java8的丰富的函数式接口

    4.3、Java内置四大核心函数式接口

    Java提供了四大核心函数式接口

    • Consumer:消费性接口。对类型为T的对象应用操作,包含方法:void accept(T t)
    • Supplier:供给型接口。返回类型为T的对象,包含方法:T get()
    • Function:函数型接口。对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t)
    • Predicate:断定型接口。确定类型为T的对象是否满足某约束,并返回boolean值。包含方法:boolean test(T t)

    在这里插入图片描述

    Consumer

    public static void main(String[] args){
        Consumer<String> con = s -> System.out.println(s);
        con.accept("hello world!"); // hello world!
    }
    
    • 1
    • 2
    • 3
    • 4

    Supplier

    public static void main(String[] args){
        Supplier<String> sup = () -> "hello world";
        String s = sup.get();
        System.out.println(s); // hello world
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Function

    public static void main(String[] args){
        // 输入类型为Integer类型,返回结果为String类型
        Function<Integer,String> fun = (a) -> String.valueOf(a);
        String apply = fun.apply(5201314);
        System.out.println(apply); // 5201314
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Predicate

    public static void main(String[] args){
        Predicate<String> pre = (s) -> s == null;
        boolean aaa = pre.test("aaa");
        System.out.println(aaa); // false
    
        boolean test = pre.test(null);
        System.out.println(test); // true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.4、其他接口

    除了上面四大核心接口外,还提供了针对四大核心接口的增强接口,有需要的可以直接使用

    在这里插入图片描述

    5、方法引用

    • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用

    • 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖

    • 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致

    • 如下三种主要使用情况

      • 对象::实例方法名

      • 类::静态方法名

      • 类::实例方法名

    5.1、对象::实例方法名

    // 情况一:对象::方法
    // Consumer中的void accept(T t)
    // PrintSteam中的void println(T t)
    public static void main(String[] args){
        Consumer<String> con1 = a -> System.out.println(a);
        con1.accept("hello"); // hello
    
        System.out.println("*****************************");
    
        PrintStream out = System.out;
        // 对象::方法,结果和上面一样
        Consumer<String> con2  = out::println;
        con2.accept("world!"); // world
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5.2、类::静态方法名

    // 情况二:类::静态方法
    // Comparator中的int compare(T o1, T o2)
    // Integer中的static int compare(int x, int y)
    public static void main(String[] args){
        Comparator<Integer> com = (x,y) -> x.compareTo(y);
        int i = com.compare(10, 20);
        System.out.println(i); // -1
    
        System.out.println("***************************");
    
        Comparator<Integer> com2 = Integer::compare;
        int i2 = com2.compare(10, 20);
        System.out.println(i2); // -1
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5.3、类::实例方法名

    // 情况二:类::实例方法
    // Comparator中的int compare(T o1, T o2)
    // Integer中的int compareTo(Integer anotherInteger)
    // 虽然参数数量不一致,但是调用方法为这样结构的 o1.xxx(o2),可以写 o1的类名::o1要调用的方法
    public static void main(String[] args){
        Comparator<Integer> com = (x,y) -> x.compareTo(y);
        int i = com.compare(10, 20);
        System.out.println(i); // -1
    
        System.out.println("***************************");
    
        Comparator<Integer> com2 = Integer::compareTo;
        int i2 = com2.compare(10, 20);
        System.out.println(i2); // -1
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    6、构造器引用

    6.1、无参构造

    public class Test{
        public static void main(String[] args){
            // Supplier中的 T get()
            // Student中的 public Student() 无参构造
            Supplier<Student> sup = () -> new Student();
            System.out.println(sup.get()); // Student{name='null', age=null, birthday=null}
    
            // 构造引用写法
            Supplier<Student> sup2 = Student::new;
            System.out.println(sup2.get()); // Student{name='null', age=null, birthday=null}
        }
    }
    
    class Student{
        private String name;
        private Integer age;
        private LocalDateTime birthday;
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", birthday=" + birthday +
                    '}';
        }
    
        public Student() {
        }
    
        public Student(String name, Integer age, LocalDateTime birthday) {
            this.name = name;
            this.age = age;
            this.birthday = birthday;
        }
        
        public Student(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
    
        public Student(String name) {
            this.name = name;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    6.2、单参数构造

    public class Test{
        public static void main(String[] args){
            // Function中的 R apply(T t)
            // Student中的 public Student(String name) 单参构造
            Function<String,Student> fun = a -> new Student(a);
            Student apply = fun.apply("张三");
            System.out.println(apply); // Student{name='张三', age=null, birthday=null}
    
            // 构造引用写法
            Function<String,Student> fun2 = Student::new;
            Student apply1 = fun2.apply("李四");
            System.out.println(apply1); // Student{name='李四', age=null, birthday=null}
        }
    }
    
    //... 下面实体类省略
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    6.2、双参数构造

    public class Test{
        public static void main(String[] args){
            // BiFunction中的 R apply(T t, U u)
            // Student中的 public Student(String name, Integer age) 双参构造
            BiFunction<String,Integer,Student> biFun1 = (a,b) -> new Student(a,b);
            Student apply = biFun1.apply("张三", 20);
            System.out.println(apply); // Student{name='张三', age=20, birthday=null}
    
            // 构造引用写法
            BiFunction<String,Integer,Student> biFun2 = Student::new;
            Student apply2 = biFun2.apply("李四", 25);
            System.out.println(apply2); // Student{name='李四', age=25, birthday=null}
        }
    }
    //... 下面实体类省略
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    6.3、三参数构造

    三参构造我们上面讲解Java的函数式接口并没有提到,我也不知道,所以需要自己写一个函数式接口,满足业务需求

    1、定义函数式接口
    @FunctionalInterface
    interface MyFunction<T,U,O,R>{
        R apply(T t, U u, O o);
    }
    
    • 1
    • 2
    • 3
    • 4
    2、使用自定义函数式接口
    public static void main(String[] args){
        // MyFunction中的 R apply(T t, U u, O o)
        // Student中的 public Student(String name, Integer age, LocalDateTime birthday) 三参构造
        MyFunction<String,Integer,LocalDateTime,Student> mf = (a,b,c) -> new Student(a,b,c);
        Student apply = mf.apply("张三", 20, LocalDateTime.now());
        System.out.println(apply); // Student{name='张三', age=20, birthday=2022-11-25T16:01:10.610}
    
        // 构造引用写法
        MyFunction<String,Integer,LocalDateTime,Student> mf2 = Student::new;
        Student apply2 = mf.apply("李四", 25, LocalDateTime.now());
        System.out.println(apply2); // Student{name='李四', age=25, birthday=2022-11-25T16:01:10.611}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    7、数组引用

    就把数组当成特殊的类,使用new关键字创建

    public static void main(String[] args){
        // MyFunction中的 R apply(T t, U u, O o)
        // Student中的 public Student(String name, Integer age, LocalDateTime birthday) 三参构造
        Function<Integer,String[]> fun = length -> new String[length];
        String[] strings = fun.apply(3);
        for (String string : strings) {
            /*
                    null
                    null
                    null
                 */
            System.out.println(string);
        }
    
        System.out.println("**********************************************");
    
        // 数组引用
        Function<Integer,String[]> fun2 = String[]::new;
        String[] strings2 = fun2.apply(4);
    
        for (String s : strings2) {
            /*
                    null
                    null
                    null
                    null
                 */
            System.out.println(s);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    8、注意事项

    1、匿名类和Lambda表达式中的this和super的含义是不同的。在匿名类中,this代表的是类自身,但是在Lambda中,它代表的是包含类。其次,匿名类可以屏蔽包含类的变量,而Lambda表达式不能(会编译错误)

    public static void main(String[] args){
        int a = 10;
        // 匿名类
        Runnable runn1 = new Runnable() {
            @Override
            public void run() {
                int a = 2;
            }
        };
    
        //lambda
        Runnable runn2 = () -> {
            // int a = 3; 编译错误:Variable 'a' is already defined in the scope
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2、Lambda表达式也允许使用自由变量(不是参数,而是在外层作用域定义的变量),就像匿名类一样。它们被称作捕获Lambda,但是也有一些限制。

    Lambda可以没有限制地魔火(也就是在其主题中引用)实例遍历和静态变量。但局部变量必须显式声明为final。换句话说,Lambda表达式只能捕获指派给它们的局部变量一次。

    原因:

    ​ 实例变量和局部变量背后有一个关键不同。实例变量存储在堆中,而局部变量则保存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。

    ​ 因此,Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了

    public static void main(String[] args){
        int a = 111;
    
        Runnable runn = () -> {
            // 编译错误:Variable used in lambda expression should be final or effectively final
            // a = 222;
            System.out.println(a); // 111
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    Ansible---playbook 剧本
    2022Q3手机配件增长榜:手机壳、数据线等供求不断增加
    软考高级——系统架构设计师通关宝库
    MU editor IDE编辑器 二次开发记录与踩坑
    element树形控件单选
    Django——数据库
    EN 14782建筑金属结构产品—CE认证
    C# 文件/文件夹操作(文本写入,追加,覆盖,清空,文件/文件夹新建,复制,删除,移动)+驱动器+目录+路径+Path类大全
    Oracle将归档日志从 ASM 拷贝到 Linux 文件系统中操作步骤
    【React组件】github搜索案例之 兄弟组件通信---PubSubJS / fetch
  • 原文地址:https://blog.csdn.net/qq_57404736/article/details/128039932