If you are interested in the work being done by the JSR 335 working group on defining Lambda Expressions for Java and you would like to get a feel for how they are going to work in practice you can either download a binary release of the Project Lambda version of the JDK which includes the reference implementation or, if you are feeling adventerous or possibly lucky, you can build it from source.
As you can tell from the title of this post I went with option two.
1.0 Prerequisites
1.1 Mac OS X Lion
JDK 7 is required to build the Project Lambda version of the JDK and the version of JDK 7 available for download will only install on Lion, and I assume Mountain Lion.
I started by trying to build on Snow Leopard using a locally built version of the Mac OS X OpenJDK port but ran into a few problems during the building of the javax.nio
buffer code. The problems do not look as though they are insurmountable, but for the moment I am not able to build the Project Lambda JDK on Snow Leopard.
I have not done so but I would assume that it also possible to build the Project Lambda JDK on Mountain Lion.
1.2 JDK 7
The Project Lambda JDK needs to be built using JDK 7. You can get JDK 7 for Mac OS X Lion here.
1.3 Project Lambda JDK Source Code
You can clone the Project Lambda Mercurial repository using the command.
hg clone http://hg.openjdk.java.net/lambda/lambda lambda
This should result in something like the following output
...
requesting all changes
adding changesets
adding manifests
adding file changes
added 502 changesets with 536 changes to 85 files
updating working directory
82 files updated, 0 files merged, 0 files removed, 0 files unresolved
Once you have done this you need to clone the nested Mercurial repositories which comprise the JDK itself.
You can do this by changing to the directory into which you cloned the Project Lambda Mercurial repository, if you used the command above that will be the lambda
directory, and then running the command.
sh ./get_source.sh
Once you have got all the source you can do the build.
2.0 Building
There are currently two build systems for the JDK. The existing one and a new one being developed for use when building JDK 8.
Following advice given on the lambda-dev mailing list I went with the new one.
You need to start by changing to the directory common/makefiles
cd common/makefiles
and then perform a configuration step
sh ../autoconf/configure --with-boot-jdk=/Library/Java/JavaVirtualMachines/jdk1.7.0_07.jdk/Contents/Home
The --with-boot-jdk
argument identifies the location of the JDK 7 binaries, libraries and JARs, etc.. JDK 7 is installed as a Mac OS X package and the given path points inside the package.
Running the command should produce a lot of output ending with something like the following which has been edited slightly for clarity
...
===================================================
A new configuration has been successfully created in \
elided/lambda/build/macosx-x64-normal-server-release
using configure arguments '--with-boot-jdk=\
/Library/Java/JavaVirtualMachines/jdk1.7.0_07.jdk/Contents/Home'.
Configuration summary:
* Debug level: release
* JDK variant: normal
* JVM variants: server
* OpenJDK target: OS: macosx, CPU architecture: x86, address length: 64
* Boot JDK: /Library/Java/JavaVirtualMachines/jdk1.7.0_07.jdk/Contents/Home
Build performance summary:
* Cores to use: 2
* Memory limit: 4096 MB
* ccache status: not installed (consider installing)
Build performance tip: ccache gives a tremendous speedup for C++ recompilations.
You do not have ccache installed. Try installing it.
Once the configuration has completed you simply run the command
make images
This should start by producing the output
Building OpenJDK for target 'images' in configuration 'macosx-x64-normal-server-release'
and complete some time later with something like the following
...
##### Leaving jdk-images for target(s) images-only #####
########################################################################
##### Build time 00:00:48 jdk-images for target(s) images-only #####
########################################################################
-- Build times ----------
Target
Start 2012-09-25 00:11:17
End 2012-09-25 00:22:38
00:00:28 corba
00:02:45 hotspot
00:00:26 jaxp
00:00:31 jaxws
00:05:43 jdk
00:00:48 jdk-images
00:00:40 langtools
00:11:21 TOTAL
-------------------------
If the build is successful then the resulting JDK can be found in the directory
build/macosx-x64-normal-server-release/images/j2sdk-image
Unlike JDK 7 it is not currently built as a Mac OS X package.
3.0 Testing The Build
To check whether I had a functioning build at least as far as Java Lambda Expressions were concerned I wrote three very simple pieces of code.
All of them use the forthcoming java.util.streams.Stream
interface because it defines lots of methods which can be passed Lambda Expressions.
All of them actually work as expected when compiled and run using the Project Lambda JDK that I built.
3.1 Echo
package xper.lambda;
import java.util.Arrays;
public final class Echo
{
public static void main(String[] theArgs)
{
new Echo().run(theArgs);
}
//
private Echo()
{
}
//
private void run(String[] theArgs)
{
first = true;
Arrays.
stream(
theArgs).
forEach(
s -> {
if (!first)
{
System.out.print(' ');
}
else
{
first = false;
}
System.out.print(s);
});
System.out.println();
}
//
private boolean first;
}
The code creates a Stream
from the program’s arguments using the Arrays.stream()
utility method and then uses the Stream’s forEach()
method to apply a Lambda Expression to each of the arguments.
The forEach()
method is declared like this
void forEach(Block<? super T> block);
and the Block
interface is defined in the java.util.functions package like this
public interface Block {
/**
* Performs operations upon the provided object which may modify that object
* and/or external state.
*
* @param t an input object
*/
void apply(T t);
/**
* Returns a Block which performs in sequence the {@code apply} methods of
* multiple Blocks. This Block's {@code apply} method is performed followed
* by the {@code apply} method of the specified Block operation.
*
* @param other an additional Block which will be chained after this Block
* @return a Block which performs in sequence the {@code apply} method of
* this Block and the {@code apply} method of the specified Block operation
*/
public Block<T> chain(Block<? super T> other) default {
return (T t) -> { apply(t); other.apply(t); };
}
}
At first glance it is not entirely obvious why the forEach()
method accepts a Lambda Expression at all, but the
Block
interface is what JSR 335 defines as a functional interface
A functional interface is an interface that has just one abstract method, and thus represents a single function contract. (In some cases, this “single” method may take the form of multiple abstract methods with override-equivalent signatures inherited from superinterfaces; in this case, the inherited methods logically represent a single method.)
In addition to the usual process of creating an interface instance by declaring and instantiating a class, instances of functional interfaces can be created with lambda expressions, method references, or constructor references.
Note that although the Block
interface declares two methods it is still a functional interface because only one of them is abstract. The other is a default method which defines an implementation of the method despite appearing in an interface declaration. This is another new feature defined by JSR 335.
A default method is a method that is declared in an interface and that has a block body prefixed by the keyword
default
. Its body provides a default implementation for any class that implements the interface without overriding the method. This allows new functionality to be added to existing (and perhaps already widely-distributed) interfaces. More generally, it provides a mechanism for multiple inheritance of behavior.
In this example the Lambda Expression passed to the forEach()
can be made to match the definition of the Block
interface by the compiler.
3.2 ToUpper
package xper.lambda;
import java.util.Arrays;
public final class ToUpper
{
public static void main(String[] theArgs)
{
new ToUpper().run(theArgs);
}
//
private ToUpper()
{
}
private void run(String[] theArgs)
{
first = true;
Arrays.
stream(
theArgs).
map(
s -> s.toUpperCase()).
forEach(
s -> {
if (!first)
{
System.out.print(' ');
}
else
{
first = false;
}
System.out.print(s);
});
System.out.println();
}
//
private boolean first;
}
This code is a modified version of the Echo code above. The map()
method is used to covert each of the program’s argument to upper case before they are printed out.
The map()
method is declared like this
<R> Stream<R> map(Mapper<? super T, ? extends R> mapper);
and the Mapper
interface is declared in the java.util.functions
package like this
public interface Mapper<T, R> {
/**
* Map the provided input object to an appropriate output object.
*
* @param t the input object to be mapped.
* @return the mapped output object.
*/
R map(T t);
/**
* Combine with another mapper producing a mapper which preforms both
* mappings.
*
* @param Type of output objects from the combined mapper. May be the
* same type as {@code <U>}.
* @param after An additional mapping to be applied to the result of this
* mapping.
* @return A mapper which performs both the original mapping followed by
* a second mapping.
*/
public <V> Mapper<T, V> compose(Mapper<? super R, ? extends V> after) default {
return Mappers.chain(this, after);
}
}
Like the Block
interface above it is a functional interface which is why the Lambda Expression
s -> s.toUpperCase()
can be passed to the map()
method.
3.3 Sum
package xper.lambda;
import java.util.Arrays;
public final class Sum
{
public static void main(String[] theArgs)
{
System.out.print("sum == ");
System.out.println(
Arrays.
stream(
theArgs).
map(
s -> Integer.parseInt(s)).
reduce(
0,
(i, j) -> i + j));
}
}
This code relies heavily on auto-boxing and unboxing and type inference by the compiler and is an example of how gnomic it is possible to be when using Lambda Expressions.
It uses the map()
method to produce a Stream
of Integer
s and the reduce()
method to sum them.
The reduce()
method is declared like this
T reduce(T base, BinaryOperator<T> op);
and the BinaryOperator
interface is declared in the java.util.functions
package like this
public interface BinaryOperator<T> extends Combiner<T,T,T> {
public T operate(T left, T right);
@Override
T combine(T t1, T t2) default {
return operate(t1, t2);
}
}
Like the Block
and Mapper
interfaces this is a functional interface which is why it is possible to pass the Lambda Expression
(i, j) -> i + j
to the reduce()
method.
The resulting program behaves as expected.
For example, the command
java xper.lambda.Sum 1 2 3 4 5 6
produces the output
sum == 21
Copyright (c) 2012 By Simon Lewis. All Rights Reserved.