Lambda表达式

Lambda

Lambda 表达式是 JDK8 的一个新特性,可以取代接口的匿名内部类,写出更优雅的Java 代码。

// 在Java 8以前的代码中,为了实现带一个方法的接口,往往需要定义一个匿名类并复写接口方法,代码显得很臃肿
String[] oldWay = "Improving code with Lambda expressions in Java 8".split(" ");
Arrays.sort(oldWay, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        // 忽略大小写排序:
        return s1.toLowerCase().compareTo(s2.toLowerCase());
    }
});
System.out.println(String.join(", ", oldWay));

// 对于只有一个方法的接口,在Java 8中,现在可以把它视为一个函数,用lambda表示式简化如下:
String[] newWay = "Improving code with Lambda expressions in Java 8".split(" ");
Arrays.sort(newWay, (s1, s2) -> {
    return s1.toLowerCase().compareTo(s2.toLowerCase());
});
System.out.println(String.join(", ", newWay));

如果说匿名内部类实际上是局部内部类的更进一步,简化了局部内部类,那么Lambda就是匿名内部类更进一步,语法上更简洁了,代码更优雅了。
以上描述总体就说了三点:

  1. Lambda表达式仍然是局部内部类,是特殊的局部内部类,仍然定义在局部位置。而且局部内部类的注意事项,也一样存在。
  2. Lambda表达式在取代匿名内部类时,不是全部都取代,是取代接口的匿名内部类,而类的匿名内部类Lambda表达式是不能取代的。
  3. Lambda表达式是匿名内部类的更进一步, Lambda表达式得到的也不是一个类,而是一个对象,并且是接口的子类对象。

Lambda表达式非常重要,在整个Java的开发过程中,一般来说,只要涉及集合,它都是一定要使用的
而集合是开发中必然会用到的容器!!!

使用前提:

  1. Lambda表达式表示创建接口的子类对象,但并不是所有的接口都能够用Lambda表达式创建子类对象

  2. 使用Lambda表达式要求,接口必须是功能接口(Functional Interface)

  3. 功能接口: 要求接口当中有且仅有一个必须强制子类实现的抽象方法!

    • 功能接口在Java当中用注解"@FunctionalInterface"标记,如果该注解没有报错,那么接口就是一个功能接口
    • 实际开发,如果自身有定义功能接口的需求,请不要忘记该注解标记!

扩展:

  1. 功能接口只有一个方法吗?
    不是,因为Java8以后接口还有带实现的默认方法和静态方法,这两种方法都不需要子类强制实现

  2. (了解)功能接口只有一个抽象方法吗?
    不是的
    某些方法,在接口中定义,不需要强制子类实现
    考虑Object,某个类在实现接口的同时,必须继承Object
    如果接口中的抽象方法,可以用Object类当中的实现方法作为实现,那么该抽象方法就不会强制子类实现

当然实际开发中,碰到的大多数功能接口都是比较单纯的,往往就只有一个方法,而且就是强转子类实现的抽象方法

功能接口有了以后,就可以按照以下语法来创建功能接口的子类对象:
() -> {}
解释:

  1. “()“表示功能接口中强制子类实现的抽象方法的形参列表
    也就是直接照抄抽象方法的形参列表
    比如抽象方法形参是:”()”,那么就直接写"()"
    如果抽象方法形参是:“(int num)”,那么就直接写"(num)"

  2. "->“由一个英文的横杠和大于号组成,它读作"goes to”,它是Lambda表达式的运算符
    是固定的语法形式

  3. (重点)"{}“是重写功能接口中抽象方法的重写方法体,是方法体,不是类体
    所以
    ①.在”{}"当中,定义的变量都是局部变量,而不是成员变量
    ②.Lambda表达式没有匿名子类的类体,所以也不能定义属于匿名子类的成员,只能重写抽象方法
    ③.Lambda表达式的语法当中,只能重写一个抽象方法,所以Lambda表达式要求接口是功能接口

如果按照上述语法,直接在局部位置写Lambda表达式,会编译报错,为什么呢?

  原因在于单独写Lambda表达式,编译器无法确定Lambda表达式的具体类型,而Java是强类型语言,每一个变量/对象的类型都是需要确定.

于是接下来,就需要一个告诉编译器,Lambda表达式创建对象类型的过程,称之为"Lambda表达式的类型推断"

Lambda表达式的类型推断: 明确Lambda表达式创建的接口子类对象的类型,明确创建的是哪个接口的子类对象

包括以下方式:

  1. (最简单直接的方式)用父类类型的引用接收,也就是用功能接口的引用接收,直接指明Lambda表达式的具体类型

    // old way:
        Runnable oldRunnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + ": Old Runnable");
            }
        };
    
    // new way:
    	Runnable newRunnable = () -> {
            System.out.println(Thread.currentThread().getName() + ": New Lambda Runnable");
    };
    
  2. (Lambda表达式类型推断的特殊语法,很少用,了解即可,它类似于强制类型转换)
      ((功能接口的名字)Lambda表达式).调用方法;

  3. (最常见,常用的方式)
    配合方法来完成Lambda表达式的类型推断
    比如:
    a.借助方法的形参数据类型来完成类型推断

    ​   假如方法的形参数据类型是一个功能接口时,具体传入的对象就可以是一个Lambda表达式创建的功能接口子类对象

    b.借助方法的返回值类型来完成类型推断

    ​   假如方法的返回值类型是一个功能接口时,具体返回的对象就可以是一个Lambda表达式创建的功能接口子类对象

你可能感兴趣的:(Java,java,lambda)