作者:汤圆
集体博客:javalover.cc
前言
大家好啊,我是汤圆,明天给大家带来的是《Java8中的Lambda表达式》,心愿对大家有帮忙,谢谢
文章纯属原创,集体总结不免有过错,如果有,麻烦在评论区回复或后盾私信,谢啦
简介
Lambda表达式是一个可传递的代码块,能够在当前执行一次或屡次;
上面贴个比照代码:
// Java8之前:旧的写法Runnable runnable = new Runnable() { @Override public void run() { System.out.println("old run"); }};Thread t = new Thread(runnable);// Java8之后:新的写法Runnable runnable1 = ()->{ System.out.println("lambda run");};Thread t1 = new Thread(runnable1);
能够看到,有了lambda,代码变得简洁多了
你能够把lambda当作一个语法糖
上面让咱们一起来摸索lambda的美好世界吧
目录
上面列出本文的目录
- lambda的语法
- 为啥引入lambda
- 什么是函数式接口
- 什么是行为参数化
- 手写一个函数式接口
- 罕用的函数式接口
- 什么是办法援用
- 什么是结构援用
- lambda的组合操作
注释
1. lambda的语法
上面别离说下语法中的三个组成部分
参数:
( Dog dog )
- 参数类型可省略(当编译器能够主动推导时),比方
Comparator<String> comparatorTest = (a, b)->a.length()-b.length();
,能够推导出a,b都为String - 当参数类型可省略,且只有一个参数时,括弧也能够省略(然而集体习惯保留)
- 参数类型可省略(当编译器能够主动推导时),比方
- 符号:
->
主体:
{ System.out.println("javalover"); }
- 如果是一条语句,则须要加大括号和分号
{;}
(比方上图所示) - 如果是一个表达式,则间接写,啥也不加(比方
a.length()- b.length()
)
- 如果是一条语句,则须要加大括号和分号
2. 为啥引入lambda
为了简化代码
因为Java是面向对象语言,所以在lambda呈现之前,咱们须要先结构一个对象,而后在对象的办法中实现具体的内容,再把结构的对象传递给某个对象或办法
然而有了lambda当前,咱们能够间接将代码块传递给对象或办法
当初再回头看下结尾的例子
能够看到,用了lambda表达式后,少了很多模板代码,只剩下一个代码块(最外围的局部)
3. 什么是函数式接口
就是只定义了一个形象办法的接口
- 正例:有多个默认办法,然而如果只有一个形象办法,那它就是函数式接口,示例代码如下
@FunctionalInterfacepublic interface FunctionInterfaceDemo { void abstractFun(); default void fun1(){ System.out.println("fun1"); } default void fun2(){ System.out.println("fun2"); } }
这里的注解@FunctionalInterface能够省略,然而倡议加上,就是为了通知编译器,这是一个函数式接口,此时如果该接口有多个形象办法,那么编译器就会报错
- 反例:比方A extends B,A和B各有一个形象办法,那么A就不是函数式接口,示例代码如下
// 编译器会报错,Multiple non-overriding abstract methods found in XXX@FunctionalInterfacepublic interface NoFunctionInterfaceDemo extends FunctionInterfaceDemo{ void abstractFun2();}
下面的父接口FunctionInterfaceDemo中曾经有了一个形象办法,此时NoFunctionInterfaceDemo又定义了一个形象办法,后果编译器就提醒了:存在多个形象办法
在Java8之前,其实咱们曾经接触过函数式接口
比方Runnable 和 Comparable
只是没有注解@FunctionalInterface。
那这个函数式接口要怎么用呢?
配合lambda食用,成果最佳(就是把lambda传递给函数式接口),示例代码如下:
new Thread(() -> System.out.println("run")).start();
其中用到的函数式接口是Runnable
4. 什么是行为参数化
就是把行为定义成参数,行为就是函数式接口
相似泛型中的类型参数化<T>
,类型参数化是把类型定义成参数
行为参数化,艰深点来说:
- 就是用函数式接口做形参
- 而后传入接口的各种实现内容(即lambda表达式)作为实参
- 最初在lambda内实现各种行为(如同又回到[多态]()的那一节了?这也是为啥多态是Java的三大个性的起因之一,利用太宽泛了)
这样来看的话,行为参数化和设计模式中的策略模式有点像了(前面章节会别离讲罕用的几种设计模式)
上面咱们手写一个函数式接口来加深了解吧
5. 手写一个函数式接口
上面咱们循序渐进,先从简略的需要开始
- 第一步:比方咱们想要读取某个文件,那能够有如下办法:
public static String processFile() throws IOException { // Java7新增的语法,try(){},可主动敞开资源,缩小了代码的臃肿 try( BufferedReader bufferedReader = new BufferedReader(new FileReader("D:\\JavaProject\\JavaBasicDemo\\test.txt"))){ return bufferedReader.readLine(); }}
能够看到,外围的行为动作就是 return bufferedReader.readLine();
,示意读取第一行的数据并返回
那如果咱们想要读取两行呢?三行?
- 第二步:这时就须要用到下面的函数式接口了,上面就是咱们本人编写的函数式接口
@FunctionalInterfaceinterface FileReadInterface{ // 这里承受一个BufferedReader对象,返回一个String对象 String process(BufferedReader reader) throws IOException;}
能够看到,只有一个形象办法process()
,它就是用来解决第一步中的外围动作(读取文件内容)
至于想读取多少内容,那就须要咱们在lambda表达式中定义了
- 第三步:接下来咱们定义多个lambda表达式,用来传递给函数式接口,其中每个lambda表达式就代表了一种不同的行为,代码如下:
// 读取一行FileReadInterface fileReadInterface = reader -> reader.readLine();// 读取两行FileReadInterface fileReadInterface2 = reader -> reader.readLine() + reader.readLine();
- 第四步:咱们须要批改第一步的
processFile()
,让其承受一个函数式接口,并调用其中的形象办法,代码如下:
// 参数为第二步咱们本人手写的函数式接口public static String processFile(FileReadInterface fileReadInterface) throws IOException { try( BufferedReader bufferedReader = new BufferedReader(new FileReader("./test.txt"))){ // 这里咱们不再本人定义行为,而是交给函数式接口的形象办法来解决,而后通过lambda表达式的传入来实现多个行为 return fileReadInterface.process(bufferedReader); } }
- 第五步:拼接后,残缺代码如下:
public class FileReaderDemo { public static void main(String[] args) throws IOException { // 第三步: // lambda表达式1 传给 函数式接口:只读取一行 FileReadInterface fileReadInterface = reader -> reader.readLine(); // lambda表达式2 传给 函数式接口:只读取两行 FileReadInterface fileReadInterface2 = reader -> reader.readLine() + reader.readLine(); // 最初一步: 不同的函数式接口的实现,体现出不同的行为 String str1 = processFile(fileReadInterface); String str2 = processFile(fileReadInterface2); System.out.println(str1); System.out.println(str2); } // 第四步: 读取文件办法,承受函数式接口作为参数 public static String processFile(FileReadInterface fileReadInterface) throws IOException { try( BufferedReader bufferedReader = new BufferedReader(new FileReader("./test.txt"))){ // 调用函数式接口中的形象办法来解决数据 return fileReadInterface.process(bufferedReader); } } // 第一步: public static String processFile() throws IOException { try( BufferedReader bufferedReader = new BufferedReader(new FileReader("./test.txt"))){ return bufferReader.readLine(); } }}// 第二步: 咱们手写的函数式接口@FunctionalInterfaceinterface FileReadInterface{ String process(BufferedReader reader) throws IOException;}
其实你会发现,咱们手写的这个函数式接口,其实就是Function<T>
去除泛型化后的接口,如下所示:
@FunctionalInterfacepublic interface Function<T, R> { // 都是承受一个参数,返回另一个参数 R apply(T t);}
上面咱们列出Java中罕用的一些函数式接口,你会发现自带的曾经够用了,根本不会须要咱们本人去写
这里的手写只是为了本人实现一遍,能够加深了解水平
6. 罕用的函数式接口
7. 什么是办法援用
咱们先看一个例子
后面咱们写的lambda表达式,其实还能够简化,比方
// 简化前Function<Cat, Integer> function = c->c.getAge();// 简化后Function<Cat, Integer> function2 = Cat::getAge;
其中简化后的Cat::getAge
,咱们就叫做办法援用
办法援用就是援用类或对象的办法;
上面咱们列出办法援用的三种状况:
- Object::instanceMethod(对象的实例办法)
- Class::staticMethod(类的静态方法)
- Class::instanceMethod(类的实例办法)
像咱们下面举的例子就是第三种:类的实例办法
上面咱们用代码演示下面的三种办法:
public class ReferenceDemo { public static void main(String[] args) { // 第一种:援用对象的实例办法 Cat cat = new Cat(1); Function<Cat, Integer> methodRef1 = cat::getSum; // 第二种:援用类的静态方法 Supplier<Integer> methodRef2 = Cat::getAverageAge; // 第三种:援用类的实例办法 Function<Cat, Integer> methodRef3 = Cat::getAge; }}class Cat { int age; public Cat(int age) { this.age = age; } // 获取猫的平均年龄 public static int getAverageAge(){ return 15; } // 获取两只猫的年龄总和 public int getSum(Cat cat){ return cat.getAge() + this.getAge(); } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}
为啥要用这个办法援用呢?
办法援用好比lambda表达式的语法糖,语法更加简洁,清晰
一看就晓得是调用哪个类或对象的哪个办法
8. 什么是结构援用
下面介绍了办法援用,就是间接援用某个办法
这里的结构援用同理可得,就是援用某个类的构造方法
结构援用的表达式为:Class::new
,仅此一种
如果你有多个构造函数,那编译器会本人进行推断参数(你看看,多好,多简洁)
比方上面的代码:
// 这里调用 new Cat()Supplier<Cat> constructRef1 = Cat::new;// 这里调用 new Cat(Integer)Function<Integer, Cat> constructRef2 = Cat::new;
9. lambda表达式中引入内部变量的限度
要求引入lambda表达式中的变量,必须是最终变量,即该变量不会再被批改
比方上面的代码:
public static void main(String[] args) { String str = "javalover.cc"; Runnable runnable = ()->{ str = "1";// 这里会报错,因为批改了str援用的指向 System.out.println(str); }}
能够看到,lambda表达式援用了里面的str援用,然而又在表达式外部做了批改,后果就报错了
为啥要有这个限度呢?
为了线程平安,因为lambda表达式有一个益处就是只在须要的时候才会执行,而不是调用后立马执行
这样就会存在多个线程同时执行的并发问题
所以Java就从本源上解决:不让变量被批改,都是只读的
那你可能好奇,我不把str的批改代码放到表达式外部能够吗?
也不行,情理是一样的,只有lambda有用到这个变量,那这个变量不论是在哪里被批改,都是不容许的
不然的话,我这边先执行了一次lambda表达式,后果你就改了变量值,那我第二次执行lambda,不就乱了吗
10. lambda的组合操作
最初是lambda的必杀技:组合操作
在这里叫组合或者复合都能够
概述:组合操作就是先用一个lambda表达式,而后再在前面组合另一个lambda表达式,而后再在前面组合另另一个lambda表达式,而后。。。有点像是链式操作
学过JS的都晓得Promise,外面的链式操作就和这里的组合操作很像
用过Lombok的敌人,应该很相熟@Builder注解,其实就是结构者模式
上面咱们用代码演示下组合操作:
// 重点代码public class ComposeDemo { public static void main(String[] args) { List<Dog> list = Arrays.asList(new Dog(1,2), new Dog(1, 1)); // 1. 先按年龄排序(默认递增) // Dog::getAge, 下面介绍的办法援用 // comparingInt, 是Comparator的一个静态方法,返回Comparator<T> Comparator<Dog> comparableAge = Comparator.comparingInt(Dog::getAge); // 2. 如果有雷同的年龄,则年龄雷同的再按体重排序(如果年龄曾经比拟出大小,则上面的体重就不会再去比拟) Comparator<Dog> comparableWeight = Comparator.comparingInt(Dog::getWeight);; // 3. 调用list对象的sort办法排序,参数是Comparator<? super Dog> list.sort(comparableAge.thenComparing(comparableWeight)); System.out.println(list); }}// 非重点代码class Dog{ private int age; private int weight; public Dog(int age, int weight) { this.age = age; this.weight = weight; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } @Override public String toString() { return "Dog{" + "age=" + age + ", weight=" + weight + '}'; }}
输入:[Dog{age=1, weight=1}, Dog{age=1, weight=2}]
比拟的流程如下所示:
总结
- lambda的语法: 参数+合乎+表达式或语句,比方
(a,b)->{System.out.println("javalover.cc");}
- 函数式接口:只有一个形象办法,最好加@FunctionalInterface,这样编译器可及时发现谬误,javadoc也阐明这是一个函数式接口(可读性)
- 行为参数化:就是函数式接口作为参数,而后再将lambda表达式传给函数式接口,通过不同的lambda内容实现不同的行为
办法援用:lambda的语法糖,总共有三种:
- Object::instanceMethod(对象的实例办法)
- Class::staticMethod(类的静态方法)
- Class::instanceMethod(类的实例办法)
- 结构援用:就一种,编译器本人可判断是哪个构造函数,语法为
Class::new
- 在lambda中引入内部变量,必须保障这个变量是最终变量,即不再被批改
- lambda的组合操作,就是链式操作,组合是通过函数式接口的静态方法来组合(静态方法会返回另一个函数式接口的对象)
比方list.sort(comparableAge.thenComparing(comparableWeight));
后记
最初,感激大家的观看,谢谢