Lambda 表达式
在讨论Lambda的相关内容前,我们先回顾一下有关Java(Interface)接口的内容。
一、Java接口的定义方式
传统的Java接口定义
Java接口是一个抽象类型,是抽象方法的集合
- 抽象方法:只有定义而没有主体的方法。
- 无属性:不能在接口中定义变量。
public interface MyInterface{
String getName();
int getAge();
void hello();
}
新型的Java接口定义
从Java8开始,接中的定义有了更加丰富的内容
- 允许在接口内声明静态变量。
- 可以定义静态(static)方法和默认实现(default)方法。
public interface MyInterface{
//获得名字
default String getName(){
return "Default Name";
}
//打招呼
void hello();
}
二、接口的实现方式
以声明类的方式实现
public class MyImpl implements MyInterface{
@Override
public void hello(){
System.out.println("Hello world");
}
}
public class MyService{
/**自我介绍*/
public void introduce(MyInterface imy){
imy.hello();
}
public void myMethod(){
//某人介绍自己
MyInterface someOne = new MyImpl();
this.introduce(someOne);
}
}
以匿名内部类的方式实现
public class MyService{
/**自我介绍*/
public void introduce(MyInterface imy){
imy.hello();
}
public void myMethod(){
//张三介绍自己
MyInterface zhangSan = new MyInterface(){
@Override
public void hello(){
System.out.println("Hello,I'm ZhangSan");
}
};
this.introduce(zhangSan);
//李四介绍自己
this.introduce(new MyInterface(){
@Override
public void hello(){
System.out.println("Hi,I'm LiSi");
}
});
}
}
三、Java的Lambda表达式
() -> {}
- Lambda表达式并不是Java独有的,C#、Ruby、Python等语言都使用了Lambda表达式。
- 在Java中Lambda表达式也是一种对象,是匿名内部类的快速表达方式,使用时忽略接口名和方法名,只关注方法实现。
- 是Java的函数式编程方式。
面向对象(OOP)编辑和函数式编程(FP)
面向对象编程(关注谁来做)
解决问题的途径是找到一个具有方法的对象,将对象在流程中进行传递(传入或返回),比如上面的例子,想要进行介绍(introduce)必须找到一个具有hello()能力的对象传入。
//传入接口的new实例 this.introduce(new MyInterface(){ @Override public void hello(){ System.out.println("Hi,I'm LiSi"); } });
函数式编程(关注怎么做)
解决问题的途径是找到可用的方法,把方法(函数)在流程中进行传递(传入或返回),比如上面的例子中,不需要某个特定的实现对象,只要传入方法的实现就行。
//传入方法实现 this.introduce(()->System.out.println("Hello,I'm ZhangSan"));
Lambda表达式的写法
格式:(类型1 变量名2)3 ->4 {函数体}5
- 类型声明1可省略,编译器会自动推断参数类型
- ->4是不可省略的
- 当()3中只有一个参数,()3可以省略;如果是无参数的表达式,()3不可省略
- 当函数体只有一行,{}5可以省略
- 如果是有返回值的函数,当函数体只有一行,return也可以省略
比如(int a)->{return a*10;}
可以写成 a->a*10
以Lambda表达式的方式实现函数式接口
public class MyService{
/**自我介绍*/
public void introduce(MyInterface imy){
imy.hello();
}
public void myMethod(){
//张三介绍自己
this.introduce(()->System.out.println("Hello,I'm ZhangSan"));
//李四介绍自己
this.introduce(()->System.out.println("Hi,I'm LiSi"));
//啥也不说
this.introduce(()->{}); //对于空函数体,{}不能省略
}
}
如何定义一个函数式接口
-
有且只有一个抽象方法的接口就符合函数式接口的定义
这并不是说接口只能有一个方法,在第一节提到新型Java接口定义中接口,方法可以通过default或static在接口内进行实现,这样可以使接口内可以有多个方法而只有唯一的抽象方法。
-
可以使用*@FunctionalInterface*注解验证是否是函数式接口
当一个接口不符合函数式接口的定义规范时,加上@FunctionalInterface注解编译器会报错,比如下面的情况。
@FunctionalInterface public interface MyFunctionalInterface{ void toDoSomething(); String getSomethine(); }
如何定义一个可以接收Lambda表达式的方法
很简单,只要方法的参数是一个函数式接口,这个方法就可以接受Lambda表达式。那么是不是想用Lambda表达式就必需要自定义函数式接口呢,并不用,Java已经给我们准备好了很多的函数式接口供我们直接使用。
四、Java的函数式接口
下面列举几个常用的Java函数式接口
接口名称 | 接口方法 | 说明 |
---|---|---|
Consumer<T> | void accept(T t) | 接受一个输入参数,操作后无返回结果 |
Function<T,R> | R apply(T t) | 接受一个输入参数,操作后返回一个结果 |
Predicate<T> | boolean test(T t) | 接受一个输入参数,操作后返回一个布尔值结果 |
Supplier<T> | T get() | 无参数,操作后返回一个结果 |
下面看一下这几个接口的应用场景
Consumer接口
Consume<T>接口是一个消费者接口,它接受一个输入对象,但不产生返回对象。
场景:
-
用户信息类UserInfo保存了用户的个人信息
-
按需求将List中的元素进行格式化
class UserInfo {
private String name;
private String nickname;
private int age;
// ...更多属性
}
public void testFunction(){
List<UserInfo> list = Arrays.asList(
new UserInfo("张三","Zhang",23),
new UserInfo("李四","Lee",24),
new UserInfo("王五","King",25)
);
//姓名之后加昵称
//昵称英文大写
}
姓名之后加昵称
//姓名加昵称 void mixName(){ for(UserInfo u: list){ u.setName(u.getName()+" "+u.getNickname()); } }
昵称英文大写
//昵称英文大写 void upperCaseNickname(){ for(UserInfo u: list){ u.setNickname(u.getNickname().toUpperCase()); } }
如果还有更多排序需求呢…
下面我们采用Consumer函数接口的方式,List接口的forEach方法是一个参数为Consumer的函数接口
//姓名加昵称
list.forEach(u->u.getName()+" "+u.getNickname());
//昵称英文大写
list.forEach(u->u.getNickname().toUpperCase());
Function接口
Function<T,R>接口是一个类型转换接口,它接受一个输入对象并返回不同类型的对象。
场景
- 用户信息类UserInfo保存了用户的个人信息
- 按需求把一个列表(List)中用户的信息提取成另一个单独的列表
class UserInfo {
private String name;
private String nickname;
private int age;
// ...更多属性
}
public void testFunction(){
List<UserInfo> list = Arrays.asList(
new UserInfo("张三","Zhang",23),
new UserInfo("李四","Lee",24),
new UserInfo("王五","King",25)
);
//提取姓名
//提取昵称
//提取年龄
}
以往的处理方法
如果需要从用户列表中提取所有姓名,那么就要定义方法
private List<String> extractNameList(){ List<String> strList = new ArrayList<>(); //查询用户列表 List<UserInfo> list = ... for (UserInfo userInfo : list) { strList.add(userInfo.getName()); } return strList; }
List<String> nameList = extractNameList();
如果需要从用户列表中提取所有昵称,那么就再要定义方法
private List<String> extractNicknameList(){ List<String> strList = new ArrayList<>(); //查询用户列表 List<UserInfo> list = ... for (UserInfo userInfo : list) { strList.add(userInfo.getNickname()); } return strList; }
List<String> nameList = extractNicknameList();
如果需要从用户列表中提取所有年龄,那么就还要定义方法
private List<Integer> extractAgeList(){ List<String> strList = new ArrayList<>(); //查询用户列表 List<UserInfo> list = ... for (UserInfo userInfo : list) { strList.add(userInfo.getAge()); } return strList; }
List<String> nameList = extractAgeList();
如果还有更多属性需求呢…
当我们采用Function函数接口的解决方式,结合泛型只需要定义一个方法就可以解决
private <T> List<T> extractUserProp(Function<UserInfo,T> func){
List<T> strList = new ArrayList<>();
//查询用户列表 List<UserInfo> list = ...
for (UserInfo userInfo : list) {
strList.add(func.apply(userInfo));
}
return strList;
}
//提取姓名
List<String> nameList = extractUserProp(u->u.getName());
//提取昵称
List<String> nicknameList = extractUserProp(u->u.getNickname());
//提取年龄
List<Integer> ageList = extractUserProp(u->u.getAge());
//更多的属性也能处理
Predicate接口
Predicate<T>接口是一个判断接口,它接受一个输入对象在执行后返回布尔值(boolean)。
场景
- 用户信息类UserInfo保存了用户的个人信息
- 按需求过滤列表中的记录并返回
class UserInfo {
private String name;
private String nickname;
private int age;
// ...更多属性
}
以往的处理方法
如果我们要按年龄过滤
public List<UserInfo> filterByAge(int min,int max){ List<UserInfo> list = Arrays.asList( new UserInfo("张三","Zhang",23), new UserInfo("李四","Lee",24), new UserInfo("王五","King",25) ); List<UserInfo> flist = new ArrayList()<>; for(UserInfo userinfo: list){ // 比较年龄是否符合条件 if(userinfo.getAge()>=min&&userinfo.getAge()<=max){ flist.add(userinfo); } } return flist; }
通过传入年龄范围来实现过滤,这种方式是最"偷懒"的实现了
//假如我只想要23岁以上的呢,接口必需要两个值,最大年龄传999吧 List<UserInfo> list = filterByAge(23,999); //假如我只想要60岁以下的呢,最小年龄传0吧,18似乎也行 List<UserInfo> list = filterByAge(0,60);
能用,但不优雅
下面我们使用Predicate接口的解决方案
private List<UserInfo> filterBy(Predicate<UserInfo> p){
List<UserInfo> list = Arrays.asList(
new UserInfo("张三","Zhang",23),
new UserInfo("李四","Lee",24),
new UserInfo("王五","King",25)
);
List<UserInfo> flist = new ArrayList<>();
for (UserInfo userInfo : list) {
if(p.test(userInfo)){
flist.add(userInfo);
}
}
return flist;
}
//23岁以上的人
List<UserInfo> list = filterBy((u)->u.getAge()>23);
//60岁以下的
List<UserInfo> list = filterBy((u)->u.getAge()<60);
//姓张的
List<UserInfo> list = filterBy((u)->u.getName().startsWith("张"));
//更多你想要的条件都可以
....
五、方法引用(::)
作用
将已定义好的方法引用为Lambda表达式。
用法
前面用到的Lambda表达式都是定义匿名内部类,Java对于类中已经声明的方法也可以引用为Lambda表达式,方法是通过双冒号(::)操作符。双冒号操作符的用法有以下几种
- 静态方法引用(类名::方法名)
比如前面Predicate接口的例子中,如果我们之前已经有一个业务方法来判断是否大于60岁
public class UserInfoUtil{
public static boolean isLargeThan60(UserInfo u){
return u.getAge()>60;
}
}
那么我们就可以直接使用这个方法
List<UserInfo> list = filterBy(UserInfoUtil::isLargeThan60);
- 实例方法引用(变量名::方法名)
public class UserInfoServiceImpl{
private boolean isLargeThan60(UserInfo u){
return u.getAge()>60;
}
private List<UserInfo> filterBy(Predicate<UserInfo> p){
List<UserInfo> list = Arrays.asList(
new UserInfo("张三","Zhang",23),
new UserInfo("李四","Lee",24),
new UserInfo("王五","King",25)
);
List<UserInfo> strList = new ArrayList<>();
for (UserInfo userInfo : list) {
if(p.test(userInfo)){
strList.add(userInfo);
}
}
return strList;
}
public List<UserInfo> findUser(){
List<UserInfo> list = filterBy(this::isLargeThan60);
//List<UserInfo> list = filterBy(new UserInfoServiceImpl()::isLargeThan60);
return list;
}
}
- 特定类型任意对象实例方法引用
解释:
- 函数接口抽象方法的参数比引用的方法参数多一个
- 函数接口抽象方法的第一个参数是引用方法的对象实例
Function接口的例子
private <T> List<T> extractUserProp(Function<UserInfo,T> func){
//实现代码
}
//原来的写法
List<String> nameList = extractUserProp(u->u.getName());
/**改用双冒号的写法*/
List<String> nameList = extractUserProp(UserInfo::getName);
用到了UserInfo.getName(),getName()方法并不是一个静态方法,为什么可以用 类名::方法名 的方式引用
/**
*引用方法UserInfo.getName()是一个Supplier接口,方法:T get() 无参数
*extractUserProp的参数是一个Function接口,方法:R apply(T t) 比get方法多一个参数
*函数定义u->u.getName() 第一个参数u是UserInfo的实例
*/
//UserInfo::getName写作Funciton类型与u->u.getName()等价
Function<UserInfo, String> getName = UserInfo::getName;
Function<UserInfo, String> getName2 = u->u.getName();