Java 方法引用---lambda的孪生兄弟

语言: CN / TW / HK

theme: jzman

方法引用

是什么

方法引用通过方法的名字来指向一个方法。方法引用可以使语言的构造更紧凑简洁,减少冗余代码。 方法引用同样是Java 8 引入的新特性,而且和Lambda表达式有着不小的联系,它同样可以根据上下文进行推导,进而可以简化代码,可以说是Lambda的孪生兄弟。下面我会通过最简单的一个例子来展开方法引用:

冗余的Lambda场景

java @FunctionalInterface public interface Printable { void print(String str); } 先定义一个函数式接口,在 Printable 接口当中唯一的抽象方法 print 接收一个字符串参数,目的就是为了打印显示它。那么通过Lambda来使用它的代码很简单: java public class Demo01PrintSimple { private static void printString(Printable data) { data.print("Hello, World!"); } public static void main(String[] args) { printString(s ‐> System.out.println(s)); } } 其中 printString 方法只管调用 Printable 接口的 print 方法,而并不管 print 方法的具体实现逻辑会将字符串打印到什么地方去。而 main 方法通过Lambda表达式指定了函数式接口 Printable 的具体操作方案为:拿到String(类型可推导,所以可省略)数据后,在控制台中输出它。

注:Lambda 中 传递的参数 一定是方法引用中 的那个方法可以接收的类型,否则会抛出异常

使用方法引用改进

java public class Demo02PrintRef { private static void printString(Printable data) { data.print("Hello, World!"); } public static void main(String[] args) { printString(System.out::println); } } 这里的双冒号 :: 写法,这被称为“方法引用”,而双冒号是一种新的语法。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。

怎么用

下面会引出六种不同的方法引用。

通过对象名引用成员方法

这个是最常见的一种用法,与上例相同(System.out 实际上是一个对象,out是System类中的一个静态打印流对象,详细内容参考API文档),如果一个类中已经存在了一个成员方法: java public class MethodRefObject { public void printUpperCase(String str) { System.out.println(str.toUpperCase()); } } 函数式接口仍然定义为: java @FunctionalInterface public interface Printable { void print(String str); } 那么当需要使用这个 printUpperCase 成员方法来替代 Printable 接口的Lambda的时候,已经具有了 MethodRefObject 类的对象实例,则可以通过对象名引用成员方法,代码为: java public class Demo04MethodRef { private static void printString(Printable lambda) { lambda.print("Hello"); } public static void main(String[] args) { MethodRefObject obj = new MethodRefObject(); printString(obj::printUpperCase); } }

通过类名称引用静态方法

由于在 java.lang.Math 类中已经存在了静态方法 abs ,所以当我们需要通过Lambda来调用该方法时,有两种写法。首先是函数式接口: java @FunctionalInterface public interface Calcable { int calc(int num); } 第一种写法是使用Lambda表达式: java public class Demo05Lambda { 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)); } } 但是使用方法引用的更好写法是: java public class Demo06MethodRef { 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

通过super引用成员方法

如果存在继承关系,当Lambda中需要出现super调用时,也可以使用方法引用进行替代。首先是函数式接口: java @FunctionalInterface public interface Greetable { void greet(); } 然后是父类 Human 的内容: java public class Human { public void sayHello() { System.out.println("Hello!"); } } 最后是子类 Man 的内容,其中使用了Lambda的写法: java public class Man extends Human { @Override public void sayHello() { System.out.println("大家好,我是Man!"); } //定义方法method,参数传递Greetable接口 public void method(Greetable g){ g.greet(); } public void show(){ //使用super关键字代替父类对象 method(()‐>super.sayHello()); } } 但是如果使用方法引用来调用父类中的 sayHello 方法会更好,将show方法中的方法体改为: java method(super::sayHello);

通过this引用成员方法

使用和上一种大同小异,这里就不赘述了。

类的构造器引用

由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用 类名称::new 的格式表示。首先是一个简单的 Person 类: java public class Person { private String name; public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } 然后是用来创建 Person 对象的函数式接口: java public interface PersonBuilder { Person buildPerson(String name); } 要使用这个函数式接口,可以通过Lambda表达式: java public class Demo09Lambda { 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)); } } 方法引用优化写法: java public class Demo10ConstructorRef { public static void printName(String name, PersonBuilder builder) { System.out.println(builder.buildPerson(name).getName()); } public static void main(String[] args) { printName("赵丽颖", Person::new); } } 在这个例子中,下面两种写法是等效的: - Lambda表达式: name -> new Person(name) - 方法引用: Person::new

数组的构造器引用

数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同。所以这个案例和上一个其实本质上是一样的只不过上一个是自定义类的构造函数引用,而这个则是数组类的构造函数引用。如果对应到Lambda的使用场景中时,需要一个函数式接口: java @FunctionalInterface public interface ArrayBuilder { int[] buildArray(int length); } 在应用该接口的时候,可以通过Lambda表达式: java public class Demo11ArrayInitRef { 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]); } } 但是更好的写法是使用数组的构造器引用: java public class Demo12ArrayInitRef { 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