多平台统一管理软件接口,如何实现多平台统一管理软件接口
211
2022-08-28
Java8中Lambda表达式的理解与应用
目录简介正文1. lambda的语法2. 为啥引入lambda3. 什么是函数式接口4. 什么是行为参数化5. 手写一个函数式接口6. 常用的函数式接口7. 什么是方法引用8. 什么是构造引用9. lambda表达式中引入外部变量的限制10. 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的美好世界吧
正文
1. lambda的语法
下面分别说下语法中的三个组成部分
参数: ( Dog dog )
参数类型可省略(当编译器可以自动推导时),比如Comparator
如果是一条语句,则需要加大括号和分号{;}(比如上图所示)如果是一个表达式,则直接写,啥也不加(比如a.length()- b.length())
2. 为啥引入lambda
为了简化代码
因为Java是面向对象语言,所以在lambda出现之前,我们需要先构造一个对象,然后在对象的方法中实现具体的内容,再把构造的对象传递给某个对象或方法
但是有了lambda以后,我们可以直接将代码块传递给对象或方法
现在再回头看下开头的例子
可以看到,用了lambda表达式后,少了很多模板代码,只剩下一个代码块(最核心的部分)
3. 什么是函数式接口
就是只定义了一个抽象方法的接口
正例:有多个默认方法,但是如果只有一个抽象方法,那它就是函数式接口,示例代码如下
@FunctionalInterface
publicinterfaceFunctionInterfaceDemo{
voidabstractFun();
defaultvoidfun1(){
System.out.println("fun1");
}
defaultvoidfun2(){
System.out.println("fun2");
}
}
这里的注解@FunctionalInterface可以省略,但是建议加上,就是为了告诉编译器,这是一个函数式接口,此时如果该接口有多个抽象方法,那么编译器就会报错
反例:比如A extends B,A和B各有一个抽象方法,那么A就不是函数式接口,示例代码如下
// 编译器会报错,Multiple non-overriding abstract methods found in XXX
@FunctionalInterface
publicinterfaceNoFunctionInterfaceDemoextendsFunctionInterfaceDemo{
voidabstractFun2();
}
上面的父接口FunctionInterfaceDemo中已经有了一个抽象方法,此时NoFunctionInterfaceDemo又定义了一个抽象方法,结果编译器就提示了:存在多个抽象方法
在Java8之前,其实我们已经接触过函数式接口
比如Runnable 和 Comparable
只是没有注解@FunctionalInterface。
那这个函数式接口要怎么用呢?
配合lambda食用,效果最佳(就是把lambda传递给函数式接口),示例代码如下:
newThread(()->System.out.println("run")).start();
其中用到的函数式接口是Runnable
4. 什么是行为参数化
就是把行为定义成参数,行为就是函数式接口
类似泛型中的类型参数化,类型参数化是把类型定义成参数
行为参数化,通俗点来说:
就是用函数式接口做形参然后传入接口的各种实现内容(即lambda表达式)作为实参最后在lambda内实现各种行为(好像又回到多态的那一节了?这也是为啥多态是Java的三大特性的原因之一,应用太广泛了)
这样来看的话,行为参数化和设计模式中的策略模式有点像了(后面章节会分别讲常用的几种设计模式)
下面我们手写一个函数式接口来加深理解吧
5. 手写一个函数式接口
下面我们循序渐进,先从简单的需求开始
第一步:比如我们想要读取某个文件,那可以有如下方法:
publicstaticStringprocessFile()throwsIOException{
// Java7新增的语法,try(){},可自动关闭资源,减少了代码的臃肿
try(BufferedReaderbufferedReader=
newBufferedReader(newFileReader("./test.txt"))){
returnbufferedReader.readLine();
}
}
可以看到,核心的行为动作就是 return bufferedReader.readLine();,表示读取第一行的数据并返回
那如果我们想要读取两行呢?三行?
第二步:这时就需要用到上面的函数式接口了,下面就是我们自己编写的函数式接口
@FunctionalInterface
interfaceFileReadInterface{
// 这里接受一个BufferedReader对象,返回一个String对象
Stringprocess(BufferedReaderreader)throwsIOException;
}
可以看到,只有一个抽象方法process(),它就是用来处理第一步中的核心动作(读取文件内容)
至于想读取多少内容,那就需要我们在lambda表达式中定义了
第三步:接下来我们定义多个lambda表达式,用来传递给函数式接口,其中每个lambda表达式就代表了一种不同的行为,代码如下:
// 读取一行
FileReadInterfacefileReadInterface=reader->reader.readLine();
// 读取两行
FileReadInterfacefileReadInterface2=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 ZlJGrmKzostr2 = 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();
}
}
}
// 第二步: 我们手写的函数式接口
@FunctionalInterface
interface FileReadInterface{
String process(BufferedReader reader) throws IOException;
}
其实你会发现,我们手写的这个函数式接口,其实就是Function
@FunctionalInterface
publicinterfaceFunction
// 都是接受一个参数,返回另一个参数
Rapply(Tt);
}
下面我们列出Java中常用的一些函数式接口,你会发现自带的已经够用了,基本不会需要我们自己去写
这里的手写只是为了自己实现一遍,可以加深理解程度
6. 常用的函数式接口
7. 什么是方法引用
我们先看一个例子
前面我们写的lambda表达式,其实还可以简化,比如
// 简化前
Function
// 简化后
Function 其中简化后的Cat::getAge,我们就叫做方法引用 方法引用就是引用类或对象的方法; 下面我们列出方法引用的三种情况: Object::instanceMethod(对象的实例方法)Class::staticMethod(类的静态方法)Class::instanceMethod(类的实例方法) 像我们上面举的例子就是第三种:类的实例方法 下面我们用代码演示上面的三种方法: public class ReferenceDemo { public static void main(String[] args) { // 第一种:引用对象的实例方法 Cat cat = new Cat(1); Function // 第二种:引用类的静态方法 Supplier // 第三种:引用类的实例方法 Function } } 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 // 这里调用 new Cat(Integer) Function 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 // 1. 先按年龄排序(默认递增) // Dog::getAge, 上面介绍的方法引用 // comparingInt, 是Comparator的一个静态方法,返回Comparator Comparator // 2. 如果有相同的年龄,则年龄相同的再按体重排序(如果年龄已经比较出大小,则下面的体重就不会再去比较) Comparator // 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的语法糖,总共有三种: Class::instanceMethod(类的实例方法) Object::instanceMethod(对象的实例方法)Class::staticMethod(类的静态方法)构造引用:就一种,编译器自己可判断是哪个构造函数,语法为Class::ZlJGrmKzonew在lambda中引入外部变量,必须保证这个变量是最终变量,即不再被修改lambda的组合操作,就是链式操作,组合是通过函数式接口的静态方法来组合(静态方法会返回另一个函数式接口的对象) 比如list.sort(comparableAge.thenComparing(comparableWeight));
其中简化后的Cat::getAge,我们就叫做方法引用
方法引用就是引用类或对象的方法;
下面我们列出方法引用的三种情况:
Object::instanceMethod(对象的实例方法)Class::staticMethod(类的静态方法)Class::instanceMethod(类的实例方法)
像我们上面举的例子就是第三种:类的实例方法
下面我们用代码演示上面的三种方法:
public class ReferenceDemo {
public static void main(String[] args) {
// 第一种:引用对象的实例方法
Cat cat = new Cat(1);
Function
// 第二种:引用类的静态方法
Supplier
// 第三种:引用类的实例方法
Function
}
}
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
// 这里调用 new Cat(Integer)
Function
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
// 1. 先按年龄排序(默认递增)
// Dog::getAge, 上面介绍的方法引用
// comparingInt, 是Comparator的一个静态方法,返回Comparator
Comparator
// 2. 如果有相同的年龄,则年龄相同的再按体重排序(如果年龄已经比较出大小,则下面的体重就不会再去比较)
Comparator
// 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的语法糖,总共有三种:
Class::instanceMethod(类的实例方法)
Object::instanceMethod(对象的实例方法)Class::staticMethod(类的静态方法)构造引用:就一种,编译器自己可判断是哪个构造函数,语法为Class::ZlJGrmKzonew在lambda中引入外部变量,必须保证这个变量是最终变量,即不再被修改lambda的组合操作,就是链式操作,组合是通过函数式接口的静态方法来组合(静态方法会返回另一个函数式接口的对象)
比如list.sort(comparableAge.thenComparing(comparableWeight));
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~