Java中的方法参考

时间:2020-01-09 10:35:14  来源:igfitidea点击:

在Java的后Lambda表达式中,我们已经看到Lambda表达式如何提供功能接口的实例并实现功能接口的抽象方法。尽管有时,lambda表达式仅用于调用现有方法。在这种情况下,我们可以使用Java中的方法引用按名称引用现有方法。方法引用是一种紧凑且可读性强的lambda表达式,用于具有名称的方法。

例如,考虑以下lambda表达式

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.forEach((Integer a) -> System.out.println(a));

在这里,lambda表达式只是调用一个现有方法,可以使用方法引用来完成该方法,从而使代码更具可读性和简洁性。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.forEach(System.out::println);

方法参考也需要目标类型

Java方法引用可以称为lambda表达式的一种特殊形式,因为方法引用还需要目标类型上下文(兼容的功能接口),并且它也像lambda表达式一样创建功能接口的实例。这也意味着方法引用只能用于单个方法。

两者之间的区别在于,lambda表达式还可以为抽象方法提供实现,其中方法引用指的是现有方法。

Java方法参考语法

正如我们在示例中已经看到的那样,在Java中添加了一个新的双冒号运算符(::)以与方法引用一起使用。

包含方法的类或者对象位于双冒号运算符的左侧,而方法的名称位于运算符的右侧。

方法参考种类

Java中有四种方法参考

种类示例
参考静态方法ContainingClass :: staticMethodName
引用特定对象的实例方法containsObject :: instanceMethodName`
参考特定类型的任意对象的实例方法ContainingType :: methodName
参考构造函数ClassName :: new

静态方法参考

下面的示例演示如何在Java中使用静态方法引用。

public class MethodRef {
  public static <T> List<T> filter(List<T> myList, Predicate<T> predicate) {
    List<T> filterdList = new ArrayList<T>();
    for(T element: myList) {
      if(predicate.test(element)) {
        filterdList.add(element);
      }
    }
    return filterdList;
  }
  public static boolean isMoreThanValue(int i) {
    return i > 10;
  }
  public static void main(String[] args) {
    List<Integer> myList = Arrays.asList(25, 5, 17, 1, 7, 14, 9, 11);
    System.out.println("Method call as lambda expression");
    List<Integer> filterdList = filter(myList, (i) -> MethodRef.isMoreThanValue(i));
    System.out.println("Filtered elements- " + filterdList);
    System.out.println("Method call using method reference");
    filterdList = filter(myList, MethodRef::isMoreThanValue);
    System.out.println("Filtered elements- " + filterdList);
  }
}

输出:

Method call as lambda expression
Filtered elements- [25, 17, 14, 11]
Method call using method reference
Filtered elements- [25, 17, 14, 11]

在示例中,使用方法参考MethodRef :: isMoreThanValue调用了isMoreThanValue静态方法。

在filter方法中,参数之一是Predicate类型。谓词是一个具有抽象方法test()的功能接口,该方法对给定参数评估此谓词并返回布尔值(true或者false)。

静态方法isMoreThanValue()是谓词功能接口的抽象方法test()的实现。当我们进行方法调用filter(myList,MethodRef :: isMoreThanValue)时,Java可以从上下文中推断isMoreThanValue()是Predicate接口的实现。

对实例方法的方法引用

在这种情况下,我们可以使用类的对象来引用方法,而不是使用类名。

public class MethodRef {
  public <T> List<T> filter(List<T> myList, Predicate<T> predicate) {
    List<T> filterdList = new ArrayList<T>();
    for(T element: myList) {
      if(predicate.test(element)) {
        filterdList.add(element);
      }
    }
    return filterdList;
  }
  public boolean isMoreThanValue(int i) {
    return i > 10;
  }
  public static void main(String[] args) {
    List<Integer> myList = Arrays.asList(25, 5, 17, 1, 7, 14, 9, 11);
    MethodRef obj = new MethodRef();
    System.out.println("Method call as lambda expression");
    List<Integer> filterdList = obj.filter(myList, (i) -> obj.isMoreThanValue(i));
    System.out.println("Filtered elements- " + filterdList);
    System.out.println("Method call using method reference");
    filterdList = obj.filter(myList, obj::isMoreThanValue);
    System.out.println("Filtered elements- " + filterdList);
  }
}

输出:

Method call as lambda expression
Filtered elements- [25, 17, 14, 11]
Method call using method reference
Filtered elements- [25, 17, 14, 11]

这是与静态方法引用相同的示例,只是更改,现在将类的实例用于方法引用。现在也不要求方法是静态的。

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

在前面的示例中,使用了类的特定对象,但是我们可能会遇到一种情况,在这种情况下,我们需要指定可以与给定类的任何对象一起使用的实例方法。在这种情况下,方法引用将具有以下形式:

ClassName::instanceMethodName

在这种情况下,功能接口的第一个参数与用于调用该方法的对象匹配,并且其他任何参数都将传递给该方法。

在示例中,有一个类Person,其字段firstName,lastName,age,并且我们需要获取年龄大于50的Person的计数。在这种情况下,必须为所有Person对象调用isAgeGreater()方法。

class Person {
  private String firstName;
  private String lastName;
  private int age;
  public Person(String firstName, String lastName, int age){
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  public String getFirstName() {
    return firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public int getAge() {
    return age;
  }
  // This becomes the abstract method (test) implementation
  // of the functional interface
  boolean isAgeGreater(int age) {
    return this.getAge() > age;
  }
}
@FunctionalInterface
interface TestInterface {
  boolean test(Person person, int age);
}
public class MethodRef {
  public static void main(String[] args) {
    List<Person> tempList = createList();
    int count = ageCounter(tempList, Person::isAgeGreater, 50);
    System.out.println("Person count age greater than 50- " + count);
  }

  static int ageCounter(List<Person> list, TestInterface ref, int age) {
    int count = 0;
    for(Person person : list) {
      // first param becomes the invoking object
      // other parameters are passed to method
      if(ref.test(person, age))
        count++;
    }
    return count;
  }

  private static List<Person> createList(){
    List<Person> tempList = new ArrayList<Person>();
    tempList.add(new Person("Joe","Root", 28));
    tempList.add(new Person("Mathew","Hayden", 42));
    tempList.add(new Person("Richard","Hadlee", 55));
    tempList.add(new Person("Sunil","Gavaskar", 65));
    tempList.add(new Person("Brian","Lara", 45));     
    return tempList;
  }
}

构造函数参考

我们还可以引用与方法引用类似的构造函数,只是在这种情况下,方法的名称是新的。

构造函数参考的语法如下:

classname::new

构造函数参考Java示例

在copyElements方法中,参数之一是Supplier类型,它是java.util.function包中定义的功能接口。功能接口Supplier包含一个方法get,该方法不带任何参数并返回一个对象。新的ArrayList实例作为构造函数引用传递给Supplier。

public class MethodRef {
  public static void main(String[] args) {
    List<Integer> myList = Arrays.asList(25, 5, 17, 1, 7, 14, 9, 11);
    List<Integer> tempList = copyElements(myList, ArrayList::new);
    System.out.println("Copied list- " + tempList);
  }

  public static List<Integer> copyElements(List<Integer> sourceList, Supplier<List<Integer>> destList) {      
    List<Integer> list = destList.get();
    for (Integer i : sourceList) {
      list.add(i);
    }
    return list;
  }
}