Running on Java 24-ea+24-2960 (Preview)
Home of The JavaSpecialists' Newsletter

233Intersection Types to Give Lambdas Multiple Personalities

Author: Dr. Heinz M. KabutzDate: 2015-10-18Java Version: 8Category: Performance
 

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.

Intersection Types to Give Lambdas Multiple Personalities

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".

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.

Cast Expressions

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

 

Comments

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)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...