文档

Java™教程
隐藏目录
方法引用
路径:学习Java语言
课程:类和对象
章节:嵌套类
子章节:Lambda表达式

方法引用

你可以使用lambda表达式来创建匿名方法。然而,有时候lambda表达式只是调用一个已经存在的方法。在这种情况下,通过引用已存在的方法来引用它往往更清晰。方法引用使您可以做到这一点;它们是紧凑且易读的lambda表达式,用于已经有名称的方法。

再次考虑在Person类中讨论的Lambda表达式部分:

public class Person {

    // ...
    
    LocalDate birthday;
    
    public int getAge() {
        // ...
    }
    
    public LocalDate getBirthday() {
        return birthday;
    }   

    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }
    
    // ...
}

假设您的社交网络应用程序的成员包含在一个数组中,并且您想按年龄对数组进行排序。您可以使用以下代码(在示例MethodReferencesTest中查找此部分的代码摘录):

Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);

class PersonAgeComparator implements Comparator<Person> {
    public int compare(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
        
Arrays.sort(rosterAsArray, new PersonAgeComparator());

此调用sort的方法签名如下:

static <T> void sort(T[] a, Comparator<? super T> c)

请注意,接口Comparator是一个函数式接口。因此,您可以使用lambda表达式而不是定义并创建实现Comparator的类的新实例:

Arrays.sort(rosterAsArray,
    (Person a, Person b) -> {
        return a.getBirthday().compareTo(b.getBirthday());
    }
);

然而,比较两个Person实例的出生日期的这种方法已经存在,即Person.compareByAge。您可以在lambda表达式的主体中调用此方法:

Arrays.sort(rosterAsArray,
    (a, b) -> Person.compareByAge(a, b)
);

因为此lambda表达式调用了一个已经存在的方法,所以您可以使用方法引用代替lambda表达式:

Arrays.sort(rosterAsArray, Person::compareByAge);

方法引用Person::compareByAge在语义上与lambda表达式(a, b) -> Person.compareByAge(a, b)相同。它们都具有以下特点:

方法引用的种类

方法引用有四种种类:

种类 语法 示例
静态方法引用 ContainingClass::staticMethodName Person::compareByAge
MethodReferencesExamples::appendStrings
特定对象的实例方法引用 containingObject::instanceMethodName myComparisonProvider::compareByName
myApp::appendStrings2
特定类型的任意对象的实例方法引用 ContainingType::methodName String::compareToIgnoreCase
String::concat
构造函数引用 ClassName::new HashSet::new

下面的示例,MethodReferencesExamples,包含了前三种方法引用的示例:

import java.util.function.BiFunction;

public class MethodReferencesExamples {
    
    public static <T> T mergeThings(T a, T b, BiFunction<T, T, T> merger) {
        return merger.apply(a, b);
    }
    
    public static String appendStrings(String a, String b) {
        return a + b;
    }
    
    public String appendStrings2(String a, String b) {
        return a + b;
    }

    public static void main(String[] args) {
        
        MethodReferencesExamples myApp = new MethodReferencesExamples();

        // 用lambda表达式调用mergeThings方法
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", (a, b) -> a + b));
        
        // 静态方法引用
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", MethodReferencesExamples::appendStrings));

        // 特定对象的实例方法引用
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", myApp::appendStrings2));
        
        // 特定类型的任意对象的实例方法引用
        System.out.println(MethodReferencesExamples.
            mergeThings("Hello ", "World!", String::concat));
    }
}

所有的System.out.println()语句都会打印相同的内容:Hello World!

BiFunctionjava.util.function包中的众多函数式接口之一。 BiFunction函数式接口可以表示接受两个参数并产生结果的lambda表达式或方法引用。

对静态方法的引用

Person::compareByAgeMethodReferencesExamples::appendStrings这两个方法引用是对静态方法的引用。

对特定对象的实例方法的引用

以下是对特定对象的实例方法的引用的示例:

class ComparisonProvider {
    public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }
        
    public int compareByAge(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);

方法引用myComparisonProvider::compareByName调用了对象myComparisonProvidercompareByName方法。JRE推断出了方法的类型参数,本例中为(Person, Person)

类似地,方法引用myApp::appendStrings2调用了对象myAppappendStrings2方法。JRE推断出了方法的类型参数,本例中为(String, String)

对特定类型的任意对象的实例方法的引用

以下是对特定类型的任意对象的实例方法的引用的示例:

String[] stringArray = { "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

方法引用String::compareToIgnoreCase的等效lambda表达式的形式参数列表为(String a, String b),其中ab是用于更好地描述此示例的任意名称。该方法引用将调用a.compareToIgnoreCase(b)方法。

类似地,方法引用String::concat将调用a.concat(b)方法。

对构造函数的引用

您可以使用new关键字引用构造函数,与引用静态方法的方式相同。以下方法将元素从一个集合复制到另一个集合:

public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
    DEST transferElements(
        SOURCE sourceCollection,
        Supplier<DEST> collectionFactory) {
        
    DEST result = collectionFactory.get();
    for (T t : sourceCollection) {
        result.add(t);
    }
    return result;
}

函数式接口Supplier包含一个方法get,该方法不接受任何参数并返回一个对象。因此,您可以使用lambda表达式调用transferElements方法,如下所示:

Set<Person> rosterSetLambda =
    transferElements(roster, () -> { return new HashSet<>(); });

您可以使用构造函数引用替代lambda表达式,如下所示:

Set<Person> rosterSet = transferElements(roster, HashSet::new);

Java编译器推断您想要创建一个包含Person类型元素的HashSet集合。或者,您可以这样指定:

Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);

上一页: Lambda表达式
下一页: 何时使用嵌套类、局部类、匿名类和Lambda表达式