Lambda表达式总结
一、使用范例以及例子
使用匿名内部类:
1 | Comparator<Integer>com = new Comparator<Integer>() { |
使用Lambda
表达式:
1 | Comparator<Integer> com = (x, y) -> Integer.compare(y, x); |
下面给出一个例子来引入Lambda
表达式。
给出一个Employee
类,有name、age、salary
三个属性:
1 | public class Employee { |
然后我们需要通过限制查询数据:
- 比如查询年龄
>25
岁的所有员工的信息; - 再如查询工资
>4000
的员工信息;
首先给出一个List
集合类模拟数据库表:
1 | //将数组转换成集合的 |
1、原始方法
然后我们写分别查询出年龄大于25
岁的员工信息和工资大于4000
的员工信息,发现findEmployeesByAge
和findEmployeesBySalary
两个方法代码非常的相似,只有查询条件不同,所以这个方法是不太可取的。
1 | public void test3(){ |
2、优化方式一-使用策略模式来优化
策略模式需要行为算法族,于是我们创建查询行为的接口MyPredicate<T>
:
1 | public interface MyPredicate <T>{ |
并创建相关的实现类代表不同的算法行为: (分别是年龄 > 25
和工资> 4000
的 ):
1 | public class FilterEmployeeByAge implements MyPredicate<Employee> { |
1 | public class FilterEmployeeBySalary implements MyPredicate<Employee>{ |
这时我们可以只需要创建通用的方法: 具体的调用只需要传入具体的实现类(接口作为参数)
1 | public List<Employee> filterEmployees(List<Employee>list,MyPredicate<Employee>mp){ |
测试的时候就传入两个不同的类,来指定查询的行为
1 | //优化方式一 : 使用策略设计模式进行优化 下面的方法只要写一个 |
3、优化方式二-使用匿名内部类优化
这样的好处在于不需要创建接口的具体的实现类,(但是还是需要MyPredicate
接口和filterEmployees()
方法):
1 | //优化方式二 : 使用匿名内部类 这样的好处是不要创建一个额外的 策略类 |
4、优化方式三-使用Lambda表达式
省去匿名内部类的没用的代码,增强可读性:(注意还是需要那个filterEmployees()
方法和MyPredicate
接口)
1 | public void test6(){ |
5、优化方式四-使用Stream-API
使用StreamAPI
完全不需要其他的代码,包括不需要filterEmployees()
方法,代码很简洁:
1 | public void test7(){ |
二、Lambda表达式基础语法
关于箭头操作符:
Java8
中引入了一个新的操作符,"->"
,该操作符称为箭头操作符或者Lambda
操作符,箭头操作符将Lambda
表达式拆分成两部分;- 左侧:
Lambda
表达式的参数列表,对应的是接口中抽象方法的参数列表; - 右侧:
Lambda
表达式中所需要执行的功能(Lambda
体),对应的是对抽象方法的实现;(函数式接口(只能有一个抽象方法)) Lambda
表达式的实质是 对接口的实现;
语法格式:
(一)、接口中的抽象方法 : 无参数,无返回值;
例如: Runnable
接口中的run
方法:
1 | public void test1(){ |
(二)、接口中的抽象方法 : 一个参数且无返回值; (若只有一个参数,那么小括号可以省略不写)
1 | public void test2(){ |
(三)、两个参数,有返回值,并且有多条语句 : 要用大括号括起来,而且要写上return
1 | public void test3(){ |
输出:
1 | 函数式接口 |
(四)、两个参数,有返回值,但是只有一条语句: 大括号省略,return
省略
1 | public void test4(){ |
输出:
1 | [1, 2, 4, 5, 8] |
(五)、 Lambda
表达式的参数列表的数据类型 可以省略不写,因为JVM编译器通过上下文推断出数据类型,即”类型推断”, (Integer x,Integer y ) -> Integer.compare(x,y)
可以简写成(x,y) -> Integer.compare(x,y)
;
1 | 上联: 左右遇一括号省 |
三、函数式接口
- 若接口中只有一个抽象方法的接口称为函数式接口;
- 可以使用注解
@FunctionlInterface
来标识,可以检查是否是函数式接口;
例子: 对一个进行+-*/
的运算:
函数式接口:
1 | //函数式接口 |
通用函数:
1 | public Integer operation(Integer num,MyFunction mf){ |
测试:
1 | public void test5(){ |
四、Lambda练习
1、练习一-Employee
类中先按年龄比,年龄相同按照姓名比-都是升序
先给出集合,模拟数据库表:
1 | List<Employee> employees = Arrays.asList( |
1 | public void test1(){ |
输出:
1 | name='张三', age=23, salary=3333.33 |
2、练习二-声明一个带两个泛型的接口,并且对两个Long
型数值计算
1 |
|
对应函数和测试:
1 | public void test3(){ |
更多的例子: (取自<<
Java8实战>>
)
根据上述语法规则,以下哪个不是有效的Lambda表达式?
(1) () -> {}
(2) () -> “Raoul”
(3) () -> {return “Mario”;}
(4) (Integer i) -> return “Alan” + i;
(5) (String s) -> {“IronMan”;}
答案:只有4和 5是无效的Lambda。(1) 这个Lambda没有参数,并返回void。 它类似于主体为空的方法:public void run() {}。
(2) 这个Lambda没有参数,并返回String作为表达式。
(3) 这个Lambda没有参数,并返回String(利用显式返回语句)。(4) return是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示:
(Integer i) -> {return "Alan" + i;}
。(5)“Iron Man”是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括号和分号,如下所示:
(String s) -> "Iron Man"
。或者如果你喜欢,可以使用显式返回语句,如下所示:(String s)->{return "IronMan";}
。
(注意类型可以省略(类型推导)。
下面是一些使用示例:
上图的Apple
类:
1 | public class Apple { |
五、Java8四大内置函数式接口
我们发现,如果使用Lambda
还要自己写一个接口的话太麻烦,所以Java
自己提供了一些接口:
Consumer< T >con
消费性 接口:void accept(T t)
;Supplier< T >sup
供给型接口 :T get()
;-
Function< T , R >fun
函数式接口 :R apply (T t)
; -
Predicate< T >
: 断言形接口 :boolean test(T t)
;
1、Consumer< T >con
消费性接口-void accept(T t)
1 |
|
2、Supplier< T >sup
供给型接口-T get()
例子: 产生指定个数的整数,并放入集合中;
1 | public void test2(){ |
3、Function< T, R >fun
函数式接口- R apply (T t)
1 | public void test3(){ |
4、Predicate< T >
断言形接口-boolean test(T t)
判断一些字符串数组判断长度>2
的字符串:
1 | public void test4(){ |
六、方法引用和构造器引用
1、方法引用
使用前提: Lambda
体中调用方法的参数列表和返回值类型,要和函数式接口中抽象方法的参数列表和返回值类型保持一致;
1)、语法格式(一) 对象::实例方法名
1 | public void test1(){ |
注意,这样写的前提: Consumer
中的accept()
方法和println()
方法的参数列表和返回类型要完全一致:
再看一个例子:
三种写法的效果是一样的:
1 | public class TestLambda { |
再看一个例子:
1 | public static void main(String[] args) { |
再看一个例子:
1 | public void test2(){ |
2)、语法格式(二) 类名::静态方法
1 | public void test3(){ |
Integer
类中的
Comparator
接口中的方法:
3)、语法格式(三) 类::实例方法名
使用注意: 若Lambda参数列表中的第一个参数是实例方法的第一个调用者,而第二个参数是实例方法的参数时,可以使用ClassName :: method
。
1 | public void test4(){ |
2)、构造器引用
需要调用构造器的参数列表,要与函数式接口中的抽象方法的参数列表保持一致;
1 | public void test5(){ |
输出:
1 | name='null', age=0, salary=0.0 |
再看构造器一个参数的:
1 | public void test6(){ |
输出:
1 | name='zx', age=0, salary=0.0 |
如果想要匹配多个的,(两个的可以使用BiFunction
),下面看一个三个的:
例如想匹配这个:
1 | public class ComplexApple { |
自己建一个接口:
1 |
|
测试:
1 |
|