Method References are part of Lambda Expressions in The Java Tutorials but that’s a pretty big topic so I’m splitting it out here. It’s also a little hard to follow the examples if you are not familiar with Generics.

When a lambda expression does nothing but call an existing method, it’s often clearer to refer to the existing method by name. We can do this with a Method Reference.

  • 4 types of Method References:
Kind Example
Reference to a static method ContainingClass::staticMethodName

Person::compareByAge

Reference to an instance method of a particular object containingObject::instanceMethodName

myComparisonProvider::compareByName

Reference to an instance method of an arbitrary object of a particular type ContainingType::methodName

String::compareToIgnoreCase

Reference to a constructor ClassName::new

HashSet::new

The examples given refer to this use case:

  • Local Class Version
// Convert the roster List to an Array of Person objects using 
<T> T[] toArray(T[] a) defined in the List interface
Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);

// Define a Local Class which implements the Comparator<T> interface
// and the abstract method int compare(T o1, T o2)
// in order to pass a Comparator<Person> to the sort method
class PersonAgeComparator implements Comparator<Person> {
    public int compare(Person a, Person b) {
        // LocalDate implement compareTo method which returns -ve int if the 
        // the param date is less, +ve if its is greater
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
     
// Use this sort method public static void sort(T[] a, Comparator<? super T> c)   
Arrays.sort(rosterAsArray, new PersonAgeComparator());
  • Lambda Expression Version

The Comparator<T> Interface is a functional interface so we can use a lambda expression to represent the compare method, rather than defining and creating a new instance of the PersonAgeComparator which implements it.

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

If this method is put inside the Person class,

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

we could invoke it directly,

Arrays.sort(rosterAsArray,
    (a, b) -> Person.compareByAge(a, b)
);
  • Method Reference Version

Because the lambda expression invokes an existing method we can use a method reference instead:

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

Semantically the method reference and the lambda expression are the same:

  • Its formal parameter list is copied from Comparator<Person>.compare, which is (Person, Person).
  • Its body calls the method Person.compareByAge

Reference to a Constructor Example

The purpose of this method is to  copy elements from one Collection<E> to another. Why so complicated looking? The thing to remember is that Collection<E> is just an interface that types of collections must implement. This method takes many types and it wants to create a new destination Collection of the right type and return a reference to it.

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;
}
  • The 2nd method param, collectionFactory, is of type Supplier<T> which is a functional interface with one abstract method T get (), that takes no arguments and returns an object.
  • <T> is a Generic Type DEST which is a type of Collection
  • So the parameter of Type Supplier<DEST> can be represented with a lambda expression that has no argument and returns a Collection:
    () -> { return new HashSet<>(); }
  • Or by using a Constructor Reference:
    HashSet::new
    • The Type of the elements in the HashSet can be inferred from an assignment:
      Set<Person> rosterSet = transferElements(roster, HashSet::new);
    • Or set explicitly via the HashSet(Collection<? extends E> c) constructor:
      Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);

This example feels like it’s over engineered to demonstrate using a constructor reference and that might be the only reason. Any comments? It certainly can be simplified without losing functionality:

  • Create the required collection in the method call instead of inside the method:
public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>> 
  DEST transferElements2(
    SOURCE sourceCollection,
    DEST destFactory ) {

      for (T t : sourceCollection) {
        destFactory.add(t);
      }
      return destFactory;
}

Set<Person> rosterSet2 = transferElements2(roster, new HashSet());
  • Change the method signature to Collection and cast the Collection to Set in the assignment:
public static Collection transferElements3(
  Collection sourceCollection,
  Collection destCollection ) {
                     
  for (Object t : sourceCollection) {
    destCollection.add(t);
  }
  return destCollection;
}

Set rosterSet3 = (Set) transferElements3(roster, new HashSet());

 

The exercise notes for this tutorial are here

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s