Abstract: Lambdas are often described as anonymous inner classes without the boilerplate code. However, lambdas are more powerful. Firstly, a lambda does not always result in a new object. Secondly, "this" refers to the outer object. Thirdly, a lambda object can implement multiple interfaces. In this newsletter we will look at this third feature, where cast expressions use intersection types to produce lambdas with several interfaces.
Welcome to the 233rd edition of The Java(tm) Specialists' Newsletter, sent to you from where it all began. 15 years ago, I was sitting at the Frankfurt Airport, bored out of my skull. In those days we did not have infinite fast wifi to distract us. We could either chat to fellow passengers, read a book or stare into space. A friend had suggested that I should write a book about advanced Java. With nothing better to do, I took a pen and paper and started jotting down topics for my book. Very soon I had about a dozen ideas to write about. However, instead of ever publishing that one book that would make me famous, I decided to publish my ideas as a series of free Java Specialists' Newsletters.
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
Two days ago I was teaching my Java Specialist Master Course at a bank in Frankfurt and someone asked what the strange cast was all about in the Comparator.comparing() method. We see that it looks as if the lambda is being cast to a Comparable and to a Serializable interface. I had seen these types of "intersection types" before with generics, although I had also forgotten what their purpose was there. Here is what the cast looked like:
return (Comparator<T> & Serializable) (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2));
A little bit of digging revealed that lambdas have a very nice feature that allow us to make an instance that implements several interfaces using something called a cast expression. We can combine as many interfaces as we want to, as long as the intersection eventually results in only a single abstract method. For example, we can write:
import java.io.*; public class MultiplePersonalities { public Runnable create() { return (Runnable & Serializable) () -> System.out.println("Hello World"); } }
The job object above is both Runnable and Serializable. Here is the proof:
import java.util.stream.*; public class Psychiatrist { public static void main(String... args) { Runnable job = new MultiplePersonalities().create(); Stream.of(job.getClass().getInterfaces()) .map(Class::getName) .forEach(System.out::println); } }
When we run this, we see the following output:
java.lang.Runnable java.io.Serializable
Since the beginning of 2015, we have had an explosion of orders for our Extreme Java - Concurrency and Performance Course. A few classes were so large that I took along Maurice Naftalin as a co-presenter. You might remember Naftalin from his excellent Java books on Generics and Lambdas. As a result, I've learned a thing or two about how lambdas and streams should be used, plus have had the chance through lots of experimentation to come up with my own conclusions. After writing dozens of lambdas, I've come up with Heinz's Lambda Reduction Principle: "Favour Method References Over Lambdas".
The Psychiatrist above could have been written in many different ways. First off, a plain old for loop:
public class PsychiatristTraditional { public static void main(String... args) { Runnable job = new MultiplePersonalities().create(); for (Class<?> clazz : job.getClass().getInterfaces()) { System.out.println(clazz.getName()); } } }
I suspect we will be seeing this traditional looping code for a while still in Java. It seems easier to read for Java programmers, but only because we are used to it. A normal person will ask: "What is the clazz variable for?" There is hardly any reason to have to think about naming such trivial local variables. A quick transform to streams produces this:
import java.util.stream.*; public class PsychiatristLambda { public static void main(String... args) { Runnable job = new MultiplePersonalities().create(); Stream.of(job.getClass().getInterfaces()) .forEach(clazz -> System.out.println(clazz.getName())); } }
We are using streams, but also creating a lambda:
clazz -> System.out.println(clazz.getName())
.
If we follow Heinz's Lambda Reduction Principle, we should
instead try to use method references. The
lambda actually consists of two actions, not
one. #1 we call clazz.getName() and #2 we call
System.out.println(). Thus if we split this into two actions
in our stream, we end up with:
Stream.of(job.getClass().getInterfaces()) .map(clazz -> clazz.getName()) .forEach(name -> System.out.println(name));
Again, we had to use our creative juices for something as
uncreative as thinking up names for these two variables,
clazz
and name
. How boring. With
"Heinz's Lambda Reduction Principle" we "Favour Method
References over Lambdas" and thus transform it to the
more readable and logical
Stream.of(job.getClass().getInterfaces()) .map(Class::getName) .forEach(System.out::println);
Simplifying lambdas as much as possible, preferably by always reducing them to method references, makes the resultant code far easier to understand and also to debug.
If we go back to the topic of this newsletter, we were looking at something called "Intersection Types". With these we do a cast with several interfaces, also called "Cast Expressions". In the Java 8 Language Specification, we read in chapter 15.16
"Casts can be used to explicitly "tag" a lambda expression or a method reference expression with a particular target type. To provide an appropriate degree of flexibility, the target type may be a list of types denoting an intersection type, provided the intersection induces a functional interface." (emphasis mine)
What exactly does this phrase mean? A "functional interface"
has exactly one abstract method. Examples are:
java.util.Comparator, java.lang.Comparable,
java.lang.Runnable, java.awt.event.ActionListener, etc.
Some of these, such as Comparator and Runnable, are
explicitly marked with Java 8's new @FunctionalInterface
annotation,
whereas ActionListener and Comparable are not. However, the
fact that they only have one abstract method makes them
"functional interfaces", regardless of their annotation, which means that they can
be used
in a lambda expression.
Anonymous inner classes do not allow you to specify multiple interfaces. You can make an anonymous inner class that implements Runnable with a run() method, for example, but you cannot make one that is both Runnable and something else. Lambdas do not have this restriction.
I wrote a small program to scan through the rt.jar file to
look for interfaces that have no abstract methods and which
could be seen as "marker" interfaces. There were more than
I expected. So here we have our
SeriouslyDisturbed
class:
public class SeriouslyDisturbed extends MultiplePersonalities { public Runnable create() { return ( Runnable & java.awt.peer.FontPeer & java.io.Serializable & java.lang.Cloneable & java.lang.reflect.Type & java.rmi.Remote & java.util.RandomAccess & java.util.EventListener ) () -> System.out.printf("Oh my what am I?"); } }
Run this object through my Psychiatrist class above and you will see an object that implements all the interfaces:
java.lang.Runnable java.awt.peer.FontPeer java.lang.Cloneable java.lang.reflect.Type java.rmi.Remote java.util.RandomAccess java.util.EventListener java.io.Serializable
Even though I started this newsletter whilst sitting at the Frankfurt Aiport waiting for my flight to take off, I wrote a chunk of it at 30000 feet. As fate would have it, I ended up sitting next to a psychiatrist.
Kind regards from a sunny Crete. So glad to be home!
Heinz
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.