Java 8 方法引用
引言
在学习lambda表达式之后,我们通常使用lambda表达式来创建匿名方法。然而,有时候我们仅仅是调用了一个已存在的方法。如下:
Arrays.sort(stringsArray,(s1,s2)->s1.compareToIgnoreCase(s2));
在Java8中,我们可以直接通过方法引用来简写lambda表达式中已经存在的方法。
Arrays.sort(stringsArray, String::compareToIgnoreCase);
介绍
这种特性就叫做方法引用(Method Reference)。
方法引用通过方法的名字来指向一个方法。
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用使用一对冒号 ::
引用类型
方法引用的标准形式是:类名::方法名
。(注意:只需要写方法名,不需要写括号)
有以下四种形式的方法引用,用的最多的应该就是第三种:
类型 | 示例 |
引用静态方法 | ContainingClass::staticMethodName |
引用某个对象的实例方法 | containingObject::instanceMethodName |
引用某个类型的任意对象的实例方法 | ContainingType::methodName |
引用构造方法 | ClassName::new |
案例(对比使用lambda)
package jdk8.lambda; import java.time.LocalDate; import java.util.Arrays;
import java.util.function.Function;
import java.util.function.Supplier; /** * jdk8.lambda * * @Description: * @author: changliu * @Date: 2019/7/31 */ public class refer { /** * 实体类-人 */ public static class Person{ public Person(String name, LocalDate birthday) { this.name = name; this.birthday = birthday; }
public Person(String name){
this.name = name;
}
public Person(){
} private String name; private LocalDate birthday; public String getName(){
return name;
}
public static int compareByAge(Person a, Person b) { return a.birthday.compareTo(b.birthday); } } public static void main(String[] args) { Person[] pArr = new Person[]{ new Person("003", LocalDate.of(2016,9,1)), new Person("001", LocalDate.of(2016,2,1)), new Person("002", LocalDate.of(2016,3,1)), new Person("004", LocalDate.of(2016,12,1))}; //使用lambda表达式和类的静态方法 Arrays.sort(pArr, (a , b) -> Person.compareByAge(a, b)); //使用方法引用,引用的是类的静态方法 Arrays.sort(pArr, Person::compareByAge); System.out.println(Arrays.asList(pArr));
String var = "abcd";
//使用方法引用,引用某个对象的实例方法
Function<Integer, Character> biFunction = var::charAt;
//lambda写法
Function<Integer,Character> biLambdaFun = index -> var.charAt(index);
System.out.println(biFunction.apply(3));
Person p = new Person("小命",LocalDate.of(2016,9,1));
//使用方法引用,引用某个类型的任意对象的实例方法
Function<Person,String> pf = Person::getName;
//lambda写法
Function<Person,String> pfLam = person -> person.getName();
System.out.println(pf.apply(p));
//使用方法引用,引用构造方法(无参)
Supplier<Person> sp = Person::new;
//使用方法引用,引用构造方法(带参)
Function<String,Person> fsp = Person::new;
//lambda写法
Function<String,Person> fspLam = name -> new Person(name);
System.out.println(sp.get());
System.out.println(fsp.apply("张三").getName());
} }
这个例子中红色标注的是使用了方法引用,通过对比lambda写法,可以看出更为简洁,同样也能看出方法引用仅在特定场合下可以使用,假设是用lambda做一系列的逻辑,那么方法引用就不合适了。
接下来方法引用还有个重要的特性需要知晓,那就是使用方法引用则隐含了引入的外部变量一定是不变的,所以可以不用加final修饰,也不像lambda引入的外部变量是隐含的final修饰,换句话说,只要可以用方法引用,那么引入的外部变量是可以随时修改指向的。这个地方应该怎么理解呢?接下来我们举个🌰
从上图可以看出,方法引用idea并没有提示我们有误,而lambda表达式提示有误,而且错误指的是var应该由final修饰或者符合final修饰。
那么前面有说过“方法引用”实际是lambda表达式的一种特定情况下的写法,并且更加精简。所以“方法引用”不应该遵循lambda表达式的特点以及限定吗?答案是确定的,“方法引用”确实要遵循,但是通过总结“方法引用”的四种表达方式我们可以发现一个现象,即“方法引用”因为固定的场景及写法,所以一旦用上了“方法引用”,是无法修改引入变量的地址的,可以确定引入变量在外部和内部均指向同一个地址。
这里又牵扯出另一个问题,为什么lambda要求引入的变量必须要是final修饰的?这是因为lambda本身就属于匿名内部类,所以遵循匿名内部类使用外部变量的要求。那么匿名内部类为什么要求外部变量是final修饰呢?简单的来说,是为了保证局部变量和内部类中复制品 的数据一致性,如果想了解为啥,可以看看这个https://blog.csdn.net/u011617742/article/details/51613519
所以通过追溯,我们知道了加final的目的,自然就知道为啥“方法引用”可以不受限制,因为你用它,它就能保证内外的数据一致性,但是lambda不行,那是因为lambda是可以进行扩展的,通俗来讲就是可以写很多行代码,所以无法提前预知引入的外部变量会发生什么事情,所以lambda是要求必须是final修饰的。