文章目录
一、概述
在学习 lambda 表达式之后,我们通常使用 lambda 表达式来创建匿名方法。然而,有时候我们仅仅是调用了一个已存在的方法。如下:
Arrays.sort(stringsArray,(s1, s2)->s1.compareToIgnoreCase(s2));
在 Java8 中,我们可以直接通过方法引用来简写 lambda 表达式中已经存在的方法。
Arrays.sort(stringsArray, String::compareToIgnoreCase);
这种特性就叫做方法引用(Method Reference)。
二、什么是方法引用
方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。
简单地说,就是一个Lambda表达式。在Java 8中,我们会使用Lambda表达式创建匿名方法,但是有时候,我们的Lambda表达式可能仅仅调用一个已存在的方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方***更加清晰,Java 8的方法引用允许我们这样做。方法引用是一个更加紧凑,易读的Lambda表达式,注意**方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号 ::
**。
三、方法引用符 ::
双冒号 ::
为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。
3.1 语义分析
String
对象中有一个重载的 compareToIgnoreCase
方法恰好就是我们所需要的。那么对于 compareToIgnoreCase
方法的函数式接口参数,对比下面两种写法,完全等效:
Lambda表达式写法: (s1, s2)->s1.compareToIgnoreCase(s2)
方法引用写法: String::compareToIgnoreCase
第一种语义是指:拿到参数之后经Lambda之手,继而传递给 String.compareToIgnoreCase(String)
方法去处理。
第二种等效写法的语义是指:直接让 String
中的 compareToIgnoreCase
方法来取代Lambda。两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案,更加简洁。
注: Lambda 中 传递的参数 一定是方法引用中 的那个方法可以接收的类型,否则会抛出异常 。
3.2 省略与推导
如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式——它们都将被自动推导。
而如果使用方法引用,也是同样可以根据上下文进行推导,所以也无需指定重载形式——它们也都将被自动推导。
四、方法引用的方式
4.1 通过对象名引用成员方法
-
使用前提:
对象名是已经存在的,成员方法也是已经存在,就可以使用对象名来引用成员方法
-
格式:
对象名::成员方法
函数式接口:
@FunctionalInterface
public interface Printable {
//定义字符串的抽象方法
void print(String s);
}
如果一个类中已经存在了一个成员方法:
public class MethodRefObject {
public void printUpperCase(String str) {
System.out.println(str.toUpperCase());
}
}
那么当需要使用这个 printUpperCase
成员方法来替代 Printable
接口的 Lambda 的时候,已经具有了 MethodRefObject
类的对象实例,则可以通过对象名引用成员方法,代码为:
public class Demo01MethodRef {
private static void printString(Printable printable) {
printable.print("Hello");
}
public static void main(String[] args) {
MethodRefObject obj = new MethodRefObject();
printString(obj::printUpperCase);
}
}
4.2 通过类名称引用静态方法
-
使用前提:
类已经存在,静态成员方法也已经存在,就可以通过类名直接引用静态成员方法 -
格式:
类名::静态成员方法
由于在 java.lang.Math
类中已经存在了静态方法 abs ,所以当我们需要通过Lambda来调用该方法时,有两种写法。首先是函数式接口:
@FunctionalInterface
public interface Calcable {
//定义一个抽象方法,传递一个整数,对整数进行绝对值计算并返回
int calsAbs(int number);
}
第一种写法是使用Lambda表达式:
public class Demo02Lambda {
private static void method(int num, Calcable lambda)
{
System.out.println(lambda.calc(num));
}
public static void main(String[] args) {
method(‐10, n ‐> Math.abs(n));
}
}
第二种更好的方法是使用方法引用:
public class Demo03MethodRef {
private static void method(int num, Calcable lambda) {
System.out.println(lambda.calc(num));
}
public static void main(String[] args) {
method(‐10, Math::abs);
}
}
在这个例子中,下面两种写法是等效的:
- Lambda表达式:
n -> Math.abs(n)
- 方法引用:
Math::abs
4.3 通过 super 引用成员方法
如果存在继承关系,当Lambda中需要出现 super
调用时,也可以使用方法引用进行替代。
首先是函数式接口:
@FunctionalInterface
public interface Greetable {
//定义一个见面的方法
void greet();
}
然后是父类 Human
的内容:
public class Human {
//定义一个sayHello的方法
public void sayHello(){
System.out.println("Hello 我是Human!");
}
}
最后是子类 Man
的内容,其中使用了Lambda的写法:
public class Man extends Human {
//子类重写父类sayHello的方法
@Override
public void sayHello() {
System.out.println("大家好,我是Man!");
}
//定义方法method,参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
//调用method方法,使用Lambda表达式
method(()‐>{
//创建Human对象,调用sayHello方法
new Human().sayHello();
});
//简化Lambda
method(()‐>new Human().sayHello());
//因为有子父类关系,所以存在的一个关键字super,代表父类,所以我们可以直接使用super调用父类的成员方法
method(()‐>super.sayHello());
}
}
但是如果使用方法引用来调用父类中的 sayHello
方***更好,例如另一个子类 Woman
:
public class Woman extends Human {
//子类重写父类sayHello的方法
@Override public void sayHello() {
System.out.println("大家好,我是Woman!");
}
//定义方法method,参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
method(super::sayHello);
}
}
在这个例子中,下面两种写法是等效的:
-
Lambda表达式:
() -> super.sayHello()
-
方法引用:
super::sayHello
4.4 通过 this 引用成员方法
this
代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用 this::成员方法
的格式来使用方法引用。首先是简单的函数式接口:
@FunctionalInterface
public interface Richable {
//定义一个想买什么就买什么的方法
void buy();
}
下面是一个丈夫 Husband
类:
public class Husband {
//定义一个结婚的方法,参数传递Richable接口
private void marry(Richable lambda) {
lambda.buy();
}
/定义一个买房子的方法
public void buyHouse() {
marry(() ‐> System.out.println("买套房子"));
}
public void beHappy() {
marry(() ‐> this.buyHouse());
}
}
如果希望取消掉Lambda表达式,用方法引用进行替换,则更好的写法为:
public class Husband {
private void buyHouse() {
System.out.println("买套房子");
}
private void marry(Richable lambda) {
lambda.buy();
}
public void beHappy() {
marry(this::buyHouse);
}
}
在这个例子中,下面两种写法是等效的:
-
Lambda表达式:
() -> this.buyHouse()
-
方法引用:
this::buyHouse
4.5 类的构造器引用
-
使用前提:
构造方法已知,创建对象已知,就可以使用
类名称
引用new
创建对象 -
格式:
类名称::new
首先是一个简单的 Person
类:
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后是用来创建 Person
对象的函数式接口:
@FunctionalInterface
public interface PersonBuilder {
//定义一个方法,根据传递的姓名,创建Person对象返回
Person builderPerson(String name);
}
要使用这个函数式接口,可以通过Lambda表达式:
public class Demo04Lambda {
public static void printName(String name, PersonBuilder builder) {
System.out.println(builder.buildPerson(name).getName());
}
public static void main(String[] args) {
printName("鞠婧祎", name ‐> new Person(name));
}
}
但是通过构造器引用,有更好的写法:
public class Demo05Lambda {
public static void printName(String name, PersonBuilder builder) {
System.out.println(builder.buildPerson(name).getName());
}
public static void main(String[] args) {
printName("鞠婧祎", Person::name);
}
}
在这个例子中,下面两种写法是等效的:
-
Lambda表达式:
name -> new Person(name)
-
方法引用:
Person::new
4.6 数组的构造器引用
数组也是 Object
的子类对象,所以同样具有构造器,只是语法稍有不同。如果对应到Lambda的使用场景中时,需要一个函数式接口:
@FunctionalInterface
public interface ArrayBuilder {
//定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组
int[] builderArray(int length);
}
在应用该接口的时候,可以通过Lambda表达式:
public class Demo06ArrayInitRef {
/** 定义一个方法 方法的参数传递创建数组的长度和ArrayBuilder接口 方法内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回 */
private static int[] initArray(int length, ArrayBuilder builder) {
return builder.buildArray(length);
}
public static void main(String[] args) {
int[] array = initArray(10, length ‐> new int[length]);
}
}
但是更好的写法是使用数组的构造器引用:
public class Demo07ArrayInitRef {
/** 定义一个方法 方法的参数传递创建数组的长度和ArrayBuilder接口 方法内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回 */
private static int[] initArray(int length, ArrayBuilder builder) {
return builder.buildArray(length);
}
public static void main(String[] args) {
int[] array = initArray((10, int[]::new);
}
}
在这个例子中,下面两种写法是等效的:
-
Lambda表达式:
length -> new int[length]
-
方法引用:
int[]::new
结语
方法引用仅仅是Lambda的配套服务,主要目的是通过名字来获得Lambda,重复利用已有的方法。