Lambda实践
Lambda实践
java8以前的comparator和java8的comparator
可以看到使用lambda表达式,使得对象的创建以及声明变得简单且优雅
//老版本的比较器声明方式
Comparator<Apple> comparator=new Comparator<Apple>() {
@Override
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
};
//java8的比较器的声明方式
Comparator<Apple> cmp=(a1,a2)->a1.getWeight().compareTo(a2.getWeight());
lambda的优势
- 匿名:使用lambda声明方法无需像普通方法需要很多明确的东西,它给了我们一种写得少但是想得多的优雅
- 函数:lambda表达式用起来就和方法一样,有参数列表、函数主体、返回值等
- 传递:lambda可以作为参数传递或者存储在变量中
- 简洁:如上文所示的代码,使用lambda可以避免没必要的模板编写,优雅且简洁
lambda的使用场景
函数式接口
在介绍lambda表达式之前我们需要先介绍一下函数式接口,因为lambda就是为缩写函数式的接口而生的。 如下图所示Runnable接口就是一个函数式接口,函数式接口的特征也很明显,他是一个接口,且有且只有一个方法。并且还有一个注解@FunctionalInterface
,需要说明的是这个注解非必须的,他只是告知编译器这是个函数式接口而已,让编译器留点心 也正是因为方法为一,才能确保简洁的lambda表达式可以唯一确定匹配接口的方法。
函数式描述符
了解了函数式接口,了解函数描述符就很简单了,以Runnable为例,他的方法是 public abstract void run();
所以他是一个没有参数且没有返回值的方法。 那么它的函数描述符就是() -> void
,后续我们使用lambda表达式的时候只要遵循这个表达式即可。 再看看一个例子,这也是笔者自定义的一个函数式接口,可以看出他的入参是一个苹果类,返回值是boolean,所以它的函数描述符是**(Apple)->boolean**
interface ApplePredicate{
boolean test(Apple a);
}
ApplePredicate applePredicate=(a)->true;
这就是为什么我们声明ApplePredicate可以缩写成下文所示
使用lambda优化环绕执行模式的调用
需求描述
我们现在有个文件data.txt,它的内容为
Java
8
Lambdas
In
Action
我们希望编写的代码只读取第1行的结果然后返回即可
代码实现
可以看到这个需求实现也非常简单,如下所示
public static String processFileLimited() throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader("F:\\github\\src\\main\\resources\\lambdasinaction\\chap3\\data.txt"))) {
return br.readLine();
}
}
项目演进
需求描述
现在需求变了,我们希望能够读取两行,后续可能还会发生变化,这时候警觉的你就会发现读取文件这个行为可能多变无常,我们需要对变化进行封装
声明函数式接口
所以我们将读取文件这个行为封装成一个函数式接口,代码如下所示
public interface BufferedReaderProcessor{
String process(BufferedReader b) throws IOException;
}
修改原有方法逻辑
这时候我们在进行行为参数化,将读取文件内容这个逻辑参数化,后续我们就可以大展身手了
public static String processFile(BufferedReaderProcessor p) throws IOException {
try(BufferedReader br = new BufferedReader(new FileReader("F:\\github\\Java8InAction\\src\\main\\resources\\lambdasinaction\\chap3\\data.txt"))){
return p.process(br);
}
}
调用并查看测试结果
这时候我们就可以使用lambda进行调用测试了
测试读取1行
// 测试读取1行 输出结果 Java
String s = processFile(b -> b.readLine());
System.out.println(s);
测试读取2行
// 测试读取2行 输出结果 Java 8
String s2 = processFile(b -> b.readLine()+" "+b.readLine());
System.out.println(s2);
测试读取1行并行尾加上 "本人已读"
//测试读取1行并行尾加上 "本人已读" 输出结果 Java 本人已读
String s3 = processFile(b -> b.readLine()+" 本人已读");
System.out.println(s3);
"现成的轮子" java8自带的函数式接口
简介
其实面对常见的行为参数化,java已经考虑到这些情况了,他也为我们提供了不少的现有轮子,下面我们就来一一介绍几个常见的轮子
Predicate
这个接口就是针对于那些需要传入指定类型,并返回Boolean行的行为,例如:我们需要一组对字符串进行判断操作的行为,我们可以先这样写一个方法,通过Predicate将行为参数化
public static boolean test(String s,Predicate<String> p){
return p.test(s);
}
假如我们需要判断字符串是大于2,我们就可以这样调用
boolean result = test("123", (s) -> s.length() > 12);
System.out.println(result);
假如我们需要判断字符串是否为test,我们可以这样调用
boolean result=test("test",(s)->"test".equals(s));
System.out.println(result);
Consumer
与上同理,Consumer的函数描述符为(T)->void
,即传入任意类型,无返回值的操作。 例如我们现在要遍历并输出不同类型的元素,我们可以先定义一个方法,将consumer行为参数化
public static <T> void forEach(List<T> list, Consumer<T> consumer){
for (T t : list) {
consumer.accept(t);
}
}
假如我们要遍历整形数组,我们可以这样
List<Integer> integerList=new ArrayList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
integerList.add(4);
forEach(integerList,(i)-> System.out.println(i));
Function
通过查看源码,function的定义如下,不难看出,它适用于那些传入T类型返回R类型的行为
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
假如我们要求传入一个字符串,返回它的长度或者字符串第1位unocode码值,我们就可以使用Function做到。 首先我们定义一个方法,将返回整型的行为参数化,如下所示
public static Integer StringCalculate(String s, Function<String,Integer> function){
return function.apply(s);
}
调用如下所示
//获取字符串长度
System.out.println(StringCalculate("123", (s) -> s.length()));
// 获取字符串第1位unicode
System.out.println(StringCalculate("132", (s) -> s.codePointAt(0)));
更多函数式接口
![在这
lambda工作原理简析
类型检查
那么问题来了,lambda表达式如此精简,请问它是如何完成类型检查的呢?我们就以下面代码为例,可以看到这段代码就是根据传入的String返回相应的boolean值
public static <T> boolean test(T s, Predicate<T> p) {
return p.test(s);
}
假如我们这样调用
boolean result = test("123", (String s) -> s.length() > 12);
他的匹配过程就如下图所示,首先根据调用方法找到主方法,根据主方法的泛型得知入参是String,再查看predicate的唯一方法得知返回值是boolean,由此得知函数描述符为String->boolean
,最终和调用的lambda匹配成功,校验通过
类型推断
因为函数式接口方法是唯一的,我们也可以对类型进行省略,让java编译器去自动推断类型
boolean result = test("123", (s) -> s.length() > 12);
System.out.println(result);
使用限制(使用lambda使用局部变量)
如下代码所示,这样一段代码在idea中会报红,原因很简单,局部变量分配在栈上,而r线程可能会在该变量被回收之后才使用这个变量,所以java在让线程r访问这个变量的时候,实际上访问到的num是num的副本,所以假如lambda访问变量num,再给num赋值就会爆红,因为如下所示代码若能正常执行,那么r线程最终输出的num很可能前后不一致是随机,进而导致线程安全问题。
int num=1;
Runnable r=()-> System.out.println(num);
r.run();
num=2;