Edwin is a software developer, programming languages enthusiast and a novice photographer. Edwin has posted 13 posts at DZone. You can read more from them at their website. View Full User Profile

Java Lambda Expressions vs Method References

04.09.2013
| 6186 views |
  • submit to reddit

Now we can use lambda expressions to implement functional interfaces as we have seen in previous posts, but the lambda expressions are not the only mechanism we can use for this purpose. Particularly where there are preexisting code that we would like to reuse we can simplify the implementation of the functional interface by using method references.

Static Method References

First, consider the existence of a functional interface Predicate as follows:

public interface Predicate<T> {
public void test(T t);
}

And let’s say that we had a method to filter elements out of a list using this predicate, as follows:

static <T> List<T> filter(Predicate<T> predicate, List<T> source) {
List<T> destiny = new ArrayList<>();
for (T item : source) {
if(predicate.test(item)){
destiny.add(item);
}
}
return destiny;
}

Finally, let’s say we had a class containing a set of static method predicates which we had defined in the past, prior to the existence of the Java 8. Something as follows:

static class IntPredicates {
public static boolean isOdd(Integer n) { return n % 2 != 0; }
public static boolean isEven(Integer n) { return n % 2 == 0; }
public static boolean isPositive(Integer n) { return n >= 0; }
}

Now, one way to implement a predicate that could reuse our static methods would be through the use of lambda expressions, like this:

Predicate<Integer> isOdd = n -> IntPredicates.isOdd(n);
Predicate<Integer> isEven = n -> IntPredicates.isEven(n);

However, we can clearly see that the signature of the static predicate methods corresponds perfectly with the signature of the test method for integer predicates. So, an alternative way to implement the functional interface in this case is through a static method reference, as follows:

Predicate<Integer> isOdd = IntPredicates::isOdd;
Predicate<Integer> isEven = IntPredicate::isEven;

Notice the use of double colon :: here. We are not invoking the method, we are just referencing its name.

We could now use this technique to filter a list of numbers that satisfy any of these predicates, something like this:

List<Integer> numbers = asList(1,2,3,4,5,6,7,8,9);
List<Integer> odds = filter(IntPredicates::isOdd, numbers);
List<Integer> evens = filter(IntPredicates::isEven, numbers);

So, as we can see, we could implement the functional interfaces in this case using both: lambda expressions and method references, but the syntax with the static method references was more succinct.

Constructor Method References

Let’s consider now the existence of a functional interface named Function, as follows:

public interface Function<T,R> {
public R apply(T t);
}


Based on it, we could define a method map, that converts the elements from a source list from certain value T to certain value R, as follows:

static <T,R> List<R> map(Function<T,R> function, List<T> source) {
List<R> destiny = new ArrayList<>();
for (T item : source) {
R value = function.apply(item);
destiny.add(value);
}
return destiny;
}

Now imagine that we had a list of strings containing numbers that we would like to transform to integer values. We could do it using a lambda expression to provide an implementation for the Function interface, more or less like this:

List<String> digits = asList("1","2","3","4","5");
List<Integer> numbers = map(s -> new Integer(s), digits);

However, we can clearly infer that the constructor Integer(String) has the same signature as the  apply method in the Function reference required here, namely, it receives a string as argument and returns an integer.

So, in this case we simplify the implementation of the functional interface by means of using a constructor reference, as follows:

List<String> digits = asList("1","2","3","4","5");
List<Integer> numbers = map(Integer::new, digits);

This conveys the same meaning: take a string and make me an integer out of it. It is the perfect task for our Integer(String) constructor.

Instance Method Reference to Arbitrary Objects

Consider now the existence of a class named  Jedi, defined as follows:

public class Jedi  {
private String name;
private int power;
public Jedi(String name, int power){
this.name = name;
this.power = power;
}
public String getName() {
return name;
}
public int getPower() {
return power;
}
//equals,hashCode,toString
}

Now, consider that we had a list of jedis, and we would like to use our previous function map to extract the name from every jedi and generate a list of names out of the list of jedis. Somewhat like this, using lambda expressions:

List<Jedi> jedis = asList(new Jedi("Obi-wan", 80),
new Jedi("Anakin", 25),
new Jedi("Yoda", 500));
List<String> names = map(jedi -> jedi.getName() , jedis);

The interesting observation here is that the parameter jedi is the argument for the apply method in the Function reference. And we use that reference to a jedi to invoke on it the method getName. In other words, we invoke a method on the reference we receive as argument.

So, we could simplify this implementation by using an instance method reference as follows:

List<Jedi> jedi = asList(new Jedi("Obi-wan", 80),
new Jedi("Anakin", 25),
new Jedi("Yoda", 500));
List<String> names = map(Jedi::getName, jedi);


Again, the interesting aspect of this type of method reference is that the method getName is an instance method. Therefore, the target of its invocation must be an instance, which in this case is an arbitrary object being provided as the argument for the method apply in the Function interface definition.

Instance Method Reference to a Specific Object

Let’s consider the existence of functional interface named Consumer, as follows

public interface Consumer<T> {
public void accept(T t);
}

And let’s define a method capable of using a consumer to consume all the elements of a given list, like this:

static  void forEach(Consumer<T> consumer, List<T> source){
for (T item : source) {
consumer.accept(item);
}
}

Imagine that now we would like to print all the elements contained in a list, and for that purpose we could define a consumer using a lambda expressions:

List<Integer> numbers = asList(1,2,3,4,5,6,7,8,9);
forEach(n -> { System.out.println(n); }, numbers);

However, we could also make the observation that the method println has the same signature that our Consumer has, it receives an integer and does something with it, in this case  it prints it to the main output.

However, we cannot specify that this is an arbitrary instance method reference by saying PrintStream::println, because in this case the Consumer interface method accept does not receive as one of its arguments the PrintStream object on which we may want to invoke the method println. Conversely, we already know which is the target object on which we would like to invoke the method: we can see that every time we would like to invoke it on a specific reference, in this case the object System.out.

So, we could implement our functional interface using an instance method reference to a specific object as follows:

List<Integer> numbers = asList(1,2,3,4,5,6,7,8,9);
forEach(System.out::println, numbers);

In summary, there are circumstances in which we would like to use some preexisting code as the implementation for a functional interface, in those case we could use one of several variants of method references instead of a more verbose lambda expression.

 

Published at DZone with permission of its author, Edwin Dalorzo. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)