Java方法引用和构造器引用详细解释
1 什么是方法/构造器引用
简单来说,方法引用是对lambda
表达式的一种更加简便的写法。
所谓引用
,就是对当前已存在的一段代码的借用。
方法引用
则是隐式借用已经存在的方法作为现成的执行逻辑,而不必在lambda
表达式中显示调用该方法,或者重写这一部分代码。
同理,构造器引用
则是隐式借用某个类的构造函数创建对象的执行逻辑。
2 举个简单的例子
假设有以下方法,它接收一个泛型参数t
,然后使用Comsumer
函数式接口进行动态处理:
public class FI1 {
public static <T> void accept(T t, Consumer<T> consumer) {
consumer.accept(t);
}
}
假如,我们只需要简单打印一个字符串,可以按照如下方式进行显示调用引用的println()
方法:
FI1.accept("Xianhuii", t -> {
System.out.println(t);
});
或者按照如下方式进行重写代码:
FI1.accept("Xianhuii", t -> {
PrintStream out = System.out;
synchronized (out) {
out.print(t);
out.print("\n\b");
}
});
我们可以查看Consumer#accept
方法的格式:
void accept(T t);
以及System.out.println()
方法的格式:
public void println(String x) {}
发现它俩的形参数量和返回值类型都一致。同时,我们需要的业务逻辑与println()
方法定义的一致。
因此,我们可以直接引用该方法:
FI1.accept("Xianhuii", System.out::println);
相当于将System.out.println(String)
方法等价替换了accept(T)
方法,并使用了现有的执行逻辑。
3 方法引用的格式
方法引用可以分成三种情况:
对象::实例方法
类::静态方法
类::实例方法
这三种情况也分别对应着不同格式(形参和返回值)的lambda
表达式/函数式接口方法。
但是我们需要注意:任何情况下,被引用方法的执行逻辑必须与lambda
表达式中的执行逻辑一致。
3.1 对象::实例方法
对象::实例方法
引用的是对象的方法,::
前表示被引用的对象,::
后表示被引用对象的实例方法。
3.1.1 Consumer<T>
函数式接口:
public interface Consumer<T> {
void accept(T t);
}
lambda
表达式:
(t) -> {
// 相同的业务逻辑
}
方法引用:
obj::accetp
被引用方法:
public void accept(T t) {
// 相同的业务逻辑
}
3.1.2 Function<T, R>
函数式接口:
public interface Function<T, R> {
R apply(T t);
}
lambda
表达式:
(t) -> {
// 相同的业务逻辑
return r;
}
方法引用:
obj::apply
被引用方法:
public R apply(T t) {
// 相同的业务逻辑
return r;
}
3.1.3 Supplier<T>
函数式接口:
public interface Supplier<T> {
T get();
}
lambda
表达式:
() -> {
// 相同的业务逻辑
return t;
}
方法引用:
obj::get
被引用的方法:
public T get() {
// 相同的业务逻辑
return t;
}
3.1.4 Predicate<T>
函数式接口:
public interface Predicate<T> {
boolean test(T t);
}
lambda
表达式:
(t) -> {
// 相同的业务逻辑
return flag;
}
方法引用:
obj::test
被引用的方法:
public boolean test(T t) {
// 相同的业务逻辑
return flag;
}
3.2 类::静态方法
类::静态方法
引用的是类的静态方法,::
前表示被引用的类,::
后表示被引用类的静态方法。
类::静态方法
的转换方式与对象::实例方法
相同,只是引用对象和引用方法有所区别。
3.2.1 Consumer<T>
函数式接口:
public interface Consumer<T> {
void accept(T t);
}
lambda
表达式:
(t) -> {
// 相同的业务逻辑
}
方法引用:
Clz::accetp
被引用方法:
public static void accept(T t) {
// 相同的业务逻辑
}
3.2.2 Function<T, R>
函数式接口:
public interface Function<T, R> {
R apply(T t);
}
lambda
表达式:
(t) -> {
// 相同的业务逻辑
return r;
}
方法引用:
Clz::apply
被引用方法:
public static R apply(T t) {
// 相同的业务逻辑
return r;
}
3.2.3 Supplier<T>
函数式接口:
public interface Supplier<T> {
T get();
}
lambda
表达式:
() -> {
// 相同的业务逻辑
return t;
}
方法引用:
Clz::get
被引用的方法:
public static T get() {
// 相同的业务逻辑
return t;
}
3.2.4 Predicate<T>
函数式接口:
public interface Predicate<T> {
boolean test(T t);
}
lambda
表达式:
(t) -> {
// 相同的业务逻辑
return flag;
}
方法引用:
Clz::test
被引用的方法:
public static boolean test(T t) {
// 相同的业务逻辑
return flag;
}
3.3 类::实例方法
类::实例方法
本质上引用的是对象的实例方法,只是对象是lambda
表达式的第一个形参。
::
前表示被引用对象所属的类,::
后表示被引用对象的实例方法。
3.3.1 Consumer<T>
函数式接口:
public interface Consumer<T> {
void accept(T t, P p);
}
lambda
表达式:
(t) -> {
t.fun(p);
}
方法引用:
Clz::fun
被引用方法:
public void fun(P p) {
// 相同的业务逻辑
}
3.3.2 Function<T, R>
函数式接口:
public interface Function<T, R> {
R apply(T t, P p);
}
lambda
表达式:
(t) -> {
// 相同的业务逻辑
return t.fun(p);
}
方法引用:
Clz::fun
被引用方法:
public static R fun(P p) {
// 相同的业务逻辑
return r;
}
3.3.3 Supplier<T>
函数式接口:
public interface Supplier<T> {
R get(T t);
}
lambda
表达式:
() -> {
// 相同的业务逻辑
return t.fun();
}
方法引用:
Clz::fun
被引用的方法:
public static R fun() {
// 相同的业务逻辑
return r;
}
3.3.4 Predicate<T>
函数式接口:
public interface Predicate<T> {
boolean test(T t, P p);
}
lambda
表达式:
(t) -> {
// 相同的业务逻辑
return t.fun(p);
}
方法引用:
Clz::fun
被引用的方法:
public static boolean fun(P p) {
// 相同的业务逻辑
return flag;
}
4 构造器引用的格式
构造器引用可以是一种特殊的类::静态方法
引用,只不过这里的静态方法是构造函数
。
构造器引用的格式为:类::new
。
lambda
表达式/函数式接口的形参,对应构造函数的形参。并且编译器会根据形参的个数和类型,推断出正确的构造函数进行引用。
由于构造函数一定会返回实例对象,所以对应lambda
表达式也必须有返回值。
4.1 类::new
4.1.1 Function<T, R>
函数式接口:
public interface Function<T, R> {
R apply(T t);
}
lambda
表达式:
(t) -> {
return new Date(t);
}
方法引用:
Date::new
被引用方法:
public Date(long date) {
fastTime = date;
}
4.1.2 Supplier<T>
函数式接口:
public interface Supplier<T> {
R get();
}
lambda
表达式:
() -> {
return new Date();
}
方法引用:
Date::new
被引用的方法:
public Date() {
this(System.currentTimeMillis());
}
4.2 类[]::new
如果需要创建数组对象,可以引用数组的构造函数。
需要注意的是,此时必须传入一个参数,作为数组的长度。
此外,这种方式无法为每个值赋值,因此最终得到的数组值都为null
。
4.1.1 Function<T, R>
函数式接口:
public interface Function<T, R> {
R apply(T t);
}
lambda
表达式:
(t) -> {
return new Date[t];
}
方法引用:
Date[]::new
被引用方法:
new Date[t];