Lambda表达式
Lambda表达式简介
lambda表达式是从java8开始引入的,目的主要是为了简化代码,允许把函数作为⼀个⽅法的参数传递进⽅法中。在java8之前通常使用的是匿名类,但匿名类的问题是:如果匿名类的实现非常简单(例如仅包含一个方法的接口),则匿名类的语法可能看起来笨拙且不清楚,而且不能将一个函数(方法)作为参数传递给另一个方法。而使用Lambda表达式使您能够执行此操作,将功能视为方法参数,或将代码视为数据。
Lambda表达式是⼀种匿名函数(对Java⽽⾔这并不完全准确),通俗的说,它是没有声明的⽅法,即没有访问修饰符、返回值声明和名字的⽅法。使⽤Lambda表达式的好处很明显就是可以使代码变的更加简洁紧凑。Lambda表达式的使⽤场景与匿名类的使⽤场景⼏乎⼀致,都是在某个功能(⽅法)只使⽤⼀次的时候。
Lambda表达式语法
Lambda表达式通常使⽤(param)->(body)语法书写,基本格式如下:
//没有参数
() -> body
// 1个参数
(param) -> body
// 或 (param) ->{ body; }
// 多个参数
(param1, param2...) -> { body }
// 或 (type1 param1, type2 param2...) -> { body }
// ⽆参数,返回值为字符串“lambda”
() -> "lambda";
// 如果有1个String参数,直接打印结果
(System.out::println);
// 或
(String s) -> System.out.print(s)
// 有1个参数(数字),返回2倍值
x -> 2 * x;
// 有2个参数(数字),返回差值
(x, y) -> x – y
// 有2个int型整数,返回和值
(int x, int y) -> x + y
Lambda 表达式的结构总结如下:
- Lambda 表达式可以具有零个,一个或多个参数。
- 可以显式声明参数的类型,也可以由编译器自动从上下文推断参数的类型。例如
(int a)
与刚才相同(a)
。 - 参数用小括号括起来,用逗号分隔。例如
(a, b)
或(int a, int b)
或(String a, int b, float c)
。 - 空括号用于表示一组空的参数。例如
() -> 42
。 - 当有且仅有一个参数时,如果不显式指明类型,则不必使用小括号。例如
a -> return a*a
。 - Lambda 表达式的正文可以包含零条,一条或多条语句。
- 如果 Lambda 表达式的正文只有一条语句,则大括号可不用写,且表达式的返回值类型要与匿名函数的返回类型相同。
- 如果 Lambda 表达式的正文有一条以上的语句必须包含在大括号(代码块)中,且表达式的返回值类型要与匿名函数的返回类型相同。
Lambda用法案例
对字符串集合进行排序:
package com.lambda.demo;
import java.util.*;
/**
* @Author LQY
* @Date 2020/8/14
* @Description lambda表达式案例
*/
public class MyDemo {
/**
* java8之前对字符串集合进行排序常用操作
*/
public static void strListSortFun() {
System.out.println("======对字符串集合排序常规操作=========");
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
System.out.println("++++++++++排序前集合+++++++++++++++++++");
for (String name : names) {
System.out.println(name);
}
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
System.out.println("++++++++++排序后集合+++++++++++++++++++");
for (String name : names) {
System.out.println(name);
}
}
/**
* lambda对字符串集合进行排序
*/
public static void strListSortLambdaFun() {
System.out.println("======使用lambda对字符串集合排序操作=========");
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
System.out.println("++++++++++排序前集合+++++++++++++++++++");
//lambda表达式遍历集合的写法
//names.forEach(str -> System.out.println(str));
names.forEach(System.out::println);
// lambada第一种写法写法
// Collections.sort(names, (String a, String b) -> {
// return b.compareTo(a);
// });
// lambada第二种写法写法,当{}代码块里只有一行代码时大括号对{}和return关键字都省略不要
// Collections.sort(names, (String a, String b) -> b.compareTo(a));
System.out.println("++++++++++排序后集合+++++++++++++++++++");
// lambada第三种写法写法,Java编译器能够自动识别参数的类型,所以你就可以省略掉类型不写
Collections.sort(names, (a, b) -> b.compareTo(a));
names.forEach(System.out::println);
}
public static void main(String[] args) {
strListSortFun();
strListSortLambdaFun();
}
}
运行结果:
自定义函数式接口案例:
Lambda表达式如何匹配Java的类型系统?每一个lambda都能够通过一个特定的接口,与一个给定的类型进行匹配。一个所谓的函数式接口必须要有且仅有一个抽象方法声明。每个与之对应的lambda表达式必须要与抽象方法的声明相匹配。由于默认方法不是抽象的,因此你可以在你的函数式接口里任意添加默认方法。
任意只包含一个抽象方法的接口,我们都可以用来做成lambda表达式。为了让你定义的接口满足要求,你应当在接口前加上@FunctionalInterface 标注。编译器会注意到这个标注,如果你的接口中定义了第二个抽象方法的话,编译器会抛出异常。
package com.lambda.demo;
/**
* @Author LQY
* @Date 2020/8/14
* @Description 数值相加工具接口
*/
@FunctionalInterface
public interface INumberUtil<T> {
/**
* 求两数之和的方法
* @param param1 数1
* @param param2 数2
* @return
*/
T sumFun(T param1,T param2);
}
/**
* 使用自定义函数接口,用lambda方式计算两数之和
*/
public static void sumLambdaFun(){
System.out.println("======使用自定义函数接口,用lambda方式计算两数之和=========");
INumberUtil<Integer> numberUtil = (Integer num1,Integer num2)->Integer.sum(num1,num2);
Integer sumResult = numberUtil.sumFun(10,20);
System.out.println("=======计算结果为:"+sumResult+"========================");
}
运行结果:
使用::关键字
Java 8 允许你通过::关键字获取方法或者构造函数的的引用
列如改造上面的求两数和的方法,
将
INumberUtil<Integer> numberUtil = (Integer num1,Integer num2)->Integer.sum(num1,num2);
用双::改造为:
INumberUtil<Integer> numberUtil = Integer::sum;
如下:
/**
* 使用自定义函数接口,用lambda方式计算两数之和
*/
public static void sumLambdaFun(){
System.out.println("======使用自定义函数接口,用lambda方式计算两数之和=========");
//常规用法1:
//INumberUtil<Integer> numberUtil = (Integer num1,Integer num2)->Integer.sum(num1,num2);
//常规用法2:
INumberUtil<Integer> numberUtil = Integer::sum;
Integer sumResult = numberUtil.sumFun(10,20);
System.out.println("=======计算结果为:"+sumResult+"========================");
}
使用::关键字引用构造函数:
1.先创建一个对象实体(用户)
package com.lambda.demo;
/**
* @Author LQY
* @Date 2020/8/14
* @Description 用户对象
*/
public class User {
/**
* 用户名称
*/
private String userName;
/**
* 联系电话
*/
private String phone;
public User() {}
public User(String userName, String phone) {
this.userName = userName;
this.phone = phone;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
2.创建一个用户对象的工厂接口,用于生产用户对象
package com.lambda.demo;
/**
* @Author LQY
* @Date 2020/8/14
*/
@FunctionalInterface
public interface UserFactory<User> {
/**
* 创建用户
* @param userName 用户名称
* @param phone 联系电话
* @return
*/
User createUser(String userName,String phone);
}
3.使用案例:
/**
* 使用::关键字引用构造函数
*/
public static void referenceConstructorFun(){
System.out.println("======使用::关键字引用构造函数=========");
UserFactory<User> userUserFactory = User::new;
User user = userUserFactory.createUser("测试用户1","18312398810");
System.out.println("=====user info:"+user.toString()+"==================");
}
Lambda的变量范围
对于lambdab表达式外部的变量,其访问权限的粒度与匿名对象的方式非常类似。你能够访问局部对应的外部区域的局部final变量,以及成员变量和静态变量。
/**
* lambda范围
*/
public static void lambdaScope(){
final Integer num = 1;
//此地方引用了lambda表达式以外的final变量num
INumberUtil<Integer> numberUtil = (Integer num1,Integer num2) -> Integer.sum(num1,num);
//按代码逻辑,实质相加的是num1+num而不是num1+num2,所以结果是1+1=2
System.out.println("结果为:"+numberUtil.sumFun(1, 2));
}
运行结果:
但是与匿名对象不同的是,变量num并不需要一定是final。下面的代码依然是合法的:
/**
* lambda范围
*/
public static void lambdaScope(){
Integer num = 1;
//此地方引用了lambda表达式以外的final变量num
INumberUtil<Integer> numberUtil = (Integer num1,Integer num2) -> Integer.sum(num1,num);
//按代码逻辑,实质相加的是num1+num而不是num1+num2,所以结果是1+1=2
System.out.println("结果为:"+numberUtil.sumFun(1, 2));
}
由于num在编译的时候被隐式地当做final变量来处理,所以代码里不能改变num的值,否则则是不合法的,如下:
/**
* lambda范围
*/
public static void lambdaScope(){
Integer num = 1;
//此地方引用了lambda表达式以外的final变量num
INumberUtil<Integer> numberUtil = (Integer num1,Integer num2) -> Integer.sum(num1,num);
//按代码逻辑,实质相加的是num1+num而不是num1+num2,所以结果是1+1=2
System.out.println("结果为:"+numberUtil.sumFun(1, 2));
num = 2;
}
结果:
在lambda表达式的内部能获取到对成员变量或静态变量的读写权。这种访问行为在匿名对象里是非常典型的:
package com.lambda.demo;
/**
* @Author LQY
* @Date 2020/8/14
* @Description lambda表达式变量范围案例,与局部变量不同,我们在lambda表达式的内部能获取到对成员变量或静态变量的读写权。这种访问行为在匿名对象里是非常典型的。
*/
public class LambdaScopeDemo {
static int outerStaticNum;
int outerNum;
void testScopes() {
Integer num = 1;
INumberUtil<Integer> numberUtilA = (Integer num1,Integer num2) ->{
outerNum = 250;
return Integer.sum(num1,outerNum);
};
//按代码逻辑,实质相加的是num1+outerNum而不是num1+num2,所以结果是1+250=251
System.out.println("====numberUtilA 执行结果为:"+numberUtilA.sumFun(1, 2));
INumberUtil<Integer> numberUtilB = (Integer num1,Integer num2) ->{
outerStaticNum = 255;
return Integer.sum(num1,outerStaticNum);
};
//按代码逻辑,实质相加的是num1+outerStaticNum而不是num1+num2,所以结果是1+255=256
System.out.println("====numberUtilB 执行结果为:"+numberUtilA.sumFun(1, 2));
}
}
内置函数式接口:
JDK 1.8 API中包含了很多内置的函数式接口。有些是在以前版本的Java中大家耳熟能详的,例如Comparator接口,或者Runnable接口。对这些现成的接口进行实现,可以通过@FunctionalInterface 标注来启用Lambda功能支持。
此外,Java 8 API 还提供了很多新的函数式接口,⽐如通过Predicate函数式接⼝以及lambda表达式,可以向API⽅法添加逻辑,⽤更少的代码⽀持更多的动态⾏为。
用法如下:
public static void testPredicate() {
List<String> list = Arrays.asList("Aaa", "B", "C", "D");
//值等于‘B’的
list.stream().filter((str) -> ("B".equals(str))).forEach(System.out::println);
//值的长度大于2的
list.stream().filter((str) -> (((String) str).length() > 2)).forEach(System.out::println);
}
运行结果:
Predicate可以将多个条件合并成⼀个,如下:
public static void testPredicate2() {
List<String> list = Arrays.asList("Aaa", "BA", "C", "D");
//包含'A'的
Predicate condition1 = (str) -> (((String)str).contains("A"));
//长度大于2的
Predicate condition2 = (str) -> (((String) str).length() > 2);
//包含'A'的并且长度大于2的
list.stream().filter(condition1.and(condition2)).forEach((str)->{System.out.println("==包含'A'的并且长度大于2的=="+str);});
System.out.println("********************************************************");
//包含'A'的或者长度大于2的
list.stream().filter(condition1.or(condition2)).forEach((str)->{System.out.println("==包含'A'的或者长度大于2的=="+str);});
}
运行结果: