Mary Had a Little Lambda
by Stephen Chin
Published May 2014
Get familiar with lambdas and the Stream API through a simple game.
Lambda expressions are the most impactful feature to enter the Java language since the release of generics in Java SE 5. They fundamentally change the programming model, allowing a functional style of development, and they support efficient parallelization of code to take advantage of multicore systems. However, as a Java developer, you will first notice the productivity improvements you gain by using the new lambda-enabled APIs in Java SE 8.
In this article, we will use a retro game written in JavaFX to walk through the new Stream API for working with collections and data. This game is a simple Java SE 8 application written from the ground up to showcase lambdas best practices, and it is also a visual guide to programming with the Stream API. However, we will first lay the foundation with an introduction to the lambdas language changes.
Originally published in the May/June 2014 issue of Java Magazine. Subscribe today.
At a Devoxx4Kids session, kids learned to code using the sample application in this article.
Introduction to Lambdas
To use lambdas, you must be using a recent Java SDK (version 8 or higher) and set the language level to Java SE 8 when you compile. You can download the latest Java SDK version here.
Developing lambdas is a lot easier when using an IDE that supports the new syntax. Most Java IDEs have been updated with lambdas support and will assist you with real-time error reporting and code completion of lambdas. NetBeans IDE and IntelliJ are noteworthy as having the best lambdas support out of the box at the time of the Java SE 8 release, and both work well with the example we are demonstrating here.
To demonstrate how the new lambdas feature works, here is a short snippet of code that iterates through a list of shapes and changes the blue ones to red:
for (Shape s : shapes) {
if (s.getColor() == BLUE)
s.setColor(RED);
}
In Java SE 8, you could rewrite the same code by using a forEach
and a lambda expression, as follows:
shapes.forEach(s -> {
if (s.getColor() == BLUE)
s.setColor(RED);
});
The lambda form makes use of a new method on the Collection
interface called forEach
, which takes a lambda expression and evaluates it for all the contained elements. Similar API enhancements have been made throughout the Java core classes in order to simplify the usage of lambda expressions.
A related question you might have is how the Java team was able to add in new methods to interfaces without breaking backward compatibility. For example, if you have code that implements the Collection
interface and does not have a forEach
method defined, won’t the upgrade to Java SE 8 break your implementation? Fortunately, another feature called extension methods solves this problem in Java SE 8. The implementation of forEach
on the Collection
interface is shown in Listing 1.
interface Collection<T> {
default void forEach(Consumer<T> action) {
Objects.requireNonNull(action);
for (T t : this)
action.apply(t);
}
// Rest of Collection methods...
}
Listing 1
Notice the new default
keyword, which indicates that the method will be followed by a default implementation. Subclasses are free to create their own implementation of the method, but if none is defined, the subclasses will get the standard behavior defined in the interface. This allows new methods to be added to existing interfaces in the core Java classes, as well as in your own libraries and projects.
The actual lambda syntax is quite simple: in its full form, you supply the types and parameters on the left, insert a dash followed by the greater-than sign (->
) in the middle, and follow that with a method body inside curly braces, as shown below:
(int a, int b) -> { return a + b; }
In cases where the function returns a value, the code can be simplified by removing the curly braces, the return
keyword, and the semicolon:
(a, b) -> a + b
Furthermore, in cases where there is only one parameter, you can leave off the parentheses:
a -> a * a
And finally, if you have no parameters, you can simply leave the parentheses empty, as shown below, which is common for replacing Runnable
implementations or other no-parameter methods.
() ->{ System.out.println("done"); }
In addition to the basic syntax, there is also a special shortcut syntax called method references, which lets you quickly create lambda expressions that refer to a single method as the implementation. The following table summarizes the different types of method references along with the equivalent long-form lambda syntax.
The last concept that is important when working with the new lambdas methods is the creation of interfaces that allow you to accept lambda expressions. For this purpose, any interface that has one explicitly declared abstract method can be used to accept a lambda expression and is, thus, called a functional interface.
As a convenience, a new FunctionalInterface
annotation was introduced that optionally can be used to mark interfaces in order to get assistance from the compiler in checking that the interface meets the requirement for a single, explicitly declared abstract method:
@FunctionalInterface
interface Sum {
int add(int a, int b);
}
Using this annotation is a recommended best practice, because it will catch corner cases in the definition of functional interfaces—such as the inclusion of default methods that allow you to have multiple methods defined on a functional interface, because they are not abstract and don’t count toward the single-abstract-method requirement.
Now that you have a basic understanding of the lambda syntax, it is time to explore the Stream API and see the power of lambdas in the context of a visual example.
Retro Gaming with Lambdas
Mary had a little lambda
Whose fleece was white as snow
And everywhere that Mary went Lambda was sure to go!
Nowadays video games are all about high-resolution 3-D graphics, cinematic-quality cut scenes, and difficulty levels that range from newbie to pacifist. However, in the good old days of gaming, we just had sprites (see Figure 1): cute, pixelated little figures dancing and walking their role-playing game (RPG)–way through well-designed and insanely difficult levels.
Figure 1
Sprite-based graphics also happen to be really simple to program, allowing us to build a full animation system in under 400 lines of code. The full application code for our game is in GitHub. For all the graphics used in the game, the images are laid out in a standard 3 x 4 tiled format, as shown in the sprite sheet for Mary (see Figure 2).
Figure 2
The code for animating sprites is done (of course) using a lambda, and it simply moves the viewport around a tiled image in order to produce a three-frame walking animation (horizontal) and to change the direction the character is facing (vertical). See Listing 2.
ChangeListener<Object> updateImage =
(ov, o, o2) -> imageView.setViewport(
new Rectangle2D(frame.get() * spriteWidth,
direction.get().getOffset() * spriteHeight,
spriteWidth, spriteHeight));
direction.addListener(updateImage);
frame.addListener(updateImage);
Listing 2
Add a static image for a background (see Figure 3), and some key event listeners to move the character upon input, and you have the basics of a classic RPG game.
Figure 3
Generating Streams
There are several ways to create a new Java SE 8 stream. The easiest way is to start with a collection of your choice and simply call the stream()
or parallelStream()
method to get back a Stream
object, such as in the following code snippet:
anyCollection.stream();
You can also return a stream from a known set of objects by using the static helper methods on the Stream
class. For example, to get back a stream that contains a set of Strings, you could use the code in Listing 3.
Stream.of("bananas", "oranges", "apples");
Listing 3
Similarly, you can use the Stream
numeric subclasses, such as IntStream
, to get back a generated series of numbers:
IntStream.range(0, 50)
But the most interesting way to generate a new series is to use the generate
and iterate
methods on the Stream
class, which let you create a new stream of objects using a lambda that gets called to return a new object. The iterate
method is of interest because it will pass in the previously created object to the lambda. This lets you return a distinct object for each call, such as returning all the colors in the rainbow iteratively (see Listing 4).
Stream.iterate(Color.RED,
c -> Color.hsb(c.getHue() + .1, c.getSaturation(),
c.getBrightness()));
Listing 4
To demonstrate how this works visually, we are going to add a new barn element to the application, which generates a lamb when Mary steps on it. The code for the new Barn
class is shown in Listing 5. This code specifies the image to use for the sprite-based graphic, which is passed in to the superconstructor, and implements a visit
method that has the logic that will get executed when Mary steps on the barn.
public static class Barn extends MapObject {
static final Image BARN = loadImage("images/barn.png");
public Barn(Main.Location loc) {
super(BARN, loc);
}
@Override
public void visit(Shepherd s) {
SpriteView tail = s.getAnimals().isEmpty() ?
s : s.getAnimals().get(s.getAnimals().size() - 1);
Stream.iterate(tail, SpriteView.Lamb::new)
.skip(1).limit(7)
.forEach(s.getAnimals()::add);
}
}
Listing 5
The first statement in the visit
method simply gets the last element from the list of animals that are following Mary, or it returns her if there are no animals yet. This is then used as the seed to the iterate
method, which gets passed to the Lamb
constructor for the first invocation of the lambda. The lamb that gets generated by this is then passed in to the Lamb
constructor for the second invocation, and this process repeats in succession.
The resulting stream includes the seed (so we can use the skip
function to remove that from the stream), and it is theoretically infinite. Because streams are lazy, we don’t need to worry about objects getting created until we add a terminal operation, but an easy way to fix the length of the stream is to use the limit
function, which we will pass a parameter value of 7 to generate seven lambs that are following Mary.
The last step is to add a terminal operation that will use the stream. In this case, we will use a forEach
function that applies a method reference to the add
method on the list of animals. The result of executing this lambda is the addition of sevens lambs following Mary in succession, as shown in Figure 4.
Figure 4
The next element we are going to add to the game is a rainbow that will demonstrate filtering in the Stream API. The way the filter
function works is that it takes a predicate lambda, which evaluates to true
or false
for each element in the stream. The resulting stream contains all the elements for which the predicate lambda evaluated to true
.
For the logic of the rainbow, we will execute a filter that returns every fourth animal in the stream and apply a JavaFX ColorAdjust
function to shift the hue to match the passed-in color. For white, we are using null
(no color shift). The code in Listing 6 is the implementation of the visit
method for the rainbow MapObject
.
s.getAnimals().stream()
.filter(a -> a.getNumber() % 4 == 1)
.forEach(a -> a.setColor(null));
s.getAnimals().stream()
.filter(a -> a.getNumber() % 4 == 2)
.forEach(a -> a.setColor(Color.YELLOW));
s.getAnimals().stream()
.filter(a -> a.getNumber() % 4 == 3)
.forEach(a -> a.setColor(Color.CYAN));
s.getAnimals().stream()
.filter(a -> a.getNumber() % 4 == 0)
.forEach(a -> a.setColor(Color.GREEN));
Listing 6
When Mary steps on the rainbow, all the lambs get colored according to the Color
values you specified (see Figure 5).
Figure 5
“Lamb”da question 1. What happens if you step on the barn after visiting the rainbow?
Another way to use filtering is to take advantage of the new methods added to the Collection API that accept a predicate lambda. These include removeIf
, which filters out all the elements that don’t match the given predicate, and filtered
, which is on ObservableList
and returns a FilteredList
containing only the items that match the predicate.
We will use these methods to implement a Church
object that will filter on “pure” (white) animals; then, any animals that are white will be cooked by the church staff to feed the needy. This functionality includes incrementing the counter of “meals served” and removing the “pure” animals from the list. The code for the church visit
method is shown in Listing 7.
Predicate<SpriteView> pure =
a -> a.getColor() == null;
mealsServed.set(mealsServed.get() +
s.getAnimals().filtered(pure).size()
);
s.getAnimals().removeIf(pure);
Listing 7
You can see the result of successively stepping on the rainbow and the church in Figure 6.
Figure 6
“Lamb”da question 2. Is it possible to use the church to remove all the animals after they have already been colored?
The map
function is probably the most powerful operation in the Stream API. It allows you to convert all the elements in the stream from one type of object to another, performing powerful transformations along the way. We will use this to implement a chicken coop where all the animals following Mary will get converted into eggs.
I have two implementations of the visit
method for the chicken coop. The first one uses a single map operation with a lambda expression to replace the stream elements with eggs, as shown in Listing 8.
// single map:
s.getAnimals().setAll(s.getAnimals()
.stream()
.map(sv -> new Eggs(sv.getFollowing())
).collect(Collectors.toList()));
Listing 8
The second implementation uses method references with a chained set of map operations to first convert the stream to a stream containing who the animals are following, and then to call a constructor method reference to create the eggs, passing in the information shown in Listing 9 to the constructor parameter.
// or a double map:
s.getAnimals().setAll(s.getAnimals()
.stream().parallel()
.map(SpriteView::getFollowing)
.map(Eggs::new)
.collect(Collectors.toList())
);
Listing 9
The code in both of these listings behaves and performs similarly, because the Stream API is designed to be lazy and only evaluate the stream when a terminal operation (such as collect
) is called. Therefore, it is primarily a style issue as to which version you prefer to use. Running the program with the new chicken coop MapObject
will let you generate eggs from lambs, as shown in Figure 7.
Figure 7
“Lamb”da question 3. If you send colored lambs to the chicken coop, what color are the eggs?
Notice that each of the egg sprites contains three little bouncing eggs. Wouldn’t it be nice if we could hatch these guys into chickens?
To hatch the eggs, we will add in a new MapObject
for a nest where the eggs will be hatched into a group of three chickens using the hatch
method shown in Listing 10.
public static Stream<SpriteView> hatch(SpriteView sv) {
if (!(sv instanceof Eggs)) {
return Stream.of(sv);
}
return Stream.iterate(sv, Chicken::new).skip(1).limit(3);
}
Listing 10
Notice that the hatch
method returns a stream of objects, which means if we used a normal map operation we would get back a stream of streams. To flatten the stream into a single list of chickens, we can instead use flatMap
, which will map the stream using a lambda function and also collapse the nested streams into a single list of objects. The implementation of the nest visit
function utilizing flatMap
is shown in Listing 11.
s.getAnimals().setAll(s.getAnimals()
.stream().parallel()
.flatMap(SpriteView.Eggs::hatch)
.collect(Collectors.toList())
);
Listing 11
Now, upon bringing eggs to the nest, you will get an explosion of chickens, as shown in Figure 8.
Figure 8
“Lamb”da question 4. About how many animals can you add before the game runs out of memory?
The final element we will add is a fox to demonstrate how to reduce a stream. For this, we will first map the stream to a list of integers according to the weight of the animals, and then we will reduce that to a single value using a sum
method reference. See Listing 12. The reduce
function takes a seed value (for which we will use 0
) and a function that can reduce two elements into a single result. This lambda will be applied recursively for all the elements in the stream until a single value results, which will be the sum of all the animals’ weights.
Double mealSize = shepherd.getAnimals()
.stream()
.map(SpriteView::getScaleX)
.reduce(0.0, Double::sum);
setScaleX(getScaleX() + mealSize * .2);
setScaleY(getScaleY() + mealSize * .2);
shepherd.getAnimals().clear();
Listing 12
We then take the sum (stored into the variable called mealSize
) and use that to stretch the fox proportionally. Figure 9 shows the result of a tasty meal for the fox.
Figure 9
“Lamb”da question 5. How can you change the code for the fox to make him fatter when he eats?
Conclusion
In this article, we covered the basic lambda syntax, including method references, extension methods, and functional interfaces. Then we went into detail about the Stream API, showcasing some of the common operations, such as iterate
, filter
, map
, flatMap
, and reduce
.
As you have seen, Java SE 8 lambdas dramatically shift the programming model—allowing you to write simpler and more-elegant code, and opening up the possibility of new powerful APIs such as Stream. Lambdas are also used extensively throughout the Java core APIs, including new functions on I/O, inclusion in the new Date and Time API, and functional interfaces added to APIs with callbacks like JavaFX. This all leads to a more functional style of programming that you can use to build code that runs efficiently on multiprocessor systems. Why not start taking advantage of these capabilities in your own code?
Learn More
Stephen Chin (@steveonjava) is a Java technology ambassador at Oracle, the JavaOne Content Chair, and a coauthor of Pro JavaFX 2 (Apress, 2012), the leading reference for JavaFX. He has been featured at many Java conferences including Devoxx; CodeMash; OSCON; J-Fall; GeeCON; JAZOON; and JavaOne, where he has twice received a Rock Star award.