Java 方法引用(翻译自官方文档 JDK 8 tutorial)

928 阅读4分钟

方法引用(Method Reference)

你可能会使用 Lamada 表达式来创建一个匿名方法,但有些时候,一个 Lamada 表达式除了调用一个现有的方法之外本身什么事情也不做。在这种情况下,通过方法名称来引用现有方法的方式通常会使想要表达的意思更清晰。方法引用使您可以执行此操作。方法引用是一个更加紧凑,易读的Lambda表达式,注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号"::"。

考虑有如下的Person类:

public class Person {
   
    public enum Sex {
        MALE, FEMALE
    }
   
    String name; //姓名
    LocalDate birthday;//生日LocalDate实现了Comparable接口
    Sex gender;//性别
    String emailAddress;//邮件地址
   
    Person(String nameArg, LocalDate birthdayArg,
        Sex genderArg, String emailArg) {
        name = nameArg;
        birthday = birthdayArg;
        gender = genderArg;
        emailAddress = emailArg;
    }  
 
    public int getAge() {
        return birthday
            .until(IsoChronology.INSTANCE.dateNow())
            .getYears();
    }
 
    public void printPerson() {
      System.out.println(name + ", " + this.getAge());
    }
     
    public Sex getGender() {
        return gender;
    }
     
    public String getName() {
        return name;
    }
     
    public String getEmailAddress() {
        return emailAddress;
    }
     
    public LocalDate getBirthday() {
        return birthday;
    }
    //静态方法 
    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }
 
    public static List<Person> createRoster() {//roster是一个集合
         
        List<Person> roster = new ArrayList<>();
        roster.add(
            new Person(
            "Fred",
            IsoChronology.INSTANCE.date(1980, 6, 20),
            Person.Sex.MALE,
            "fred@example.com"));
        roster.add(
            new Person(
            "Jane",
            IsoChronology.INSTANCE.date(1990, 7, 15),
            Person.Sex.FEMALE, "jane@example.com"));
        roster.add(
            new Person(
            "George",
            IsoChronology.INSTANCE.date(1991, 8, 13),
            Person.Sex.MALE, "george@example.com"));
        roster.add(
            new Person(
            "Bob",
            IsoChronology.INSTANCE.date(2000, 9, 12),
            Person.Sex.MALE, "bob@example.com"));
         
        return roster;
    }

	@Override
	public String toString() {
		return "Person [name=" + name + ", birthday=" + birthday + ", gender=" + gender + ", emailAddress="
				+ emailAddress + "]";
	}
     
}

假设你的通讯录成员包含在一个数组中,并且您希望按年龄对数组进行排序,传统的方法你可能会这样做(这里只是一个代码片段)

//roster是一个集合
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进行排序        
Arrays.sort(rosterAsArray, new PersonAgeComparator());

其中,sort()方法的签名如下:

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

注意到接口Comparator是一个函数式接口,因此你可以使用一个Lamada表达式来代替,而不需要实例化一个Comparator的子类。

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

尽管如此,Person.compareByAge方法已经实现了比较两个Person实例的生日大小的功能,因此你可以调用该方法来替换Lamada表达式的方法体,如下:

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

因为这个Lamada表达式调用了一个已经存在的方法(即Person.compareByAge方法),因此你可以通过方法引用来代替Lamada表达式本身,如下:

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

Person::compareByAge这个方法引用在语义上与Lamada表达式相同。每个方法引用都有以下特征:

  1. 它的形参是从Comparator<Person>.compare(Person,Person)中复制的;
  2. 它的方法体调用了Person.compareByAge方法;

方法引用的种类

以下是四种方法引用:

种类 例子
引用一个静态方法 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这个方法引用调用了myComparisonProvider实例的compareByName方法。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方法,所以你可以使用Lamada表达式调用transferElements方法,如下:

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

你可以使用一个构造器引用替换上面的Lamada表达式:

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

Java JRE会推断你想要去创建一个Person类型的HashSet集合,或者,您可以如下指定:

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