Java8特性之Lambda表达式


Lambda表达式可以理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。Lambda表达式由参数、箭头和主体组成。

  • 如果lambda代码块主体无法用一个表达式表示,就要用{}包裹代码并明确使用return语句;
  • 如果lambda表达式没有参数,可以提供一对空的小括号,如同不带参的方法那样;
  • 如果一个lambda表达式的参数类型是可以被推导的,那么可以省略它们的类型;

在这里插入图片描述

函数式接口

函数式接口的转换是使用lambda表达式能做的唯一一件事。函数式接口就是只定义一个抽象方法的接口,但是可以有多个非抽象方法,这样的接口可以被隐式转换为lambda表达式。
在这里插入图片描述
java1.8使我们能够通过使用default关键字向接口添加非抽象方法实现,此功能也称为虚拟扩展法。

interface Formula{

    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }

}

实现该接口的类只要实现抽象方法就可以直接使用默认方法;也可以直接通过接口创建对象,然后实现接口的默认方法。

public class Main {

  public static void main(String[] args) {
    // 通过匿名内部类方式访问接口
    Formula formula = new Formula() {
        @Override
        public double calculate(int a) {
            return sqrt(a * 100);
        }
    };

    System.out.println(formula.calculate(100));     // 100.0
    System.out.println(formula.sqrt(16));           // 4.0

  }

}

注:上述通过匿名内部类方式访问接口,我们可以这样理解:一个内部类实现了接口里的抽象方法并且返回一个内部类对象,然后让接口的引用指向这个对象。

java1.8还增加了一种特殊的注解@FuntionalInterface,但是这个注解通常不是必须的。只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口。

@FunctionalInterface
public interface Converter<F, T> {
  T convert(F from);
}
Lambda实践:环绕执行模式

下面是一段代码,从一个文件中读取一行所需的文件内容:

public static String processFile() throws IOException{
    try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
        return br.readLine();
    }
}

行为参数化
这段代码的局限性在于只能读取文件的第一行,当要执行processFile方法对文件执行不同的操作时,就要把processFile的行为参数化:根据传递给它的参数不同,其行为也会不同。Lambda可以传递行为,把行为传递给processFile以便利用BufferedReader执行不同的行为。

String result = processFile( (BufferedReader br) -> br.readLine() + br.readLine() );

使用函数式接口来传递行为
Lambda表达式仅可用于上下文是函数式接口的情况,这时就需要创建一个能匹配BufferedReader -> String,还可以抛出IOException异常的接口。

@FuctionalInterface
public interface BufferedReaderProcessor{
    String process(BufferedReader b) throws IOException;
}

将这个接口作为新的processFile方法的参数:

public static String processFile(BufferedReaderProcessor p) throws IOException{
    ...
}

执行一个行为
在processFile主体内,对得到的BufferedReaderProcessor对象调用process方法执行处理:

public static String processFile(BufferedReaderProcessor p) throws IOException{
    try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
        return p.process(br);
    }
}

传递Lambda
现在就可以通过传递不同的Lambda重用processFile方法,并以不同的方式处理文件:

String oneLine = processFile( (BufferedReader br) -> br.readLine() );   //处理一行
String twoLines = processFile( (BufferedReader br) -> br.readLine() + br.readLine() );  //处理两行

下图总结了所采取的使processFile方法更灵活的四个步骤。
在这里插入图片描述

使用函数式接口

函数式接口的抽象方法的签名称为函数描述符,Java API中常见的函数式接口有:Comparable、Runnable和Callable。Java8中java.util.function包引入了几个新的函数式接口:Predicate、Consumer和Function。

Predicate

java.util.function.Predicate<T>接口定义了一个test抽象方法,它接受泛型T对象并返回一个boolean。在你需要表示一个涉及类型T的布尔表达式时,就可以使用这个接口。
在这里插入图片描述

Consumer

java.util.function.Consumer<T>定义了一个accept的抽象方法,它接受泛型T对象而没有返回值(void)。你可以用它来创建一个forEach方法,接受一个Integer的列表,并对其中每个元素执行操作。
在这里插入图片描述

Function

java.util.function.Function<T, R>接口定义了一个apply方法,它接受一个泛型T对象并返回一个泛型R对象。如果你需要将输入对象的信息映射到输出,就可以使用这个接口。
在这里插入图片描述

类型检查、类型推断以及限制

类型检查:Lambda的类型是从使用Lambda的上下文推断出来的。
在这里插入图片描述
上述类型检查可以分为以下步骤:

  • filter方法声明第二个参数为Predicate< Apple >,因此将T绑定到Apple;
  • 该函数式接口定义了一个test抽象方法,test方法描述了一个函数描述符。它可以接受一个Apple对象,并返回一个boolean对象;

类型推断:Java编译器会从上下文推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名。从而,编译器可以了解Lambda表达式的参数类型,这样就可以在Lambda语法中省去标注参数类型。
在这里插入图片描述
有限制的局部变量:Lambda表达式也允许使用自由变量,就像匿名类一样。但局部变量必须显式声明为final,或事实上就是一个final变量。
在这里插入图片描述

方法引用

方法引用就是让你根据已有的方法实现来创建Lambda表达式,可被看作是仅仅调用特定方法的Lambda的一种快捷写法。
在这里插入图片描述
方法引用主要有三种:

  1. 指向静态方法的方法引用;(例如Integer的parseInt方法,写作Integer::parseInt)
  2. 指向任意类型实例方法的引用;(例如String的length方法,写作String::length)
  3. 指向现有对象的实例方法的方法引用;(假设局部变量expensiveTransaction存放Transaction类型对象,它支持getValue方法,就可以写expensiveTransaction::getValue)

构造函数引用

对于一个现有构造函数,可以利用它的名称和关键字new来创建它的一个引用:Classname::new。
无参构造函数
在这里插入图片描述
只有一个参数的构造函数
在这里插入图片描述
具有两个参数的构造函数
在这里插入图片描述

Lambda和方法引用实战

下面将一步步展示如何用lambda表达式来对一个Apple列表进行排序:

  • 首先用List的sort(Comparator<? super E> c)方法,它需要一个Comparator对象来比较Apple。
public class AppleComparator implements Comparator<Apple>{
    public int compare(Apple a1, Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
    }
}
  • 可以使用匿名内部类进行改进,而不是实现一个Comparator却只实例化一次:
inventory.sort(new Comparator<Apple>() {
    public int compare(Apple a1, Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
    }
});
  • 也可以使用Lambda表达式来传递Comparator的函数描述符(T, T) -> int
inventory.sort( (a1, a2) -> a1.getWeight().compareTo(a2.getWeight()) );
  • Comparator具有一个comparing的静态辅助方法,可以接受一个Function来提取Comparable键值,并生成一个Comparator对象:
Comparator<Apple> c = Comparator.comparing( (a) -> a.getWeight() );

inventory.sort(comparing( (a) -> a.getWeight() ) );
  • 使用方法引用:
inventory.sort( comparing(Apple::getWeight() );

复合Lambda表达式

比较器复合

逆序
GnUuiF.png
比较器链
在这里插入图片描述

谓词复合

谓词接口包括三个方法:negate、and和or,让你可以重用已有的Predicate来创建更复杂的谓词。
在这里插入图片描述
也可把两个lambda用and方法组合起来:
GnU8qx.png
进一步组合谓词:
在这里插入图片描述

函数复合

最后,还可以把Function接口所代表的Lambda表达式复合起来,Function接口为此提供了andThen和compose两个默认方法,它们都会返回Function的一个实例。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值