Lamdba表达式的标准格式
组成Lamdba表达式的三要素:形式参数,箭头,代码块
(形式参数)->{
代码块
}
形式参数:如果有多个参数,参数之间用逗号隔开;如果只有一个参数可以不要(),如果没有参数留空即可
->:由英文中画线和大于符号组成,固定写法。代表指向动作
代码块:是我们具体要做的事情,也就是以前我们写的方法体内容,如果方法体不止一行,需要用上 {} ,如果
以下是lambda表达式的重要特征:
* lambda表达式的左边
* 1、如果没有参数的话,可以直接一个括号,类型的话,都可以省
* 2、如果只有一个参数的话,可以直接传入参数,并省略括号。
* 3、如果有两个参数的话,需要直接传入两个参数
*
* lambda表达式的右边
* 1、如果只执行一条语句的话,就直接可以省略大括号
* 2、如果只执行一条语句,并且有返回值的话,可以省略大括号
* 3、如果执行多条语句的话,就要运用到大括号来执行了。
下面对每个语法格式的特征进行举例说明:
(1)语法格式一:无参,无返回值,Lambda体只需一条语句。如下
@Test
public void test01(){
Runnable runnable=()-> System.out.println("Runnable 运行");
runnable.run();//结果:Runnable 运行
}
(2)语法格式二:Lambda需要一个参数,无返回值。如下:
@Test
public void test02(){
Consumer<String> consumer=(x)-> System.out.println(x);
consumer.accept("Hello Consumer");//结果:Hello Consumer
}
(3)语法格式三:Lambda只需要一个参数时,参数的小括号可以省略,如下:
public void test02(){
Consumer<String> consumer=x-> System.out.println(x);
consumer.accept("Hello Consumer");//结果:Hello Consumer
}
(4)语法格式四:Lambda需要两个参数,并且Lambda体中有多条语句。
@Test
public void test04(){
Comparator<Integer> com=(x, y)->{
System.out.println("函数式接口");
return Integer.compare(x,y);
};
System.out.println(com.compare(2,4));//结果:-1
}
(5)语法格式五:有两个以上参数,有返回值,若Lambda体中只有一条语句,return和大括号都可以省略不写
@Test
public void test05(){
Comparator<Integer> com=(x, y)-> Integer.compare(x,y);
System.out.println(com.compare(4,2));//结果:1
}
(6)Lambda表达式的参数列表的数据类型可以省略不写,因为JVM可以通过上下文推断出数据类型,即“类型推断”
@Test
public void test06(){
Comparator<Integer> com=(Integer x, Integer y)-> Integer.compare(x,y);
System.out.println(com.compare(4,2));//结果:1
}
函数式接口
什么是函数式接口
==只包含一个抽象方法的接口,就称为函数式接口。==我们可以通过Lambda表达式来创建该接口的实现对象。
我们可以在任意函数式接口上使用@FunctionalInterface注解,这样做可以用于检测它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。
自定义函数式接口
按照函数式接口的定义,自定义一个函数式接口,如下:
@FunctionalInterface
public interface MyFuncInterf<T> {
public T getValue(String origin);
}
定义一个方法将函数式接口作为方法参数
public String toLowerString(MyFuncInterf<String> mf,String origin){
return mf.getValue(origin);
}
将Lambda表达式实现的接口作为参数传递
public void test07(){
String value=toLowerString((str)->{
return str.toLowerCase();
},"ABC");
System.out.println(value);//结果ABC
}
Java内置函数式接口
四大核心函数式接口的介绍,如图所示
其他接口的定义,如图所示:
方法引用
当要传递给Lambda体的操作,已经有实现的方法了,就可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用的参数列表一致,方法的返回值也必须一致,即方法的签名一致)。方法引用可以理解为方法引用是Lambda表达式的另外一种表现形式。
方法引用的语法:使用操作符“::”将对象或类和方法名分隔开。
方法引用的使用情况共分为以下三种:
对象::实例方法名
类::静态方法名
类::实例方法名
举例
比如,这里我们随机生成10个整数然后取它们绝对值并一一打印出来。
new Random().ints(10)
.map(i->Math.abs(i))
.forEach(i -> System.out.println(i));
map方法接受的是一个函数式接口IntUnaryOperator,那么上面代码中的i->Math.abs(i)实际上是
new IntUnaryOperator() {
@Override
public int applyAsInt(int operand) {
return Math.abs(operand);
}
}
从上面来看IntUnaryOperator就是代理了Math.abs(int i),参数列表、返回值都相同,而且没有掺杂其它额外的逻辑。这一点非常重要,不掺杂其它逻辑才能相互代替。那么就可以通过方法引用来简化Lambda 表达式。上面的式子就可以简化为:
new Random().ints(10)
.map(Math::abs)
.forEach(System.out::println);
Java 方法引用是Java 8随着Lambda表达式引入的新特性。 可以直接引用已有Java类或对象的方法或构造器。方法引用通常与Lambda表达式结合使用以简化代码。其使用条件是:Lambda表达式的主体仅包含一个表达式,且Lambda表达式只调用了一个已经存在的方法;被引用的方法的参数列表和返回值与Lambda表达式的输入输出一致。
格式
方法引用的格式为<ClassName | instance>::<MethodName>。也就是被引用的方法所属的类名和方法名用双冒号::隔开,构造器方法是个例外,引用会用到new关键字,总结了一下:
引用方式 | 说明 |
静态方法引用 | ClassName :: staticMethodName 例如上面的Math::abs |
构造器引用 | ClassName :: new 例如通过Supplier<T> 返回新实例 |
类任意实例方法引用 | ClassName :: instanceMethodName 例如 String::concat |
类特定实例方法引用 | instance:: instanceMethodName 例如 this::equals |
关于可读性分析
大部分人认为Lambda 表达式存在阅读困难的问题,其实不然,这种流水线的结构恰恰增加了可读性,每一个Lambda 表达式都可以看作一个执行策略,方法引用反而让你能更加清楚执行了什么策略。另外我经常见到类似如下的流式写法:
new Random().ints(10)
.map(operand -> {
System.out.println("operand = " + operand);
return operand+1;
})
.forEach(System.out::println);
//方法引用
public void randomInt() {
new Random().ints(10)
.map(this::selfIncreasing)
.forEach(System.out::println);
}
// 封装
private int selfIncreasing(int self){
System.out.println("self = " + self);
return self+1;
}
这样反而可读性很强,随机取10个数,然后每个数走个自增并分别打印出来。
总结
方法引用实现在特定场景下Lambda表达式的简化表示,目的在于让代码更加简洁。
Lambda表达式的优缺点
优点:
使代码更简洁,紧凑
可以使用并行流来并行处理,充分利用多核CPU的优势
有利于JIT编译器对代码进行优化
缺点:
非并行计算情况下,其计算速度没有比传统的 for 循环快
不容易调试
若其他程序员没有学过 Lambda 表达式,代码不容易看懂