java核心技术笔记:lambda表达式
前言
lambda表达式是java 8中引入的一个新特性,可以说它是java近几次更新中变化最大的一个,当然除了它之外还有我们的Stream API。我们可以利用lambda表达式写出简洁,灵活的代码,使我们的代码更有紧凑感。
lambda表达式引入
lambda表达式是一种匿名函数或者说是闭包(closure),他能使我们的代码块在方法之间传递,像我们传递数据那样方便。在没有lambda表达式之前,在java语言中想要把一段代码传递给一个方法,我们一般会先把这段代码封装成一个函数,然后再把这个函数封装到一个类中,我们把这个类的对象传递给那个方法。比如我现在有个方法calculate,这个方法的功能就是对给定的数进行计算,计算的方式由用户指定。
//为计算抽象出一个方法 public interface CalculateNumber { public int calculateWay(int a, int b); } //实现接口,按照自己的计算方式重写calculateWay方法 public class AddNumber implements CalculateNumber{ @Override public int calculateWay(int a, int b) { return a+b; } } public class Deal { public int calculate (int a, int b, CalculateNumber calculateNumber) { return calculateNumber.calculateWay(a, b); } }
那么我们在使用calculate方法时,我们不仅需要把计算的数据传递给calculate,还要把我们指定的计算方式封装到一个类中,并且这个类要实现CalculateNumber这个接口。这样做看起来并没有什么不妥,但是如果我们每次计算的方式都不一样,那我们岂不是要写很多类去实现CalculateNumber,这样看起来很繁琐。我们要是能够直接把我们的计算方式的那段代码传递给calculate这个方法,我们就能少做很多工作,代码看起来也没那么繁琐。那么java官方也为我们提供了这样做的方式,那就是我们大名鼎鼎的lambda表达式。
函数式接口
在使用lambda表达式之前我们必须先了解函数式接口,因为java中lambda表达式需要函数式接口的支持。什么是函数式接口?若一个接口中只有一个抽象方法,那么我们可以称这个接口为函数式接口,函数式接口可以用注解@FunctionalInterface修饰。这里重点要注意的是只有一个抽象方法,因为在java 8之后,接口中可以有默认方法和静态方法。
//函数式接口可用FunctionalInterface注解修饰,也可以不用 //函数式接口 @FunctionalInterface public interface MyFunctionalInterface { int add (int a, int b); default int aa (int a, int b) { return a + b; } static int bb (int a, int b) { return a + b; } } //函数式接口 @FunctionalInterface public interface MyFunctionalInterface1 { int add (int a, int b); } //不是函数式接口,因为不止一个抽象方法,用FunctionalInterface修饰也会报错 @FunctionalInterface public interface MyFunctionalInterface2 { int add (int a, int b); int aa (int a, int b); }
lambda表达式的常见用法
了解了函数式接口之后,我们就可以学习使用lambda表达式了,首先我们来看一下lambda表达式的语法结构:(参数列表) -> {我们需要传递的代码块}
操作符: ->
操作符左侧:在文章的开始我们说了,lambda表达式是一个匿名函数,那么操作符左侧就是我们的参数列表。
操作符右侧:操作符右侧就是我们的代码块,或者说函数体,lambda体。
lambda表达式的常用形式:
注:下文出现的Consumer<T>和Comparator<T>是java内置的函数式接口,我们用这两个接口来说明lambda表达式的常见用法。
1.无参数,无返回值,lambda体中也只有一条语句: () -> lambda体
Consumer <String> con = () -> System.out.println("hello world");
2.只有一个参数,没有返回值,lambda体中也只有一条语句(若只有一个参数我们可以将操作符左边的参数列表的括号去掉):(int x)-> lambda体 / x -> lambda体
Consumer <int> con = (int x) -> System.out.println(x);
//参数列表的数据类型也可以省略,lambda表达式可以通过函数式接口中的参数类型自动推导
Consumer <int> con = x -> System.out.println(x);
3.多个参数,多条语句,有返回值:
(int a, int b) -> {
//执行多条语句......
return a + b;
}
当然此处参数列表的数据类型也可以省略: (a, b)-> {lambda体}
//此处的参数列表的数据类型也可以省略 Comparator <Integer> com = (Integer x, Integer y) -> { System.out.println(); return Integer.compare(x, y); }
4.lambda体中只有一条语句,并且方法有返回值,我们可以省略lambda体的{}和return关键字。
Comparator <Integer> com = (Integer x, Integer y) -> Integer.compare(x, y);
注意:lambda表达式的参数列表的数据类型可以省略。
以上就是lambda表达式的常见的几种用法,在使用lambda表达式的时候我们需要注意的是:lambda表达式需要函数式接口的支持,当代码的的某个地方需要提供一个函数式接口的对象时,我们可以提供一个lambda表达式来代替这个这个对象。我们来改写我们之前写的calculate,我们将CalculateNumber接口声明为函数式接口。
@FunctionalInterface public interface CalculateNumber { public int calculateWay(int a, int b); } public class Deal { //calculate方法需要两个int型的整数,和一个实现接口的对象,但是因为这是一个函数式接口,故我们可以使用lambda表达式 public int calculate (int a, int b, CalculateNumber calculateNumber) { return calculateNumber.calculateWay(a, b); } } public class Main { public static void main(String[] args) { Deal deal = new Calculate(); //这里按照以前的方式,我们需要给calculate方法一个CalculateNumber实现类的对象,但是CalculateNumber是一个函数式接口,我们就可以直接用一个lambda表达式来代替 int iResult = deal.calculate(200, 200, (a, b) -> { int c = a - b; return a + c; }); System.out.println(iResult); } }
我们在calculate这个方法中使用了函数式接口后,我们就不用每次都写一个类去实现CauculateNumber这个类,把计算的代码写到calculateWay中,而是直接利用lambda表达式将计算的代码直接传递给calclate方法。总结成一句话就是,lambda表达式用于实现一个函数式接口,但是我们不需要为这个实现提供一个类,对于代码中需要函数式接口对象的地方,我们可以直接提供一个lambda表达式来代替,在java中lambda表达式所能做的也只是能转换为函数式接口。
java内置的函数式接口
lambda表达式虽然实现了代码块的传递,但是他需要函数式接口的支持,那不是意味着我每次使用lambda表达式进行代码传递之前,我们都要写一个函数式接口?理论上是这样的,但是java官方为我们内置了我们日常可能会用到的函数式接口。
函数式接口 | 参数类型 | 返回值类型 |
抽象方法名 |
描述 |
Runnable | 无 | void | run |
执行一个动作,该动作执行没有返回值,也不需要参数 |
Supplier<T> | 无 | T | get |
提供一个T类型的值 |
Consumer<T> | T | void | accept |
处理一个T类型的值 |
Function<T,R> | T | R | apply |
有一个T类型参数的函数 |
Predicate<T> |
T | boolean | test | 参数类型为T,返回值为boolean的函数 |
当然java还有很多内置的函数式接口,但是基本都是表格中的变形,功能都是类似的。
方法引用
有时候可能会出现这中情况,比如代码的某个地方需要一个lambda表达式,但是这个lambda表达式中要写的代码与我们之前写过一个方法中的代码一摸一样,这时候我们就不需要在lambda表达式中将这些代码再写一遍,而是直接使用我们之前写的那个方法,这就是方法引用。
方法引用常见的三种格式:
1.对象::实例方法名
2.类::静态方法名
3.类::实例方法名
x -> System.out.println(x); //上面的lambda表达式就相当于 System.out::println;
方法引用的第三种格式需要注意:方法的第一个参数是实例方法的调用者,剩下的参数是实例方法的参数。
(x, y) -> x.compareToIgnoreCase(y); //用方法引用表示就是 String::compareToIgnoreCase; //这里的String是一个类,并不是一个对象,而且compareToIgnoreCase也只是一个普通的方法,并不是静态方法,需要使用对象去调用。
使用方法引用时有一个前提:引用的方法的参数列表和返回值类型要与待实现的函数式接口中的抽象方法的参数列表和返回值类型一致。
构造器引用
格式:类::new
构造器引用和方法引用差不多,只是方法名是new,有时候构造器会有多个,这种情况下lambda表达式会根据上下文推导出应该使用哪一个构造器。
变量作用域
在lambda表达式中有时我们需要访问外围方法或者类中的变量,比如:
//这里的lambda表示就使用方法AA的局部变量a和text public static void AA(String text ) { int a = 100; Consumer<String> con = x -> { System.out.println(a); System.out.println(text); }; }
lambda表达式是可以捕获外围作用域中的变量的值,但是他有一些限制:
1.外围作用域中的变量的值要有明确的定义。
2.lambda表达式中只能引用值不会改变的变量。
第二条限制主要是为了避免,多线程并发执行带来的不安全问题。
注:在lambda表示中声明一个与外围作用域中同名的参数或者局部变量是不允许的。
//这里的lambda表示就使用方法AA的局部变量a和text public static void AA(String text ) { int a = 100; Consumer<String> con = x -> { int a = 200; //这是不合法的 int text = 100; //这也是不合法的 System.out.println(a); System.out.println(text); }; }
在lambda表达式中也可以使用this关键字,lambda表达式中使用的this关键字是创建lambda表达式方法中的this。
//这里lambda表达式中使用的this就是方法AA中的this参数 public class TestThis { public void AA() { Consumer<String> con = x -> { this.toString(); }; } }