Lambdas in Java, Plan B in action

Recently, Brian Goetz published modified draft of the Lambda proposal. This draft is mature in terms of the direction where Lambda is heading for in the Java world and introduces some breaking changes from earlier draft to align with this direction. While there are few things that need to be sorted out, this draft is simple to understand, unlike the earlier proposals we have seen. Last week, Maurizio pushed an updated prototype that aligns with this new draft.

Lambdas (originally knows as Closures) are available in the form of anonymous inner classes in Java. Anonymous inner classes are a form of nested classes that has no name and exhibits the following behavior as detailed in JLS 15.9.5.

  • An anonymous class is never abstract
  • An anonymous class is always an inner class; it is never static
  • An anonymous class is always implicitly final

One common use of anonymous inner classes is to create function objects in Java on the fly. [Refer Item 21 in Effective Java 2nd Edition pg 103]

Let us look at sorting an array of strings based on its length using an anonymous Comparator instance.

String[] teams = {"Violet", "Indigo", "Blue", "Green", "Yellow", "Orange", "Red"};
Arrays.sort(teams, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});

To create process objects, such as Runnable, Thread, or TimerTask instances.

final ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(new Runnable() {
@Override
public void run() {
System.out.println("Processing a short-lived asynchronous task.");
}
});

Anonymous classes have many limitations as outlined in Item 22 in Effective Java 2nd Edition [pg 108]. With Lambda expressions, you should be able to rewrite these samples more concisely eliminating the bulky syntax that comes with anonymous inner classes :

