文档

Java™ 教程-Java Tutorials 中文版
方法引用
Trail: Learning the Java Language
Lesson: Classes and Objects
Section: Nested Classes
Subsection: Lambda Expressions

方法引用

你使用 lambda expressions 来创建匿名方法。但是,有时,lambda 表达式只会调用现有方法。在这些情况下,通过名称引用现有方法通常更清楚。方法引用使你可以这样做;对于已经有名称的方法,它们是紧凑的,易于阅读的 lambda 表达式。

再次考虑 Lambda Expressions 部分中讨论的 Person 类:

public class Person {

    public enum Sex {
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    public int getAge() {
        // ...
    }
    
    public Calendar 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
引用特定对象的实例方法 containingObject::instanceMethodName
引用特定类型的任意对象的实例方法 ContainingType::methodName
引用构造函数 ClassName::new

引用静态方法

方法引用 Person::compareByAge 是对静态方法的引用。

引用特定对象的实例方法

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

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 调用方法 compareByName,它是对象 myComparisonProvider 的一部分。JRE 推断出方法类型实参,在本例中是 (Person, Person)

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

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

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)

引用构造函数

你可以使用名称 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);

Previous page: Lambda Expressions
Next page: When to Use Nested Classes, Local Classes, Anonymous Classes, and Lambda Expressions