Lambda表达式
1、函数式接口
1.
函数式接口当接口只有一个抽象方法时才能实现。
- 必须只有一个抽象方法才能使用,需要注意的是如果接口中有Object类中的抽象方法,那么这个抽象方法不算该接口本身的方法。
- 比如Comparator接口中虽然含有compare和equals两个抽象方法,但equals方法在Object类中是默认实现的,所以Comparator接口还是会满足函数式接口的使用条件。
2.
Lambda表达式是返回一个某个实现了接口方法的对象,其中包含了方法体的具体实现方式,可以通过返回的对象直接调用接口中的抽象方法,如:
import org.junit.Test;
public class Java8Lambda01 {
@Test
public void Test02(){
/*
* Lambda表达式做法
* -> 是Lambda表达式的操作符
* 箭头左边是接口抽象方法的参数列表,run方法没有形参,就用一个()代替
* 箭头右边是在run方法中实现的内容,这里是打印一句话
*/
Runnable runnable = () -> System.out.println("你目前看过的番中感触最深的是?");
runnable.run();
}
@Test
public void Test01(){
//这里演示一下普通做法
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("我们无法一起学习");
}
};
runnable.run();
}
}
可以看出Lambda函数式接口的功能强大之处,正常情况下,是需要实现类去实现接口中的抽象方法,然后才能调用该方法。但上例中却直接由一个MathOperation接口的引用调用了该接口的抽象方法,这是很简便的。
3.
Lambda表达式会根据上下文推断出应该返回什么对象,如:
public void LambdaTest2{
public static void main(String[] args){
/*
*这里构造一个匿名对象,Thread构造器中需要传递一个Runnable类型的参数
*此时Lambda表达式根据上下文知道这里是返回一个Runnable实现类的对象
*/
new Thread( () -> System.out.println("Hello!") ).start();
}
}
4.
Java内置的4大核心函数式接口
接口 | 消费型接口:Consumer | 函数型接口:Function<T,R> | 断定型接口:Predicate | 供给型接口:Supplier |
---|---|---|---|---|
方法 | void accept(T t) | R apply(T t) | boolean test(T t) | T get() |
这些接口都是功能性接口,使用了 @FunctionalInterface标注。了解这些接口,以后如果有需求时就可以直接应用函数式接口,不必再定义自己的接口。此外在java.util.function 包下还有其他大量的功能性接口。
2、方法引用
方法引用也是 JDK 8增加的功能,属于Lambda表达式的变式,主要有3种方式:
Object::instanceMethod //对象::实例方法
Class::staticMethod //类名::静态方法
Class::instanceMethod //类名::实例方法(难!与众不同)
可以看到方法引用是通过 :: 来分隔类名(或对象名)与方法名的。具体用法如下:
import org.junit.Test;
import java.util.function.Consumer;
public class Java8Lambda02 {
@Test
public void Test03(){
/*
*本例中使用的方法:
*Consumer ---> void accept(T t)
*PrintStream ---> void println(T t)
*/
/*Lambda表达式*/
Consumer<String> consumer = str -> System.out.println(str);
consumer.accept("看过很多云");
/*
*方法引用---对象::实例方法
*要求:接口方法、类实例方法的返回类型和参数列表要相同
*注意:System.out返回的是PrintStream对象!!!
*本例可以写成:Consumer<String> consumer = System.out::println;
*/
PrintStream ps = System.out;
Consumer<String> consumer = ps::println;
consumer.accept("却只爱过你");
}
}
@Test
public void Test04(){
/*
*本例中使用的方法:
*Function ---> R apply(T t)
*Math ---> Long round(Double d)
*/
/*Lambda表达式*/
Function<Double, Long> fun1 = aDouble -> Math.round(aDouble);
System.out.println(fun1.apply(4.5));
/*
*方法引用---类::静态方法
*要求:接口方法、类静态方法的返回类型和参数列表要相同
*本例中泛型自己设置成相同的类型和参数
*/
Function<Double, Long> fun2 = Math :: round;
System.out.println(fun2.apply(4.5));
}
@Test
public void test06(){
/*
*使用的方法:
* Comparator ---> int compare(T t1, T t2)
* String ---> int t1.compareTo(t2)
*/
/*Lambda表达式*/
Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
System.out.println(com1.compare("abc", "abd"));
/*
* 方法引用---类::实例方法
* 要求:返回类型相同,接口方法参数列表中的第一个变量,作为类实例方法的调用者
*/
Comparator<String> com2 = String::compareTo;
System.out.println(com2.compare("abc", "abd"));
}
第三种方法引用比较难理解,再看一个例子:
@Test
public void test07() {
/*
*使用的方法:
*Predicate ---> boolean test(T t)
*String ---> boolean s.isEmpty()
*/
/*匿名内部类形式*/
Predicate<String> pre = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.isEmpty();
}
};
System.out.println(pre.test("123"));
/*Lambda表达式*/
Predicate<String> pre1 = s1 -> s1.isEmpty();
System.out.println(pre1.test("123"));
/*方法引用---类::实例方法*/
Predicate<String> pre2 = String::isEmpty;
System.out.println(pre2.test("123"));
}
从第四4个例子也可以看出,方法引用像是Lambda表达式的变形,而Lambda表达式又是匿名内部类的简化,使用Lambda表达式可以让我们编写的代码更加简洁。
3、构造器引用
构造器引用与方法引用差不多,也是属于Lambda表达式的语法糖。通过 类名::new 来实现。
先构造一个示例类Teacher:
public class Teacher {
static int i = 1; //方便实现编号自动+1
private int id;
private String name;
public Teacher(){
id = -1;
name = null;
System.out.println("无参构造器被调用啦!!!");
}
public Teacher(String name){
this.id = i++;
this.name = name;
}
@Override
public String toString() {
return id + " " + name;
}
public int getId() { return id; }
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
下面来看构造器引用与匿名内部类、Lambda表达式的区别
public class NewTest {
@Test
public void test1(){
/*
*需要用到的接口方法
* Supplier ---> T get()
*/
/*方式1:匿名内部类*/
Supplier<Teacher> sup1 = new Supplier<Teacher>() {
@Override
public Teacher get() {
return new Teacher(); //返回无参构造器
}
};
sup1.get();
/*方式2:Lambda表达式*/
Supplier<Teacher> sup2 = () -> new Teacher();
sup2.get();
/*方法3:构造器引用*/
Supplier<Teacher> sup3 = Teacher::new;
sup3.get();
}
}
上例调用的是无参构造器,再来看看如何调用含参的构造器:
@Test
public void test2(){
/*
*需要用到的接口方法
*Function<T,R> ---> R apply(T t)
*/
/*方式1:匿名内部类*/
Function<String, Teacher> fun1 = new Function<String, Teacher>() {
@Override
public Teacher apply(String s) {
return new Teacher(s);
}
};
Teacher tc1 = fun1.apply("黎老师");
System.out.println(tc1);
/*方式2:Lambda表达式*/
Function<String, Teacher> fun2 = s1 -> new Teacher(s1);
Teacher tc2 = fun2.apply("许老师");
System.out.println(tc2);
/*方式3:构造器引用*/
Function<String, Teacher> fun3 = Teacher::new;
Teacher tc3 = fun3.apply("欧老师");
System.out.println(tc3);
}
从上面两个例子中可以看到,实现构造器引用需要:
- 找到一个有返回类型的泛型接口(只含一个抽象方法),保证返回的类型能设置成相应的类名;
- 接口方法的参数有多少个,就能调用相同参数个数的构造器。如上两例中 get() 调用无参,apply(T t) 调用只含一个参数的构造器,它们都有返回值,返回类型为Teacher。
4、数组引用
数组引用与含一个参数的构造器引用几乎一样,传入数组的长度,返回相应数组的类型,如:
@Test
public void Test3(){
/*匿名内部类*/
Function<Integer, Teacher[]> fun1 = new Function<Integer, Teacher[]>() {
@Override
public Teacher[] apply(Integer integer) {
return new Teacher[integer];
}
};
Teacher[] teachers1 = fun1.apply(3);
System.out.println(Arrays.toString(teachers1));
/*Lambda表达式*/
Function<Integer, Teacher[]> fun2 = integer -> new Teacher[integer];
Teacher[] teachers2 = fun1.apply(6);
System.out.println(Arrays.toString(teachers2));
/*数组引用*/
Function<Integer, Teacher[]> fun3 = Teacher[]::new;
Teacher[] teachers3 = fun1.apply(9);
System.out.println(Arrays.toString(teachers3));
}
上面例子都采取不断简化的语法,先用常见的匿名内部类,再到Lambda表达式,再到各种引用。这样做是方便对比,可以看出要使用Lambda表达和方法引用、构造器引用、数组引用,都要了解接口抽象方法的参数列表和返回类型 。 这些接口除了能有和Object类相同的方法外,只含一个抽象方法(如Comparator接口中的equals方法,虽然在接口中也是抽象方法,但要排除在外),但可以含有其他默认方法和静态方法。
这章知识点我看书和视频好久,但一直都感觉没掌握好,只能先行将已经学会的知识点记下,以后有机会再仔细看看核心技术卷1的内容,希望各位大佬对笔记中有错的部分多加指点,不胜感激!!