final ExecutorService executor = Executors.newCachedThreadPool();
// assignment context
Runnable task = #{ System.out.println("Processing a short-lived asynchronous task.") }; //take1
executor.submit(task);
// cast target context
task = (Runnable)#{System.out.println("Processing a short-lived asynchronous task.")}; //take 2
executor.submit(task);
// method invocation context
executor.submit((Runnable)#{System.out.println("Processing a short-lived asynchronous task.")}); //take 3

Array sorting using a comparator with Lambda expressions is a one liner:

String[] teams = {"Violet", "Indigo", "Blue", "Green", "Yellow", "Orange", "Red"};
final Comparator<String> c = #{s1, s2 -> s1.length() - s2.length()};
Arrays.sort(teams, c); //take1
Arrays.sort(teams, #{s1, s2 -> s1.length() - s2.length()}); //take 2

So, what next? Let us do some hands-on with these Lambdas and explore what Lambdas has to offer for Java developers. I setup the new prototype on my Windows environment following Remi’s instructions. It was fairly straight forward (all you need is Mercurial Client, Apache Ant, and latest JDK 7 build).

Here is a simple build script (build.bat) I used in Windows, that you can place in the “make” sub-directory under “langtools” :

ant -Dboot.java.home=C:\jdk1.7.0 clean build

This would create classes.jar under “distlib” sub-directory. You need to place this jar in your classpath when using the prototype.

You can use the following batch scripts for compiling and running your Lambda examples. I tested with the recent JDK 7 build (b115).

javac.bat

@echo off
C:\jdk1.7.0\bin\java.exe -cp classes.jar com.sun.tools.javac.Main %1

java.bat

@echo off
C:\jdk1.7.0\bin\java.exe -cp classes.jar;. %1

It is required to include classes.jar in the classpath due to runtime dependencies that are not available in the binary build. To make sure if you are running the prototype compiler, the output should show “internal” tag:

C:\JDK7\lambdasamples>javac.bat -version
javac 1.7.0-internal

Let us look at a simple example:

public class SimpleLambda {
abstract class HelloWorld {
abstract void greet();
}
{
HelloWorld greeting = #{ System.out.println("Hello World!") };//nilary Lambda expression
greeting.greet();
}
public static void main(String[] args) {
new SimpleLambda();
}
}

To compile this example:

javac.bat SimpleLambda.java

To run this example:

java.bat SimpleLambda

Voila, you ran your first lambda example successfully if you see “Hello World!” output.

Let us look at another example which uses parameterized types in this case.

interface Squarer {
Y square(X x);
}
{
Squarer s = #{ Integer x -> x * x };// 1-ary Lambda expression
System.out.println(s.square(5));
}

In both these examples, you notice a pattern commonly referred to as single abstract method (SAM), defined using an interface or an abstract class with exactly one abstract method. This is a structural property identified by the compiler and it does not take into account methods inherited from java.lang.Object (as in java.util.Comparator).

For example, Runnable, Callable, etc belongs to this category of SAM types that can be converted in the context of assignment as shown below.

final Runnable task = #{ System.out.println(“Processing a short-lived asynchronous task.”) };

In this code, “task” is an Runnable instance, while the lambda expression #{…} is not. Only after conversion, the target type becomes an instance of that SAM type.

Syntax – The Holy Grail of Lambda


Lambda expressions begin with ‘#’ special character and an arrow token ‘->’ is used to separate the formal parameters from the body of the Lambda. In case of single line Lambda expression, return keyword and trailing semicolon can be omitted. For nilary Lambda expression, the arrow token can be omitted.

Lambdas in Java exhibit the following behavior :

  • An interface, or abstract class, with exactly one abstract method are referred to as a SAM types.
  • Lambdas are not instances of classes.
  • Lambdas are converted to SAM types.
  • SAM types are instances of classes.
  • Lambdas are lexically scoped.

Lambda definition (excerpt from draft)

A lambda expression e is convertible-to a SAM type S if an anonymous inner class that is a subtype of S and that declares a method with the same name as S’s abstract method and a signature and return type corresponding to the lambda expressions signature and return type would be considered assignment-compatible with S.

The return type and exception types of a lambda expression are inferred by the compiler; the parameter types may be explicitly specified or they may be inferred from the assignment context.

The type of a formal parameter can be omitted, in which case the compiler will infer a type from the target type of the lambda conversion. Let us see couple examples where the types are inferred by the compiler.

enum Decision {
APPROVE, DECLINE, APPROVE_REFER
}

abstract class DecisionService {
abstract Decision getRiskDecision(Applicant applicant);
}

class Applicant {
private int score;
public Applicant(int score) {
this.score = score;
}
public int getScore() {
return score;
}
}

public void test() {
DecisionService cs = #{ a ->
if (a.getScore() > 700) {
return Decision.APPROVE;
} else if (a.getScore() > 600 && a.getScore()  log("Processed # " + pmt.getAmount() + " at " + new Date())});
}
void invokeProcessor(PaymentProcessor processor) {
processor.process(new Payment(42));
}

In line #14, the type of the formal parameter Payment is inferred by the compiler.

Extension Methods

One of the significant benefits of Lambdas is that it enables the development of more powerful libraries with less code and more efficiency. To enhance the core libraries to take advantage of these powerful language features without compromising backward compatibility, the concept of public defender methods was introduced. This proposal suggests adding virtual extension methods to existing interfaces allowing the use of new language features to enhance the capabilities of core libraries.

While, extension methods by itself is more useful and when combined with Lambdas, they can be powerful. The traditional way of sorting a collection.

Collections.sort(people, new Comparator() {
public int compare(Person x, Person y) {
return x.getLastName().compareTo(y.getLastName());
}
});

With Lambdas, this can be expressed more concisely (this works with the prototype).

Collections.sort(people, #{ x, y -> x.getLastName().compareTo(y.getLastName()) });

And this still leaves room for improvement where virtual extension methods come to rescue. When core libraries are enhanced to take advantage of Lambda expressions, this can be made less verbose.

people.sortBy(#Person.getLastName);

Virtual extension methods are declared using the ‘extension’ keyword.

public interface List extends Collection {
// .... The existing List methods
extension void sort() default Collections.sort;
}

When Collections library is enhanced to support extensions methods, we should be able to use many of the Collections APIs directly in a collection:

List.sort();
//instead of
Collections.sort(List);

Some of these minor improvements are most sought after and makes Java less verbose and one less class to deal with.

Conclusion

I haven’t touched upon other fundamental concepts such as Exception transparency, Method references and Lexical Scoping. I will be covering them once I do some hands-on with these concepts. The prototype is still evolving around these areas and has made some good progress in catching up with this new draft. As we know, Lambdas are scheduled to go in Java 8 (in late 2012), almost six years after Closures were originally proposed in 2006. I believe there are plans to make binary builds available for all platforms, so the hassle of building from the source would no longer be required.

[Photo credit: http://www.lumaxart.com/]

Update (10/25/2010) : Omitted the use of optional trailing semicolon and return keyword in single line Lambda expressions.

Update (6/11/2011) : The definition of SAM types have changed since this post was originally written. Here is the latest definition.

SAM types:
1. An interface that has a single abstract method
2. Having more than one distinct methods, but only one is “real”, the others are overridden public methods in Object – example: [scala]Comparator<T>[/scala]
3. Having more than one methods due to inheritance, but they have the same signature
4. Having more than one methods due to inheritance, but one of them has a sub-signature of all other methods

    1. a) parameter types compatible

 

    1. b) return type substitutable

 

    1. c) thrown type not conflicting with the thrown clause of any other method

 

    d) mixed up

5. Type-dependent SAM types

non-SAM types:
6. An interface that has a single abstract method, which is also public method in Object
7. Having more than one methods due to inheritance, and none of them has a sub-signature of all other methods

