概述
我们用Lambda表达式来实现匿名方法。但是如果已经存在某个匿名表达式的实现,并且将其引用,不用每次重新写一个表达式,岂不是很好吗?方法引用就可以做到,它使得Lambda在调用那些已经拥有方法名的方法的代码更简洁、更容易理解。
如果就是Lambda体内只有一行语句,这个语句仅仅是调用了一个已存在的方法。如下:
Arrays.sort(stringsArray,(s1,s2)->s1.compareToIgnoreCase(s2));
在Java8中,我们可以直接通过方法引用来简写(引用)lambda表达式中已经存在的方法。
Arrays.sort(stringsArray, String::compareToIgnoreCase);
这种特性就叫做方法引用(Method Reference)。
什么是方法引用
方法引用是用来直接访问类
或者实例
的已经存在的方法
或者构造方法
。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。
当Lambda表达式中只是执行一个方法调用时,不用新写Lambda表达式,直接通过方法引用的形式可读性更高一些。方法引用是一种更简洁易懂的Lambda表达式。
方法引用特点:
-
方法引用通过方法的名字来指向一个方法。
-
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
-
方法引用使用一对冒号 :: 。
四种形式
java8方法引用有四种形式:
类型 | 语法 | 对应的Lambda表达式 |
---|---|---|
静态方法引用 | 类名::staticMethod | (args) -> 类名.staticMethod(args) |
实例方法引用 | inst::instMethod | (args) -> inst.instMethod(args) |
对象方法引用 | 类名::instMethod | (inst,args) -> 类名.instMethod(args) |
构建方法引用 | 类名::new | (args) -> new 类名(args) |
1、静态方法引用
组成语法格式:ClassName::staticMethodName
注意:
- 静态方法引用比较容易理解,
经static修饰的方法
,和静态方法调用相比,只是把 . 换为 :: - 在目标类型兼容的任何地方,都可以使用静态方法引用。
例子:
String::valueOf 等价于lambda表达式 (s) -> String.valueOf(s)
Math::pow 等价于lambda表达式 (x, y) -> Math.pow(x, y);
class User {
public static String getName() { //static 方法
return "ZhangSan";
}
}
public class Test {
public static void main(String[] args) {
Supplier<String> supplier = () -> User.getName();//传统Lambda语法
System.out.println(supplier.get()); //输出ZhangSan
Supplier<String> supplier2 = User::getName; //方法引用语法
System.out.println(supplier2.get()); //输出ZhangSan
}
}
2.实例方法引用
实例方法引用,顾名思义就是调用已经存在的实例的方法,与静态方法引用不同的是类要先实例化
,静态方法引用类无需实例化,直接用类名去调用。
class User {
public String getName() { //实例方法
return "ZhangSan";
}
}
public class Test {
public static void main(String[] args) {
User user = new User(); //构造一个实例
Supplier<String> supplier = () -> user.getName(); //传统Lambda语法
System.out.println(supplier.get()); //输出ZhangSan
Supplier<String> supplier2 = user::getName; //方法引用语法
System.out.println(supplier2.get()); //输出ZhangSan
}
}
再来看个常见的例子 System.out::println
:
// 创建出一个数组
List<String> strList = Arrays.asList("YangHang", "AnXiaoHei", "LiuPengFei");
strList.forEach(System.out::println);
forEach接收一个Consumer<T>
接口,System.out::println
这段代码其实就是Consumer<T>
接口的一个实现方式,out是System 的一个类成员变量(PrintStream 的实例),println(xxx)符合Consumer<T>
接口,因此,这种用法也是实例引用方式:
public final class System {
public final static PrintStream out = null; //类成员变量
}
public class PrintStream{
public void println(String x) { //符合`Consumer<T>`接口
synchronized (this) {
print(x);
newLine();
}
}
3.对象方法引用(类名::实例方法)
不知道为何翻译成对象方法引用,这种方法引用相对比较复杂,语法是 类名::实例方法
。
这种引用方法时特殊的场景,不是所有的实例方法都可以被冠以类名来调用,我们通常都是调用类方法(static修饰的方法)的时候才这样做。
应为前面有个
我们来看引用的示例:
public static void main(String[] args) {
Predicate<String> predicate = x -> x.isEmpty(); //例子,常规写法
Predicate<String> predicate2 = String::isEmpty; //引用写法
...
}
Predicate是个函数式接口,有个抽象方法,接收一个参数,并返回一个boolean结果。x -> x.isEmpty()
刚好符合,但是这个于lambda表达式又有个特殊情况,第一个入参x刚好是 执行语句的调用者,第二入参y刚好是实例方法isEmpty的入参。读者会疑惑,哪有第二个入参y呢?呵呵,我们可以把第二个入参y看做是空,isEmpty的入参的也是空。
我们来看个例子,入参是多个并且真实存在,我们回顾下上面的例子:
Arrays.sort(stringsArray,(s1,s2)->s1.compareToIgnoreCase(s2)); //常规lambda表达式
Arrays.sort(stringsArray, String::compareToIgnoreCase); //缩写
我们来逐步解析这个例子,Arrays类的sort方法定义如下:
public static <T> void sort(T[] a, Comparator<? super T> c)
sort()第二个参数是一个Comparator,并且为函数式接口,有个抽象方法compare(),jdk8中Comparator接口的源码:
@FunctionalInterface //声明为函数式接口
public interface Comparator<T> {
int compare(T o1, T o2); //2个入参,一个int型返回值
因此,(s1,s2)->s1.compareToIgnoreCase(s2)
表达式中,也是2个入参,一个int型返回值,符合Comparator的Lambda形式的实现。我们再仔细看看这个表达式,巧合的是第一个入参s1,是后面Lambda体内语句的调用者,第二个参数s2是Lambda体内语句的入参,符合简写逻辑 String::compareToIgnoreCase
4 构造方法引用
注意:需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表
保持一致。
如:要获取一个空的User列表:
class People {
People() { //无参数的构造函数
System.out.println("no param");
}
People(String name) { //有一个入参的构造函数
System.out.println("param :" + name);
}
public static void main(String[] args) {
Supplier<People> supplier = () -> new People(); //new People() 调用无参数构造函数
Supplier<People> supplier2 = People::new; //引用方式调用无参数构造函数
supplier2.get(); //打印no param,证明调用无参数构造函数
}
Function<String, People> supplier3 = x -> new People(x); //调用有入参的构造函数
Function<String, People> supplier4 = People::new; //引用方式调用有参数的构造函数
supplier4.apply("zhangsan"); //param :zhangsan,证明调用有参数构造函数
}
构造函数的引用,在多个构造函数时,会自动匹配入参相同的构造函数