上一篇文章,Lambda表达式与函数式接口掌握了没有呢?没有建议先看一下再来阅读本篇文章。
这里给出跳转链接:Lambda表达式与函数式接口
接下来,我们来看看下面的代码,这两个Lambda表达式有什么特点呢?
Function fn = (str) -> str.toUpperCase();
System.out.println(fn.apply("admin"));
Consumer c = (arg) -> System.out.println(arg);
c.accept("hello");
聪明的人可能一下子就看出来了。
第一个Lambda表达式,传入一个参数,并且引用了参数实例的方法。
第二个Lambda表达式,传入一个参数,作为参数去调用其他类的方法。
说白一点,二者的共同点在于,都是调用了一个方法,这样简单的操作。
那么,什么是方法引用?
方法引用是用来直接访问类或者实例里已经存在的方法或者构造方法,方法引用提供了一种引用而不执行方法的方式,如果抽象方法的实现恰好可以使用调用另外一个方法来实现,就有可能可以使用方法引用。
方法引用的类型
类型 | 语法 | 对应的Lambda表达式 |
---|---|---|
静态方法引用 | 类名::staticMethod | (args) -> 类名.staticMethod(args) |
实例方法引用 | inst::instMethod | (args) -> inst.instMethod(args) |
对象方法引用 | 类名::instMethod | (inst, args) -> 类名.instMethod(args) |
构造方法引用 | 类名::new | (args) -> new 类名(args) |
到这里,肯定还有很多人对此感到疑惑,接下来我们来一一进行讲解。
静态方法引用
如果函数式接口的实现恰好可以通过调用一个静态方法来实现,那么就可以使用静态方法引用。
Supplier s = () -> Fun.ret();
Consumer s1 = (arg) -> System.out.println(arg);
Supplier s2 = Fun::ret;
Consumer s3 = System.out::println;
class Fun {
public static String ret() {
return "hello";
}
}
我们看到上面两处都使用了方法引用,是不是两者都是静态方法引用呢?
不是的,我们知道System.out.println()
方法其实不是一个静态方法,所以它不是一个静态方法引用,但是为什么它可以使用方法引用呢?卖个关子,下面我们就会详细说到。
实例方法引用
如果函数式接口的实现恰好可以通过调用一个实例方法来实现,那么就可以使用实例方法引用。
Fun fun = new Fun();
Supplier s = () -> fun.ret();
Consumer s1 = (arg) -> System.out.println(arg);
Function s2 = (str) -> str.toUpperCase();
Supplier s2 = fun::ret;
Consumer s3 = System.out::println;
Function s2 = String::toUpperCase;
class Fun extent Base {
public Fun() {
Supplier s1 = () -> this.ret();
Function fun = (str) -> super.toUpperCase(str);
Supplier s1 = this::ret
Function fun = super::toUpperCase;
}
public String ret() {
return "hello";
}
}
class Base {
public String toUpperCase(String str) {
return str.toUpperCase();
}
}
是不是很眼熟?是的,第二个Lambda表达式就是我们上面讲到静态表达式时卖的关子,其实它是一个实例方法引用,但你们仔细看,这里面还有坑哦,我们第三个Lambda表达式不是实例方法引用哦。
肯定有人问了,第三个Lambda表达式,明明是调用了一个实例的方法,为什么它不是实例方法引用呢,它其实是和我们接下来要讲的对象方法引用有关。
还需要注意的是,我们在使用过程中,也会遇到超类的实例方法引用,使用方法差不多。
对象方法引用
抽象方法的第一个参数类型刚好是实例的类型,抽象方法剩余的参数恰好可以当做实例方法的参数。如果函数式接口的实现能由上面说的实例方法调用来实现的话,那么就可以使用对象方法引用。
看起来概念比较长,我们来解读一下。
我们可以理解为,对象方法引用函数式接口的抽象方法,一定有一个或以上的参数,而且第一个参数就是实例,剩余的参数是实例方法的参数。
Consumer s1 = fun -> fun.ret();
BiFunction bfun1 = (fun, str) -> fun.update(str);
// 这个Lambda表达式无法使用方法引用
BiFunction bfun1 = (fun, str) -> fun.ret();
Consumer s2 = Fun::ret;
BiFunction bfun1 = Fun::update;
class Fun {
public String ret() {
return "hello";
}
public boolean update(String str) {
return true;
}
}
这回真的没有坑了。不过,需要注意的是,我们如何来区分实例方法引用和对象方法引用呢?
我们只需要关注一个点即可,那就是第一个参数实例,是否被用来调用实例方法了。
再来扩展一个知识点,一般我们在使用对象方法引用时,第一个参数大多数是我们自定义的对象,否则的话,要满足业务场景就相对麻烦很多了。
构造方法引用
如果函数式接口的实现恰好可以通过调用一个类的构造方法来实现,那么就可以使用构造方法引用。
Supplier s1 = () -> new Person();
Consumer c1 = (str) -> new Person(str);
Function fun1 = (str) -> new Person(str);
Supplier s2 = Person::new;
Consumer c2 = Person::new;
Function fun1 = Person::new;
Supplier s3 = ArrayList::new;
Supplier s4 = Thread::new;
Supplier s5 = HashSet::new;
Supplier s6 = String::new;
class Person {
public Person() {
}
public Person(String str) {
}
}
构造方法引用理解起来和静态方法引用、实例方法引用差不多,使用也非常简单,这里就不多说了。
我们可以看到,所有的方法引用都是没有()小括号的,因为这只是引用,并没有真正执行,这一点需要留意一下。
到这里,方法的引用就讲完了,方法引用有四种类型,对应不同的场景,不是所有简单调用了方法的Lambda表达式,我们都能用方法引用的哦,这里需要我们好好地理解一下方法引用的含义。