java三目表达式_Java8新特性Lambda表达式

java三目表达式_Java8新特性Lambda表达式_第1张图片

1 Lambda表达式

对于很多计算机语言来说,Lambda表达式并不是陌生的语法格式,而对于Java而言,它的到来比较晚,直到Java8更新之后,Lambda表达式才正式出现在Java语法中。所以如果我们想在Java程序中使用Lambda表达式,我们必须将JDK升级到Java8的版本。

Lambda表达式它是基于数学中的λ演算而得名,也写作λ表达式,Lambda表达式表示一个闭包或者匿名函数(方法)。在不同的计算机语言中,Lambda表达式的符号各有差异。

Lambda表达式在Java中的主要作用是为了解决匿名类中匿名方法实现的问题(来自Oracle的官方文档的说法)。我们知道在接口使用上,通常是使用自定义类(或内部类)的方式来实现接口。而Lambda表达式属于一种匿名类的实现方式,但前提条件是,该接口必须是函数接口才能使用Lambda表达式。下面我们用一个例子来对比一下,接口的普通实现方式和Lambda实现方式。

java三目表达式_Java8新特性Lambda表达式_第2张图片

上述示例中采用Lambda表达式实现了Add接口,它的代码更加简洁,这也是使用Lambda表达式的重要原因。

1.1 函数式接口

函数接口,我们也称它为SAM接口(Single Abstract Method interfaces),中文的解释就是"单个抽象方法接口"。要成为函数接口,它的前提条件必须是一个接口才可以,抽象类是不允许的(即使符合单个抽象方法的抽象类也不可以)。函数式接口在定义上与普通接口的语法规则是一样的,但一个接口要想成为函数式接口需要具备以下几个条件:

· 接口中只能有一个抽象方法。

· 接口中除了一个抽象方法外,可以含有默认方法(default方法)和静态方法。因为方法和静态方法都是实现方法,并不影响接口中抽象方法的个数。

· 接口中可以拥有Object类中同名的抽象方法。

在定义一个函数式接口时,如果无法确认自定义的接口是否符合函数式接口要求,我们可以借助Java为我们提供的注解"@FunctionalInterface"来进行验证,如下面示例所示:

java三目表达式_Java8新特性Lambda表达式_第3张图片

在接口定义上添加"@FunctionalInterface"注解,在编译的时候会对接口进行检查,如果不符合函数式接口的定义规则,编译时会报错。

1.2 语法结构

Lambda表达式究竟是什么?从第一个Lambda表达式的示例上来看,Lambda表达式是一个可以向接口对象赋值的语句。在Lambda表达式中,它没有像匿名类那样去实现一个接口,省略了部分语法格式。同时Lambda表达式还省略了接口中的抽象方法,以及方法中的参数类型,而是在特殊符号后直接编写实现语句(如下图所示)。

java三目表达式_Java8新特性Lambda表达式_第4张图片

Lambda表达式组成部分

从赋值的角度上来说,我们可以将Lambda表达式看成一个对象,它会根据引用来推导出Lambda的实现(但必须保证该接口是一个函数接口如上图中的Add接口必须是一个函数接口,只有函数式接口才能通过Lambda表达式进行推导)。

一个Lambda表达式由参数部分和实现部分组成,中间用特殊符号连接符号"->"进行链接。在使用的时候,可根据具体环境继续简写。

1.参数部分:使用括号"()"代表参数列表。如果只有一个参数,可以省略括号。多个参数应使用逗号进行分割,要特别注意没有参数的表达式不能省略括号。括号中的参数类型可以添加、可以省略,省略时它会自己进行推导,但必须保证与接口方法的参数顺序一致。

2.用大括号"{}" 代表方法体(如果方法内只有一条语句,可以省去),括号内书写方法内容。如果存在返回值,且方法内只有一条语句时,可以省略return关键字,但该语句表达式必须能够表达出返回值的含义。

java三目表达式_Java8新特性Lambda表达式_第5张图片

Lambda表达式简略写法

1.3 目标类型推导

在使用Lambda表达式的时候,Lambda表达式以赋值的形式传递给接口引用,这时就需要Lambda表达式进行自动推导。例如在"Add add=(a,b)->a+b;"这句代码中,它首先要推导出Lambda表达式的主体是函数式接口"Add"的实现,其次匹配参数,参数"a"和"b"必须与接口中的唯一抽象方法数量匹配,并自动匹配参数类型。最后匹配表达式是否与抽象方法的返回类型相符。这些推导在编译的时候会检查。

Lambda表达式在编写的时候要注意以下四点:

1.引用接口必须是函数式接口。

2.表达式和接口抽象方法的参数数量、类型和位置上必须一一对应。

3.Lambda表达式中返回值与接口抽象方法的返回类型相同(类型可兼容,可返回类型的子类)。

4.Lambda表达式内抛出的异常和接口抽象方法throws的类型相同或兼容。

Lambda表达式在很多场景中都可以使用,例如在下述环境中,Lambda表达式可以根据上下文环境进行推导。

1.返回语句推导:同样在方法内的返回语句中,可以从方法的返回类型上进行推导。下面示例中,Person类中需要返回一个Comparator外比较器对象,我们可以直接在return语句中使用Lambda表达式来实现(两个表达式效果一样)。

