Lambda表达式

来源:互联网 发布:ant 执行sql脚本返回 编辑:程序博客网 时间:2024/09/21 09:28

Lambad介绍

Lambda表达式是java8新增的一个功能,lambad支持将代码作为方法参数,这个功能可以用来简洁的创建一个只有一个抽象方法的接口对象。

Lambad表达式入门

简单的看上面的的描述估计看不明白,我们来举例子说明下,比如,有时候我们在方法中使用代码块,就只能使用匿名内部类的方式来写,比如在android中给一个button添加一个点击事件,还有在使用Runnable创建新的线程,我们分别来看看新建线程平时的写法和使用lambda的写法

1.使用匿名内部类的方式启动个新线程

new Thread(new Runnable() {         @Override         public void run() {             //             System.out.print("使用匿名内部类的方式启动个新线程");         }     }).start();

2.使用匿名内部类的方式启动个新线程

 new Thread(() ->    {        //使用lambda方式启动个新线程        System.out.print("使用lambda方式启动个新线程");    }    ).start();

从上面两段代码可以看出,使用lambda完全可用于简化创建匿名内部类对象。下面就来分析下,我们看lambda的写法,大括号里面的实现和使用匿名内部类需要实现的方法体一模一样,只是不需要new XXX(){}这种繁琐的代码,不需要指出重写方法的名字和返回值类型,只需要给出方法的括号以及括号里面的形参列表即可。

从上面的例子来看,当使用Lambda表达式代替匿名内部类创建对象时,Lambda表达式的代码块就代替了抽象方法实现的方法体,Lambda就相当于一个匿名方法。
从上面的例子可以看出,Lambda表达式的主要作用就是用来代替匿名内部类的繁琐语法,那我们就学习Lambda的语法结构吧。

Lambda的语法结构

Lambda的语法结构由三部分组成,我将上面的代码贴成图片来分析分析:

  1. 形参列表:也就是上图中框起来的1 部分,就是这个括号(没有形参就括号里面什么都没有),形参列表允许省略形参类型,如果形参列表只有一个参数,甚至都可以不用写这个圆括号。
  2. 箭头(->):由英文的划线符号和大于号来组成。
  3. 代码块:如果代码块只有一条语句,Lambda表达式就允许省略外面的花括号;若代码块中只有一条return语句,就可以省略这个return关键字;Lambda需要返回值,当他的代码块只有一条语句时,就可以将花括号和return 都省略,花括号和return要不都省略,要不都不省略。

通过下面的代码,来认识下Lambda的语法
package com.gh;

interface Eat {  void eat(); }interface Drink {void drink(String drinkName);}interface Add {int add(int a, int b);}public class LambdaGrammar {public static void main(String[] args) {    LambdaGrammar grammar = new LambdaGrammar();    // 只有一条语句,所以省略代码块的花括号    grammar.eat(() -> System.out.println("吃..."));    // 只有一个形参,省略形参的圆括号    grammar.drink(drinkName -> {        System.out.println("我喜欢喝饮料");        System.out.println("我喜欢喝" + drinkName);    });    // 只有一条语句,省略花括号    // 只有一条需要返回的语句,也可以省略return    grammar.add((a, b) -> a + b    );}public void eat(Eat e) {    e.eat();}public void drink(Drink drink) {    drink.drink("可乐");}public void add(Add add) {    int i = add.add(1, 2);    System.out.println("1加2等于" + i);}}

最后,为了给我们下面讲的内容做铺垫,我们继续看上面代码,我们在LambdaGrammar类的main方法中调用的LambdaGrammar对象的eat()方法,这个eat方法的参数我们定义的时候是Eat类型的,而我们传入的是一个Lambda表达式,其他两个方法我们也是传入了Lambda表达式,那么到底Lambda表达式的类型是什么类型呢?接下来我们就来说说。

Lambda表达式与函数式接口

先来了解两个概念

  • 目标类型:Lambda表达式的类型,即称为“目标类型”,Lambda表达式的目标类型必须是函数式接口。
  • 函数式接口:代表只包含一个抽象方法的接口。函数式接口可以包含多个默认方法,类方法,但只能声明一个抽象方法。

通过以上的概念,我们就知道Runnable这个接口就是个函数式接口,他里面只有一个抽象的run()方法声明。若一个方法的参数是使用Lambda表达式来创建的,那么这个Lambda表达式的目标类型就是这个函数式表达式(参数类型),这个Lambda表达式就是这个函数式表达式的实例。
与匿名内部类一样,Lambda的结果是被当成对象,所以Lambda表达式完全可以赋值给一个变量,Lambda的目标类型是不确定哪个函数式表达式,只要Lambda实现的方法在形参上与某一个函数式表达式的抽象方法形参一样,就能将该Lambda表达式赋值给这个函数式接口类型的变量,还是看看下面的例子:

Runnable runn = () -> {        System.out.println("Lambda表达式的目标类型是Runnable");    };

看上面的代码,因为Runnable接口只有一个无参的抽象方法,并且Lambda表达式的匿名方法也是实现了一个无参的方法,所以可以赋值给Runnable类型的变量,所以说上面的的Lambda表达式创建了一个Runnable的对象。那么,我们可不可以将该Lambda表达式的结果可不可以赋值给Object的,不是说所有的对象的父类型都是Object类型的嘛,在Lambda中这样赋值是不可以的,我们总结了Lambda表达式的两个限制

  1. Lambda表达式的目标类型必须是明确的函数式接口。意思就是Lambda表达式只能赋值给一个确定的函数式接口类型的变量。
  2. Lambda只能为函数式接口创建对象,不能是其他的类。因为Lambda只能实现一个抽象方法,因此他只能为只有一个抽象方法的接口(函数式接口)创建对象

    Object obj = () -> {

        System.out.println("Lambda表达式的目标类型是Runnable");};

上面的代码我们将Lambda表达式赋值给了一个Object类型的obj,这段代码在编译阶段就会报错,提示“The target type of this expression must be a functional interface”,就是该表达式的目标类型必须是一个函数式接口。而Object并不是函数式接口,因此上端代码报错。
为了保证函数式表达式的目标类型是一个明确的函数是表达式,有以下三种方式:

  • 将Lambda表达上赋值给明确的函数式接口类型的变量。
  • 将Lambda表达式作为函数式接口类型的参数传给每个方法。
  • 使用函数式接口对Lambda进行强制类型转换。

因此若是将Lambda的表达式赋值给Object类型的变量,看下面的代码
Object obj = (Runnable)() -> {

        System.out.println("Lambda表达式的目标类型是Runnable");    };

上面讲Lambda表达式强制转换为Runnable类型,这样就可以确定Lambda表达式的的目标类型为Runnable函数式表达式。

总结下这节的重要知识点:需要注意的是Lambda表达式的目标类型完全可能是变化的,不是唯一确定的,但是前期是“Lambda表达式实现的匿名方式与目标类型(函数式接口)中唯一的抽象方法有相同的形参列表”

Lambda的方法引用和构造器引用

我们最后在来学习下当Lambda表达式的匿名函数只有一条语句时的再次简化。上面我们说过当Lambda表达式的匿名方法只有一条语句时可以省略花括号和当有返回值也可以省略return关键字。除此之外我们还可以使用方法的引用和构造器的引用,使Lambda表达式更加的简洁。注意是使整个表达式进行简化,不是简化匿名方法

方法的引用

  1. 引用类方法。
  2. 引用特定对象的实例方法。
  3. 引用某类对象的实例方法

这部分使用概念不好描述,我直接使用代码来演示

1 引用类方法

格式是: 类名::类方法名

 @FunctionalInterface interface Converter{  /**   *  功能是将一个字符串转换为一个Integer   * @param str   传入的String类型参数   * @return  返回转换Integer类型的结果   */ Integer converter(String str);}

定义了一个函数式接口,注解@FunctionalInterface用来表明这个接口是一个函数式接口,该接口有一个converter()抽象方法。下面使用Lambda表达式来创建一个该接口的对象。

      Converter converter =  str->Integer.valueOf(str);      converter.converter("123");  //输出整数123

上面的代码相信都可以理解,该Lambda表达式因为只有一个参数,和方法只有一条语句,就省略了圆括号和花括号,并且该方法有返回值,省略了return关键字。上面的Lambda表达式的方法是调用了Integer类的valuesOf()方法,我们将该方法的实现使用类方法的引用进行简化,如下代码

    // Converter converter = str->Integer.valueOf(str);    //类方法引用代替Lambda表达式    Converter converter = Integer::valueOf;    //函数式接口被实现的方法参数全部传递给该类方法作为参数    converter.converter("123");

如上代码,Lambda被简写为 “Integer::valueOf”,可以看为三部分,中间是两个冒号::,左边是一个类名,右边是类的方法名。好,开始分析下这段代码,当调用converter方法时,就会调用Lambda表达式的代码块部分,传入了参数”123”,该参数就会传入Integer的valueOf方法的参数。

2 引用特定对象的实例方法

格式是:特定对象::实例方法名

//使用Lambdab表达式创建Converter对象    Converter con = str->"123".indexOf(str);    Integer index = con.converter("2");    System.out.println(index); //输出1

上面Lambda表达式的代码块只有一条str->”123”.indexOf(str)语句,所以可以使用如下方法引用来进行替换。

Converter con = "123"::indexOf;

对于上面的实例方法引用,也是调用”123”的indexOf()方法来实现Converter函数式接口中唯一的抽象方法,当调用Converter接口中唯一的抽象方法时,调用参数会传给”123”对象的indexOf()实例方法。

3 引用某类对象的实例方法

格式: 类名::类方法名

下面来看第三种方法引用:引用某类对象的实例方法一样,先来看代码

@FunctionalInterfaceinterface SubString {/** * 作用是将传入的参数str,从start到end进行截取 *  * @param str * @param start * @param end * @return 返回截取新的字符串 */String sub(String str, int start, int end);}

定义了一个函数式接口SubString,包含一个抽象方法sub,作用是将传入的第一个参数进行从start到end的截取。
// 使用Lambda表达式来创建SubString对象
SubString sub = (str, start, end) -> str.substring(start, end);
// 调用SubString的方法
String string = sub.sub(“String”, 0, 2);
// 输出”St”
System.out.println(string);
使用Lambda创建了一个SubString类型的sub对象,然后调用sub方法,因为sub对象是Lambda表达式创建的,所以执行Lambda的代码块,返回St。下面看看使用方法引用来进行替换

    // 方法引用代替Lambda表达式    SubString sub = String::substring;`

对于上面的实例发那个发引用,也就是调用某个String对象的substring实例方法来实现SubString函数式接口中唯一的抽象方法,当调用SubString函数式接口的抽象方法时,第一个参数将作为substring方法的调用者,剩下的参数将作为substring实例方法的参数。

4 引用构造器
例如定义了如下一个类和函数式接口

    package com.gh;/** * 函数式接口 * @author Administrator * */    interface CreatePerson {     //用来创建一个Person对象    Person create(String name);}public class Person {    String name;public Person(String name) {    super();    this.name = name;}@Overridepublic String toString() {    return "Person [name=" + name + "]";}public static void main(String[] args) {    //使用Lambda创建一个CreatePerson类型的对象    CreatePerson createPerson = (String name) -> new Person(name);    //调用CreatePerson接口唯一的方法,并且传入参数"gh"    Person person = createPerson.create("gh");    System.out.println(person);  //输出新建对象的信息   } }

上面的代码创建了一个CreatePerson的函数式接口,接口中有一个create()方法;还创建了一个Person类,该类有个有参构造器。再来看看main方法,先是使用Lambda表达式创建了一个CreatePerson类型的对象createPerson,然后该对象调用方法create(),因为Lambda表达式创建了createPerson对象,所以执行Lambda的代码块,返回一个Person对象,像这种代码块式执行了某类的构造器时,我们可以使用引用构造器的方式来进行简写,如下

public static void main(String[] args) {    //使用Lambda创建一个CreatePerson类型的对象    //CreatePerson createPerson = (String name) -> new Person(name);    //使用引用构造器的方式简写Lambda表达式    CreatePerson createPerson = Person::new;    //调用CreatePerson接口唯一的方法,并且传入参数"gh"    Person person = createPerson.create("gh");    System.out.println(person);  //输出新建对象的信息}

上面使用构造器引用代替了Lambda表达式,对于上面的构造器引用,就是调用Person的构造器来实现CreatePerson函数式接口中的抽象方法,当调用CreatePerson函数式接口中的抽象方法时,参数会传递给Person构造器。从上面的程序来看,当调用CreatePerson对象的create抽象方法时,传入了一个String类型的参数,该参数会传给Person构造器,这就却动了Person类中,带一个String参数的构造器。

总结

好了上面就是Lambda的所有内容了,要是总结的不好和不对,请大家指出