一、方法引用(Method Reference)
1、使用场景:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
2、方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
3、要求: 实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
4、语法格式:
格式:使用操作符 “::” 将类(或对象)与方法名分隔开来。
如下三种主要使用情况:
对象::实例方法名
类::静态方法名
类::实例方法名
二、构造器引用
由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用 类名称::new 的格式表示。
格式:
ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。
可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。
三、数组引用
数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同。
格式:
type[] :: new
四、为什么要引入方法引用?
1、冗余的 Lambda 场景
来看一个简单的函数式接口以应用Lambda表达式:
1 @FunctionalInterface
2 public interface Printable {
3 void print(String str);
4 }
在 Printable 接口当中唯一的抽象方法 print 接收一个字符串参数,目的就是为了打印显示它。那么通过Lambda来使用它的代码很简单:
1 public class Demo01PrintSimple {
2 private static void printString(Printable data) {
3 data.print("Hello, World!");
4 }
5 public static void main(String[] args) {
6 printString(s ‐> System.out.println(s));
7 }
8 }
其中 printString 方法只管调用 Printable 接口的 print 方法,而并不管 print 方法的具体实现逻辑会将字符串打印到什么地方去。
而 main 方法通过Lambda表达式指定了函数式接口 Printable 的具体操作方案为:拿到String(类型可推导,所以可省略)数据后,在控制台中输出它。
2、问题分析
这段代码的问题在于,对字符串进行控制台打印输出的操作方案,明明已经有了现成的实现,那就是 System.out对象中的 println(String) 方法。
既然Lambda希望做的事情就是调用 println(String) 方法,那何必自己手动调用呢?
3、用方法引用改进代码
能否省去Lambda的语法格式(尽管它已经相当简洁)呢?只要“引用”过去就好了:
1 public class DemoPrintRef {
2 private static void printString(Printable data) {
3 data.print("Hello, World!");
4 }
5 public static void main(String[] args) {
6 printString(System.out::println);
7 }
8 }
请注意其中的双冒号 :: 写法,这被称为“方法引用”,双冒号是一种新的语法。
4、
五、方法引用符
双冒号 :: 为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。
1、语义分析
例如上例中, System.out 对象中有一个重载的 println(String) 方法恰好就是我们所需要的。那么对于printString 方法的函数式接口参数,对比下面两种写法,完全等效:
① Lambda表达式写法: s -> System.out.println(s);
② 方法引用写法: System.out::println
第一种语义是指:拿到参数之后经Lambda之手,继而传递给 System.out.println 方法去处理。
第二种等效写法的语义是指:直接让 System.out 中的 println 方法来取代Lambda。两种写法的执行效果完全样,而第二种方法引用的写法复用了已有方案,更加简洁
注意:Lambda 中 传递的参数 一定是方法引用中 的那个方法可以接收的类型,否则会抛出异常。
2、推导与省略
如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式——它们都将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。
函数式接口是Lambda的基础,而方法引用是Lambda的孪生兄弟。
下面这段代码将会调用 println 方法的不同重载形式,将函数式接口改为int类型的参数:
1 @FunctionalInterface
2 public interface PrintableInteger {
3 void print(int str);
4 }
由于上下文变了之后可以自动推导出唯一对应的匹配重载,所以方法引用没有任何变化:
1 public class DemoPrintOverload {
2 private static void printInteger(PrintableInteger data) {
3 data.print(1024);
4 }
5 public static void main(String[] args) {
6 printInteger(System.out::println);
7 }
8 }
这次方法引用将会自动匹配到 println(int) 的重载形式
六、方法引用案例
1、情况一:对象::实例方法
如果一个类中已经存在了一个成员方法:
1 public class MethodRefObject {
2 public void printUpperCase(String str) {
3 System.out.println(str.toUpperCase());
4 }
5 }
函数式接口仍然定义为:
1 @FunctionalInterface
2 public interface Printable {
3 void print(String str);
4 }
那么当需要使用这个 printUpperCase 成员方法来替代 Printable 接口的Lambda的时候,已经具有了MethodRefObject 类的对象实例,则可以通过对象名引用成员方法,代码为:
1 public class DemoMethodRef {
2 private static void printString(Printable lambda) {
3 lambda.print("Hello");
4 }
5 public static void main(String[] args) {
6 MethodRefObject obj = new MethodRefObject();
7 printString(obj::printUpperCase);
8 }
9 }
Demo1:
1 //Consumer中的void accept(T t)
2 //PrintStream中的void println(T t)
3 @Test
4 public void test1() {
5 Consumer<String> con1 = str -> System.out.println(str);
6 con1.accept("北京");
7
8 System.out.println("*******************");
9 PrintStream ps = System.out;
10 Consumer<String> con2 = ps::println;
11 con2.accept("beijing");
12 }
Demo2
1 //Supplier中的T get()
2 //Employee中的String getName()
3 @Test
4 public void test2() {
5 Employee emp = new Employee(1001,"Tom",23,5600);
6
7 Supplier<String> sup1 = () -> emp.getName();
8 System.out.println(sup1.get());
9
10 System.out.println("*******************");
11 Supplier<String> sup2 = emp::getName;
12 System.out.println(sup2.get());
13
14 }
2、情况二:类::静态方法
由于在 java.lang.Math 类中已经存在了静态方法 abs ,所以当我们需要通过Lambda来调用该方法时,有两种写法。首先是函数式接口:
1 @FunctionalInterface
2 public interface Calcable {
3 int calc(int num);
4 }
第一种写法是使用Lambda表达式:
1 public class DemoLambda {
2 private static void method(int num, Calcable lambda) {
3 System.out.println(lambda.calc(num));
4 }
5 public static void main(String[] args) {
6 method(‐10, n ‐> Math.abs(n));
7 }
8 }
但是使用方法引用的更好写法是:
1 public class Demo06MethodRef {
2 private static void method(int num, Calcable lambda) {
3 System.out.println(lambda.calc(num));
4 }
5 public static void main(String[] args) {
6 method(‐10, Math::abs);
7 }
8 }
在这个例子中,下面两种写法是等效的:
Lambda表达式: n -> Math.abs(n)
方法引用: Math::abs
Demo1:
1 //Comparator中的int compare(T t1,T t2)
2 //Integer中的int compare(T t1,T t2)
3 @Test
4 public void test3() {
5 Comparator<Integer> com1 = (t1,t2) -> Integer.compare(t1,t2);
6 System.out.println(com1.compare(12,21));
7
8 System.out.println("*******************");
9
10 Comparator<Integer> com2 = Integer::compare;
11 System.out.println(com2.compare(12,3));
12
13 }
Demo2:
1 //Function中的R apply(T t)
2 //Math中的Long round(Double d)
3 @Test
4 public void test4() {
5 Function<Double,Long> func = new Function<Double, Long>() {
6 @Override
7 public Long apply(Double d) {
8 return Math.round(d);
9 }
10 };
11
12 System.out.println("*******************");
13
14 Function<Double,Long> func1 = d -> Math.round(d);
15 System.out.println(func1.apply(12.3));
16
17 System.out.println("*******************");
18
19 Function<Double,Long> func2 = Math::round;
20 System.out.println(func2.apply(12.6));
21 }
3、情况三:类::非静态方法(类::实例方法)
Demo1:
1 // Comparator中的int comapre(T t1,T t2)
2 // String中的int t1.compareTo(t2)
3 @Test
4 public void test5() {
5 Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
6 System.out.println(com1.compare("abc","abd"));
7
8 System.out.println("*******************");
9
10 Comparator<String> com2 = String :: compareTo;
11 System.out.println(com2.compare("abd","abm"));
12 }
Demo2:
1 //BiPredicate中的boolean test(T t1, T t2);
2 //String中的boolean t1.equals(t2)
3 @Test
4 public void test6() {
5 BiPredicate<String,String> pre1 = (s1,s2) -> s1.equals(s2);
6 System.out.println(pre1.test("abc","abc"));
7
8 System.out.println("*******************");
9 BiPredicate<String,String> pre2 = String :: equals;
10 System.out.println(pre2.test("abc","abd"));
11 }
Demo3:
1 // Function中的R apply(T t)
2 // Employee中的String getName();
3 @Test
4 public void test7() {
5 Employee employee = new Employee(1001, "Jerry", 23, 6000);
6
7
8 Function<Employee,String> func1 = e -> e.getName();
9 System.out.println(func1.apply(employee));
10
11 System.out.println("*******************");
12
13
14 Function<Employee,String> func2 = Employee::getName;
15 System.out.println(func2.apply(employee));
16
17
18 }
4、通过 super 引用成员方法
如果存在继承关系,当Lambda中需要出现super调用时,也可以使用方法引用进行替代。首先是函数式接口:
1 @FunctionalInterface
2 public interface Greetable {
3 void greet();
4 }
然后是父类 Human 的内容:
1 public class Human {
2 public void sayHello() {
3 System.out.println("Hello!");
4 }
5 }
最后是子类 Man 的内容,其中使用了Lambda的写法:
1 public class Man extends Human {
2 @Override
3 public void sayHello() {
4 System.out.println("大家好,我是Man!");
5 }
6 //定义方法method,参数传递Greetable接口
7 public void method(Greetable g){
8 g.greet();
9 }
10 public void show(){
11 //调用method方法,使用Lambda表达式
12 method(()‐>{
13 //创建Human对象,调用sayHello方法
14 new Human().sayHello();
15 });
16 //简化Lambda
17 method(()‐>new Human().sayHello());
18 //使用super关键字代替父类对象
19 method(()‐>super.sayHello());
20 }
21 }
但是如果使用方法引用来调用父类中的 sayHello 方法会更好,例如另一个子类 Woman :
1 public class Man extends Human {
2 @Override
3 public void sayHello() {
4 System.out.println("大家好,我是Man!");
5 }
6 //定义方法method,参数传递Greetable接口
7 public void method(Greetable g){
8 g.greet();
9 }
10 public void show(){
11 method(super::sayHello);
12 }
13 }
在这个例子中,下面两种写法是等效的:
Lambda表达式: () -> super.sayHello()
方法引用: super::sayHello
5、通过this引用成员方法
this代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用“this::成员方法”的格式来使用方法引用。首先是简单的函数式接口:
1 @FunctionalInterface
2 public interface Richable {
3 void buy();
4 }
下面是一个丈夫 Husband 类:
1 public class Husband {
2 private void marry(Richable lambda) {
3 lambda.buy();
4 }
5 public void beHappy() {
6 marry(() ‐> System.out.println("买套房子"));
7 }
8 }
开心方法 beHappy 调用了结婚方法 marry ,后者的参数为函数式接口 Richable ,所以需要一个Lambda表达式。
但是如果这个Lambda表达式的内容已经在本类当中存在了,则可以对 Husband 丈夫类进行修改:
1 public class Husband {
2 private void buyHouse() {
3 System.out.println("买套房子");
4 }
5 private void marry(Richable lambda) {
6 lambda.buy();
7 }
8 public void beHappy() {
9 marry(() ‐> this.buyHouse());
10 }
11 }
如果希望取消掉Lambda表达式,用方法引用进行替换,则更好的写法为:
1 public class Husband {
2 private void buyHouse() {
3 System.out.println("买套房子");
4 }
5 private void marry(Richable lambda) {
6 lambda.buy();
7 }
8 public void beHappy() {
9 marry(this::buyHouse);
10 }
11 }
在这个例子中,下面两种写法是等效的:
Lambda表达式: () -> this.buyHouse()
方法引用: this::buyHouse
6、总结
针对于情况1和情况2:方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同!
七、构造器引用案例
构造器引用:和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。
抽象方法的返回值类型即为构造器所属的类的类型。
案例1:无参构造
1 //Supplier中的T get()
2 //Employee的空参构造器:Employee()
3 @Test
4 public void test1(){
5
6 Supplier<Employee> sup = new Supplier<Employee>() {
7 @Override
8 public Employee get() {
9 return new Employee();
10 }
11 };
12 System.out.println("*******************");
13
14 Supplier<Employee> sup1 = () -> new Employee();
15 System.out.println(sup1.get());
16
17 System.out.println("*******************");
18
19 Supplier<Employee> sup2 = Employee :: new;
20 System.out.println(sup2.get());
21 }
案例2:需要一个参数的构造器
1 //Function中的R apply(T t)
2 @Test
3 public void test2(){
4 Function<Integer,Employee> func1 = id -> new Employee(id);
5 Employee employee = func1.apply(1001);
6 System.out.println(employee);
7
8 System.out.println("*******************");
9
10 Function<Integer,Employee> func2 = Employee :: new;
11 Employee employee1 = func2.apply(1002);
12 System.out.println(employee1);
13
14 }
案例3:需要两个参数的构造器
1 //BiFunction中的R apply(T t,U u)
2 @Test
3 public void test3(){
4 BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
5 System.out.println(func1.apply(1001,"Tom"));
6
7 System.out.println("*******************");
8
9 BiFunction<Integer,String,Employee> func2 = Employee :: new;
10 System.out.println(func2.apply(1002,"Tom"));
11
12 }
在这个例子中,下面两种写法是等效的:
Lambda表达式: name -> new Person(name)
方法引用: Employee::new
八、数组引用案例
数组引用:可以把数组看做是一个特殊的类,则写法与构造器引用一致。
案例:
1 //Function中的R apply(T t)
2 @Test
3 public void test4(){
4 Function<Integer,String[]> func1 = length -> new String[length];
5 String[] arr1 = func1.apply(5);
6 System.out.println(Arrays.toString(arr1));
7
8 System.out.println("*******************");
9
10 Function<Integer,String[]> func2 = String[] :: new;
11 String[] arr2 = func2.apply(10);
12 System.out.println(Arrays.toString(arr2));
13
14 }