java三目表达式_Java8新特性Lambda表达式_第6张图片

Lambda返回语句推导

2.数组初始化器推导:在创建数组初始化时,也可以使用Lambda表达式的进行类型的推导(必须是符合函数接口的类型组成的数组,如Runnable(线程),FileFilter(标准IO中的文件过滤器等,这些接口在Java8中,都被注解了函数式接口)。

在下面示例中,我们定义的NumberRandom接口,包含了一个用于生成随机数的抽象方法。当我们创建NumberRandom接口数组时,我们可以使用Lambda表达式进行数组的创建。

java三目表达式_Java8新特性Lambda表达式_第7张图片

Lambda表达式应用于数组初始化

在上述示例中,当数据元素初始化的时候,我们没有声明个数,而是采用了直接赋值的方式进行数组初始化。给数组赋值的每一个元素都由Lambda表达式构成,由此可见Lambda可以推导出正确的数组元素类型。

3.方法和构造方法参数推导:当一个函数式接口作为参数时,我们也可以直接使用Lambda表达式,编译的时候会推导出符合参数的类型。在下面示例,自定义类FindFile中设置了文件过滤器,来完成文件查找时的过滤功能,我们可以通过Lambda表达式来传递参数。

java三目表达式_Java8新特性Lambda表达式_第8张图片

Lambda表达式作为参数使用

在设置文件过滤器时(行11), setFileFilter方法的参数是FileFilter,这时表达式被推导为FileFilter。

4.Lambda函数体推导:大多数情况下,Lambda表达式的类型是比较明确的,例如在没有重载方法的情况下,方法的参数的类型是明确的,所以Lambda表达式能够清楚的知道自己应该推导出什么类型,我们也称这样为"显式类型"。

但如果方法是重载方法,这个时候Lambda表达式被推导成什么类型完全是由参数决定的。这时表达式作为参就无法自行推导,而是去依赖表达式中的参数数量进行推导,我们称这样的方式为"隐式类型"。在下面示例中,我们可以用参数的个数或类型来推导出函数的类型。

java三目表达式_Java8新特性Lambda表达式_第9张图片

Lambda函数推导

在上述示例中,方法setFunction有两个重载方法,分别需要提供FileFilter和Runnable接口对象。恰好这两个接口对都是函数式接口,表达式的推断就要根据表达式中的参数来确定。

而在极端情况下,参数的个数都一样,就会存在分歧(二义性),这个时候我们就需要将Lambda进行转型。例如下面示例中,参数的数量也无法推导出Lambda表达式的类型,我们只能通过强制转化告诉虚拟机该如何推导。

java三目表达式_Java8新特性Lambda表达式_第10张图片

明确推导类型

5.三目表达式推导:在三目表达式中,也可以根据条件返回的分支,将类型推导出来。在下面示例所示。

java三目表达式_Java8新特性Lambda表达式_第11张图片

Lambda表达式应用于三目表达式中

6.转型表达式推导:在很多场景下,我们都会使用替换原则,用父类引用子类对象。这时Lambda无法推导出父类型,就不能被父类所引用。在这种情况下我们就需要对Lambda进行强制转型,告诉Lambda表达式要推导的类型,如下面示例所示。

java三目表达式_Java8新特性Lambda表达式_第12张图片

Lambda表达式转型

通常情况下,任何一个引用类型对象赋值给Object引用都是正常的操作,但在Lambda表达式中,Lambda会根据引用进行推导,如果引用对象是Object,它是无法推导出来的,这时我们可以在Lambda表达式前加上一个类型转化,表示Lambda表达式按指定类型进行推导。这种使用方式虽然与重载函数的使用相似,但意义是完全不同的。

2 变量的作用域

通常情况下Lambda表达式可以看做一个方法中内部类的实现,那么这就涉及到了一个问题——变量的操作。Lambda表达式除了可以操作自身定义的变量外,也可以操作表达式的外部变量,这一点它与内部类是相同的(如下示例所示):

java三目表达式_Java8新特性Lambda表达式_第13张图片

Lambda操作成员变量和局部变量

实例运行结果:

java三目表达式_Java8新特性Lambda表达式_第14张图片

在上述示例中,Lambda表达式在类的实例方法中,它可以操作该类的所有成员变量。如果Lambda操作的是表达式外部的局部变量,那么该变量只能是进行读取操作(默认成为final变量),不能进行赋值操作。

在this关键字的处理上,Lambda和内部类是不同的。内部类中this关键字不是外部类的强引用,而是内部类自身的强引用。在Lambda中,this关键字是外部类的强引用,在下面示例中,我们对两种引用做了对比。

java三目表达式_Java8新特性Lambda表达式_第15张图片

Lambda和内部类中this关键字的强引用

从上述示例中,我们可以看到,内部类Inner中出现的this关键字是自身的强引用,如果要获取外部类的强引用,需要使用"外部类.this"引出。而Lambda表达式中的this关键字就是当前类的强引用。

关于Lambda表达式的基础应用,就讲到这里。下面我们继续介绍Lambda表达式中方法引用的使用Lambda表达式——方法引用。

你可能感兴趣的:(java三目表达式,lambda表达式)