参考:
https://blog.csdn.net/w1764662543/article/details/89154408
https://blog.csdn.net/qq_31807385/article/details/82670505
https://www.cnblogs.com/nnxud/p/9827704.html
Java核心技术
Lambda表达式
第一次认真学习Lambda表达式,有错误还望指正。
Lambda表达式简介
可以将Lambda表达式理解为一个匿名函数;Lambda表达式允许将一个函数作为另外一个函数的参数; 我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码作为实参),也可以理解为函数式编程,将一个函数作为参数进行传递。
Lambda表达式只能用来简化仅包含一个抽象方法的接口的创建。
(1)只能是接口
否则报:Target type of a lambda conversion must be an interface
(2)只能是含有一个抽象方法的接口
否则报:Multiple non-overriding abstract methods found xxx
替代匿名内部类
lambda表达式用得最多的场合就是替代匿名内部类,而实现Runnable接口是匿名内部类的经典例子。
// 1.使用匿名类
Thread th1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("2.匿名类的Runnable...");
}
});
th2.start();
// 2.使用Lambda表达式
Runnable runnable = ()->System.out.println("线程启动了");
Thread th2 = new Thread(runnable);
// 调用th2.start()时会执行Lambda表达式的主体
th2.start();
// 3.更简洁的写法(但这种形式不如形式2方便人理解)
new Thread(()-> System.out.println("3.lambda表达式实现Runnable...")).start();
Lambda表达式的形式
([Lambda参数列表,即形参列表]) -> {Lambda体,即方法体}
使用 "->"将参数和实现逻辑分离;( ) 中的部分是需要传入Lambda体中的参数;{ } 中的部分,接收形参列表中的参数,完成一定的功能。->右边就相当于实现了接口中的抽象方法。此时Lambda表达式就是一个可用的接口实现类了。
若Lambda体中只有一条语句,return和大括号都可以省略不写。一定要注意,如果Lambda体中写了return,则必须加上{}。除此之外,Lambda表达式的形参列表的数据类型也可以省略不写,因为编译器可以通过上下文推断出数据类型。
package learnlambda;
// 自定义一个函数式接口
interface MyInterface{
int add(int a,int b);
}
public class LambdaTest05 {
public static void main(String[] args) {
MyInterface mInstance1 = (x,y)-> x+y;
System.out.println(mInstance1.add(2, 3));
MyInterface mInstance2 = (x,y)-> { return x+y; };
System.out.println(mInstance2.add(2, 3));
}
}
方法引用
接口中要被实现的抽象方法的形参列表和返回值类型,必须与方法引用的方法的形参列表和返回值类型保持一致,否则不能使用方法引用。其实就是使用已存在的方法作为函数式接口中抽象方法的实现,因此已存在的方法的返回值类型与形参列表同接口中的抽象方法一致。
在方法引用中,使用操作符 “::”将类(或对象)与方法名分隔开来。值得注意的是,在使用方法引用给接口变量赋值的时候,并不需要给方法提供形式参数,而仅在调用的时候提供实参即可,这与先前的lambda表达式并无不同。
以下是方法引用的3种方式。
- •object::instanceMethod
- •Class::staticMethod
- •Class::instanceMethod
注意,使用方法引用和静态方法的概念不要弄混淆。
// 2. 比较器的示例
// 1.以最简单的lambda表达式实现
Comparator<Integer> com1 = (a,b)->{
if(a>b){
return 1;
}else if(a<b){
return -1;
}else{
return 0;
}
};
// 2.使用Lambda表达式(这种形式的Lambda表达式必须指明形式参数)
Comparator<Integer> com2 = (a,b)-> Integer.compare(a,b);
// 3.使用方法引用(虽然Integer中的compare方法不是静态的)
// 注意,赋值给接口变量,但并未指明形式参数
Comparator<Integer> com3 = Integer::compare;
// 调用方法
int res1 = com1.compare(2, 3);
int res2 = com2.compare(4, 5);
int res3 = com3.compare(7,6);
System.out.println("res1 = "+res1);
System.out.println("res2 = "+res2);
System.out.println("res3 = "+res3);
构造器引用
构造器引用的方法名为new,如Person::new 就表示Person构造器的一个引用。
Supplier<Student> supp = () -> new Student("zhao",23);
// 1.使用方法引用
Supplier<Student> supp2 = Student::new;
4种形式的Lambda表达式示例
Lambda表达式中,存在1)无参无返回值、2)有参无返回值、3)无参有返回值、4)有参有返回值,4种情况。
package learnlambda;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Random;
import java.util.stream.Stream;
public class LambdaTest01 {
public static void main(String[] args) {
// 3.使用lambda表达式实现,不加{}
// (1)无参无返回值
// new Thread(()-> System.out.println("3.lambda表达式实现Runnable...")).start();
// (1)的另一种写法(更容易理解)
Runnable runnable = ()->System.out.println("线程启动了");
Thread th1 = new Thread(runnable);
th1.start();
// (2)有参无返回值
ArrayList<String> list = new ArrayList<>();
list.add("zhao");
list.add("qian");
list.add("sun");
list.add("li");
list.forEach( (tele)->{System.out.println(tele);} );
// (3)无参有返回值(当方法体只有一个return时,可以省略return)
Random random = new Random();
Stream<Integer> stream = Stream.generate( () -> random.nextInt(100) );
// Stream<Integer> stream = Stream.generate(() ->{ return random.nextInt(100);});
stream.forEach(t -> System.out.println(t));
// (4)有参有返回值
// 需求:按照字符串的长度排序
String[] names ={"Bough","Paul","Peter","Alex"};
// sort方法第2个参数需要传入一个实现了Comparator接口的对象
// 而Comparator接口中含有抽象方法int compare(T o1, T o2);
// 重写该方法 则应当是:public int compare(String first, String second)
// { return first.length() - second.length();}
//
String[] names2 ={"Bough","Paul","Peter","Alex"};
Comparator<String> com = (first,second) -> first.length()- second.length();
Arrays.sort(names2,com);
System.out.println(Arrays.toString(names2));
// (4)的另一种简洁的写法
Arrays.sort(names, (first,second)-> first.length() - second.length());
System.out.println(Arrays.toString(names));
}
}
变量作用域
在lambda表达式中访问外围方法或类中的变量,由于使用lambda表达式的重点是延时执行,所以lambda表达式可能在外围方法调用返回很久之后才运行,而那时这个变量已经不存在了,所以lambda表达式必须存储自由变量的值。
如Java核心技术一书中的例子,在lambda表达式中访问外围方法或类中的变量。
public static void repeatMessage(String text,int delay){
ActionListener listener = event ->{
System.out.println(text);
Toolkit.getDefaultToolkit().beep();
};
new Timer(delay,listener).start();
}
// 调用
repeatMessage("Hello",1000);
注意看,lambda 表达式中的变量 text,实际上是 repeatMessage 方法的一个参数变量。仔细想想,这里好像会有问题,尽管不那么明显。lambda 表达式的代码可能会在repeatMessage 调用返回很久以后才运行(个人理解:这可能主要是因为Lambda表达式的延时执行),而那时这个参数变量已经不存在了。
这里需要说明一下,自由变量指的是非lambda表达式的参数且不在lambda体中定义的变量。在lambda表达式中,只能引用值不会改变的变量。如果在lambda表达式中改变变量,并发执行多个动作时就会不安全。实际上,lambda表达式中捕获的变量必须是最终变量,最终变量的意思是这个变量初始化之后就不会再为它赋新值。
函数式接口
函数式接口的定义
如果一个接口只有一个抽象方法,那么就可以称该接口是函数式接口。当然,接口中既可以声明非抽象方法(Java8中可以声明static方法),也可以重写Object类的方法,如toString或clone,这些方法都有可能会让方法不再是抽象的。当需要这种接口的对象时,就可以提供一个lambda表达式。实际上,在Java中,对lambda表达式所能做的也只能是将其转换为函数式接口。值得注意的是,lambda表达式与函数式接口中的抽象方法的参数与返回值是一一对应的,即如果函数式接口中的抽象方法是有返回值,有参数的,那么要求Lambda表达式也是有返回值,有参数的(以此类推)。
常见的函数式接口
示例
Student类
package learnlambda;
public class Student {
private String name;
private int age;
public Student(){}
public Student(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Student [ name :"+
name+", age :"+age
+"]";
}
}
测试代码
package learnlambda;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class LambdaTest03 {
/**
* 4种函数式接口的使用
* @param args
*/
public static void main(String[] args) {
// 1. 函数式接口的对象cons使用Lambda表达式相当于实现了Consumer接口的抽象方法accept
// 所以当变量cons调用accept方法即在使用Lambda体。
Consumer<String> cons = e->System.out.println("接收到的字符串 :"+e);
cons.accept("woojopj");
// 2.Supplier接口
Supplier<Student> supp = () -> new Student("zhao",23);
Student stu1 = supp.get();
System.out.println(stu1.toString());
Supplier<String> supp2 = ()-> "hello world".substring(2);
String strSub = supp2.get();
System.out.println("2.Supplier 的结果: "+strSub);
// 3.Function接口
Function<Student,String> func = (stu) ->{
return stu.getName();
};
Student stu2 =new Student("zhao",23);
String str =func.apply(stu2);
System.out.println("3.Function 的结果: "+str);
// 4.Predicate接口
// 判断学生年纪是否在20-30岁之间,否则返回错
Predicate<Student> pre = (stu) ->
{
int age = stu.getAge();
if( age>= 20 && age<=30)
{
return true;
}else
{
return false;
}
};
System.out.println("4.Predicate 的结果 : "+pre.test(stu2));
}
}
@FunctionalInterface注解
JDK1.8之后,如果是函数式接口,则可以添加 @FunctionalInterface注解表示这是一个函数式接口,但这并不是表示必须要使用该注解。根据定义,任何有一个抽象方法的接口都是函数式接口。不过使用 @FunctionalInterface注解确实是一个很好的做法。这样做有两个优点。 (1)如果你无意中增加了另一个非抽象方法, 编译器会产生一个错误消息。(2) 另外 javadoc 页里会指出你的接口是一个函数式接口。
@FunctionalInterface
interface MyInterface{
int add(int a,int b);
}