0%

stream and lambda(4) - lambda 方法引用

lambda 表达式,写法格式是比较固定的。即使我们以最简洁的写法,也是在结构范围内的。但是有一种写法,直接突破了常规的格式,那就是方法引用。相信使用 intellij idea 时,代码提示自动帮你用过这种写法:::,提示是 Replace lambda with method reference

总览

方法引用:简单的说,就是 lambda 表达式的内容,刚好是其他类的某个方法。此时,我们就可以直接引用那个类的那个方法。俗称,处男,哦不,方法引用。

《让子弹飞》一定要申遗

方法引用引用语法常规写法
静态方法引用类名::静态方法(参数) -> 类名.静态方法(参数)
构造方法引用类名::构造方法(参数) -> new 类名(参数)
实例方法引用实例化对象::普通方法(参数) -> 实例化对象.普通方法(参数)
对象方法引用类名::普通方法(实例化对象,参数…) -> 类名.普通方法(参数…)

首先,我们定义一个 MethodRef 类,包含有静态方法、普通方法、构造器方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MethodRef {

private String methodName;

public static boolean isNull(String str) {
return str == null;
}

public MethodRef(String methodName) {
this.methodName = methodName;
}

public String getMethodName() {
return methodName;
}

public boolean nameEquals(String name) {
return Objects.equals(methodName, name);
}

@Override
public String toString() {
return "I am " + methodName;
}
}

静态方法引用

格式 :类名::静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void staticRef() {
// 普通写法
Function<String, Boolean> isNull = s -> MethodRef.isNull(s);
System.out.println(isNull.apply("abc"));

isNull = MethodRef::isNull;
System.out.println(isNull.apply(null));

Function<Boolean, String> boolean2String = b -> String.valueOf(b);
System.out.println(boolean2String.apply(true));

boolean2String = String::valueOf;
System.out.println(boolean2String.apply(false));
}

s -> MethodRef.isNull(s) 实际上就是调用 MethodRef 的静态方法,且参数一致,可以使用静态方法引用的格式来写。

构造方法引用

格式:类名::new

1
2
3
4
5
6
7
public void constructorRef() {
Function<String, MethodRef> f = s -> new MethodRef(s);
System.out.println(f.apply("commRef").toString());

f = MethodRef::new;
System.out.println(f.apply("constructorRef").toString());
}

函数式接口的的抽象方法,是通过 MethodRef 的构造方法返回一个实例化对象,且参数一致,可以使用构造方法引用的格式来写。

实例方法引用

格式:实例化对象::普通方法

1
2
3
4
5
6
7
8
9
10
public void instanceRef() {
Arrays.asList("I", "am", "instanceRef").forEach(System.out::println);

MethodRef ref = new MethodRef("instanceRef");
Supplier<String> supplier = () -> ref.getMethodName();
System.out.println(supplier.get());

supplier = ref::getMethodName;
System.out.println(supplier.get());
}

常用的 System.out::println 就是这种方式。普通方法是要有实例化对象才可以调用的,当函数式接口的参数也与实例方法一致时,可以使用实例方法引用的格式来写。

对象方法引用

格式:类名::普通方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void objRef() {
MethodRef objRef = new MethodRef("objRef");

//实例化对象,是函数式接口的第一个参数
Function<MethodRef, String> f = methodRef -> methodRef.getMethodName();
System.out.println(f.apply(objRef));

f = MethodRef::getMethodName;
System.out.println(f.apply(objRef));

//实例化对象,是函数式接口的第一个参数
BiFunction<MethodRef, String, Boolean> bf = (methodRef, s) -> methodRef.nameEquals(s);
System.out.println(bf.apply(objRef, "randomRef"));

bf = MethodRef::nameEquals;
System.out.println(bf.apply(objRef, "randomRef"));

List<String> list = Arrays.asList("objRef2", "I", "am");
list.sort((o1, o2) -> o1.compareTo(o2));
System.out.println(list);
list.sort(String::compareTo);
System.out.println(list);
}

我们知道,通过类名是无法直接调用普通方法的,如果调用必须要先有实例化对象。而对象方法引用的关键点就在于,函数式接口的第一个参数,一定要是实例化对象,可以没有第二第三个参数。当函数式接口的后几个参数与实例化对象的普通方式的参数一致时,就可以使用对象方法引用的格式来写。

总结

方法引用相较 lambda 表达式而言,更简洁易懂一些。比如,MethodRef::new,一看就是用来 new 对象的。

静态方法引用和构造方法引用会易懂一些,直接通过类名引用就可以了。

易让人混淆的是实例方法引用和对象方法引用。记住一点,对于普通方法的引用,如果函数式接口的第一个参数是实例化对象时,才考虑对象方法引用。如果函数式接口首参数不是实例化对象,就不可以使用对象方法引用。

注:本文配套代码可在 github 查看:stream-and-lambda