With these new rules, some of the examples won’t compile. Abstract classes with single abstract method are no longer considered valid SAM types. Switching to an interface should fix these examples.

13 thoughts on “Lambdas in Java, Plan B in action

  1. According to the draft, the return statement and trailing semicolon are optional for single lambda expressions. In your comparator example, is there any particular reason why you explicitly include them? I can’t think of any good reasons and would even suggest that the practice should be discouraged.
    [scala]
    final Comparator c = #{ s1, s2 -> return s1.length() – s2.length(); };
    [/scala]
    is equivalent to:
    [scala]
    final Comparator c = #{ s1, s2 -> s1.length() – s2.length() };
    [/scala]
    and
    [scala]
    Arrays.sort(teams, #{ s1, s2 -> return s1.length() – s2.length(); });
    [/scala]
    is equivalent to:
    [scala]
    Arrays.sort(teams, #{ s1, s2 -> s1.length() – s2.length() });
    [/scala]

    Like

  2. > final Runnable task = #{ System.out.println(“Processing a short-lived asynchronous
    task.”) };

    > In this code, “task” is an Runnable instance, while the lambda expression #{…} is
    > not. Only after conversion, the target type becomes an instance of that SAM type.

    I hope this isn’t the final spec. We get the same mess as in C++ where very often you have to assign an expression first to determine it’s type before you can use it.

    Like

    1. Shogg,

      Target typing section of the spec talks about this in detail. Assignment is one of the options. I have updated the example with some notes. I believe method invocation context is what you may be after. Once implemented in the prototype, you should be able to use the below code:

      [scala]
      executor.submit(#{System.out.println("Processing a short-lived asynchronous task.")});
      [/scala]

      Let me know if I am missing something.

      -Arul

      Like

  3. Hi,
    I have two questions.
    1. If the interface or abstract has two or more methods, how to process in Lambdas ?
    2. Look at the statement ” extension void sort() default Collections.sort;”
    What is different from @Override annotation?

    Like

  4. Hantsy,

    1. Lambdas can be applied only to SAM types which has exactly one abstract method.
    2. Public extension methods allows users to define new virtual methods (Ex: sort()) to existing interfaces (Ex: List). Implementations of List are free to provide an implementation of sort(). The default implementation (Ex: Collections.sort()) will be used if they don’t provide one. This is also useful when retrofitting existing libraries to take advantage of Lambdas without compromising backward compatibility.

    I hope this clarifies.

    -Arul

    Like

  5. Proposal about Lambdas in Java:

    Why don’t we all just stop messing with Java?

    > Collections.sort(people, #{ x, y -> x.getLastName().compareTo(y.getLastName()) });

    Are people insane?

    > people.sortBy(_.lastName)
    would have been to easy, right?

    Just use Scala. Even with all these defender methods added, things like map, flatMap, groupBy, sortWith, sortBy won’t work correctly in Java without higher-kinded Generics.

    Like

  6. steve,

    Scala is a different beast. It is concise and powerful and we like it a lot. But it makes a lot of sense for Java to at least have SAM level closures and lambas. We’ve all had enough of writing anonymous inner classes already! The latest lambda proposal is good for Java.

    Like

  7. Hi Steve,

    One of the primary motivation for Lambda expressions in Java is to write programs efficiently using ParallelArray APIs that perform bulk-data operations of filter, map, and reduce (Lambda Straw-Man Proposal). Core libraries can prove to be more useful when enhanced to support these methods without making significant changes to both the language and the VM.

    I do believe these changes make Java less verbose and helps implement much efficient Libraries.

    -Arul

    Like

  8. Hi Arul.

    How will Java be able to always return the exact type of the original collection class?

    Scala already has parallel collections which actually work quite nicely and can do that.

    Of course Java devs can provide an implementation with the correct return type for every collection class in the JDK. (That is called “bloat”.)

    There are tons of people out there who sub-classed the existing collections. What will they do about:
    > BobsSpecialList list = new BobsSpecialList();
    > list.put …
    > ??? newList = list.map(…)
    What will be the type of ???. And how will they implement it?

    Like

  9. (This can be merged with the comment above.)

    Actually this is a lot like Jigsaw vs. OSGi.
    NIH-drama all over the place.

    So basically Oracle plans to provide Lambdas and extensions to the collection class
    – in a release years away (2013/2014 I guess, judging from the work Oracle will have to do to provide a JDK for Mac OS X first),
    – involving wonky VM-level hacks,
    – “exception Generics” (actually, I couldn’t stop laughing when I first saw things like ),
    – adding a huge mental overhead to the usage of these things,
    – with maybe 30% of the usability and the features of fully working implementations existing today.

    And now people are actually surprised that there are persons who think this is not a good idea?

    Like

Leave a comment