Desktop Java

Java 8 Released! — Lambdas Tutorial

To celebrate the release of Java 8 which was released just minutes ago, I am publishing a draft version of my Java 8 Lambdas Tutorial.  It is a nice visual way to learn the Streams API, and will help you get started taking advantage of lambdas in your own applications from day 1.  This article is scheduled to appear in the next Java Magazine release, so please look forward to the final version, and I will do my best to incorporate comments and feedback if they meet the publication deadline.

pic1

Mary Had a Little Lambda

Java lambdas are the most impactful feature to enter the Java language since the release of generics in Java 5.  It fundamentally changes the programming model, allowing a functional style of development, and supports efficient parallelization of code to take advantage of multi-core systems.  Although as a Java developer you will first notice the productivity improvements that you gain using the new lambda-enabled APIs in Java 8.

In this article we will walk you through the new Streams API for working with collections and data by using a retro game written in JavaFX.  This game is both a simple Java 8 application written from the ground up to showcase lambdas best practices, and also a visual guide to programming with the Streams API.  However, we will first lay the foundation with an introduction to the lambdas language changes.

Introduction to Lambdas

To use lambdas you must be using a recent Java SDK (8 or higher) and set the language level to Java 8 when you compile.  You can download the latest Java SDK version from:

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 and IntelliJ are noteworthy as having the best lambdas support out of the box at the time of the Java 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 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 may have is how the Java team is able to add in new methods to interfaces without breaking backwards compatibility.  For example, if you have code that implements the Collection interface and does not have a forEach method defined, then won’t the upgrade to Java 8 break your implementation?  Fortunately, another feature called extension methods solves this problem in Java 8.  The implementation of forEach on the Collection interface is shown in the following code listing:

interface Collection<T> {
  default void forEach(Block<T> action) {
    Objects.requireNonNull(action);
    for (T t : this)
      action.apply(t);
  }
  // Rest of Collection methods…
}

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 there is none defined they will get the same standard behavior as 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, put a dash, greater-than sign [->] in the middle, and follow that with a method body in curly braces:

(int a, int b) -> { return a + b; }

In the case where the function returns a value, this can be simplified by removing the curly braces, return keyword, and semicolon:

(a, b) -> a + b

Furthermore, in the case where there is only one parameter you can leave off the parenthesis:

a -> a * a

And finally, if you have no parameters, you can simply leave the parenthesis blank, 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.

Method ReferenceLambda Equivalent
Objects::toStringobj -> Objects.toString(obj)Static method reference
Object::toStringobj -> obj.toString()Member method reference
obj::toString() -> obj.toString()Object method reference
Object::new() -> new Object()Constructor method reference

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, they introduced a new FunctionalInterface annotation that optionally be used to mark interfaces in order to get assistance from the complier in checking to make sure your interface meets the single explicitly declared abstract method requirement:

@FunctionalInterface
interface Sum {
  int add(int a, int b);
}

This 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 since they are not abstract and don’t count towards the single abstract method requirement.

Now that you have a basic understanding of the lambda syntax, it is time to explore the streams API and show 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!

pic2Nowadays video games are all about high-resolution 3D 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…  cute, pixelated little figures dancing and RPG-walking their way through well-designed and insanely difficult levels.

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 is in GitHub at the following location:

For all the graphics used in the game, the images are laid out in a standard 3×4 tiled format as shown in the adjacent sprite sheet for Mary. The code for animating sprites is done (of course) using a lambda, and simply moves the viewport around a tiled image in order to produce a 3-frame walking animation [horizontal] and to change the direction the character is facing [vertical].

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);

Add a static image for a background, and some key event listeners to move the character on input, and you have the basics of a classic RPG game!

pic3

Generating Streams

There are several ways to create a new Java 8 Stream.  The easiest way is to start with a collection of your choice and simply call the stream() or parallelStream() methods 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 following code:

Stream.of("bananas", "oranges", "apples");

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.  These let you create a new stream of objects using a lambda that gets called to return a new object.  The iterate method is particularly interesting, 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:

Stream.iterate(Color.RED,
  c -> Color.hsb(c.getHue() + .1, c.getSaturation(),
                                  c.getBrightness()));

To demonstrate how this works visually, we are going to add in a new element to the application that generates sheep when we step on it.

The code for the new Barn class is as follows:

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);
    }
}

This code specifies the image to use for the sprite-based graphics, which is passed in to the super constructor, and implements a visit method that has the logic that will get executed when Mary steps on the Barn.

The first statement in the visit method simply gets the last element from the list of animals following Mary, or 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 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.  Since 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 give a parameter of 7 to generate seven sheep 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 with the lambda expression set to 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:

pic4

The next element we are going to add to the game is a rainbow that will demonstrate filtering in the Streams 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 of the elements where the predicate lambda evaluated to true.

For the logic of the rainbow, we will execute a filter that returns every 4th 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 following code 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));

And when Mary steps on the rainbow, all the lambs get colored according the Color values you specified:

pic5

“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 back a FilteredList containing only the items that match the predicate.

We will use these to implement a Church object that will filter on “pure” animals.  Any animals that are white in color will be cooked by the church staff to feed the needy.  This includes incrementing the counter of “Meals Served” on the sign and removing the “pure” animals from the list.  The code for the church visit method is shown below.

Predicate<SpriteView> pure =
    a -> a.getColor() == null;

mealsServed.set(mealsServed.get() +
    s.getAnimals().filtered(pure).size()
);

s.getAnimals().removeIf(pure);

And you can see the result of successively stepping on the rainbow and church in the following screen capture.

pic6

“Lamb”da Question 2: Is it possible to use the church to clear all the animals after they have already been colored?

Probably the most powerful operation in the Streams API is the map function.  This 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 here:

// single map:
s.getAnimals().setAll(s.getAnimals()
    .stream()
    .map(sv -> new Eggs(sv.getFollowing())
).collect(Collectors.toList()));

The second implementation uses method references with a chained set of map operations to first convert the stream to a stream of whom the animals are following, and then to call a constructor method reference to create the eggs, passing in the following information to the constructor parameter:

// or a double map:
s.getAnimals().setAll(s.getAnimals()
    .stream().parallel()
    .map(SpriteView::getFollowing)
    .map(Eggs::new)
    .collect(Collectors.toList())
);

Both of these code fragments behave and perform similarly since the stream API is designed to be lazy and only evaluate the stream when a terminal operation (such as collect) is called.  So it is primarily a style issue for which one you prefer to use.  Running the program with the new chicken coop MapObject will let you generate eggs from lambs as shown in the following picture:

pic7

“Lamb”da Question 3: If you send colored lambs to the chicken coop, what color are the eggs?

Notice that each of the eggs sprites contain 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 following hatch method:

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);
}

Notice that this method returns back a Stream of objects, which means if we used a normal map operation we would get back a Stream of Streams.  In order to flatten the Stream into a single list of chickens, we can instead use flatMap, which will both map the stream using a lambda function and also collapses the nested Streams into single list of objects.  The implementation of the nest visit function utilizing flatMap is shown below:

s.getAnimals().setAll(s.getAnimals()
    .stream().parallel()
    .flatMap(SpriteView.Eggs::hatch)
    .collect(Collectors.toList())
);

Now, upon bringing eggs to the nest, you will get an explosion of chickens as shown in the following screenshot:

pic8

“Lamb”da Question 4: Approximately 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 scale of the animals, and then we will reduce that using a sum method reference into a single value.  The reduce function takes a seed value (which we will use 0 for), 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 animal scales.

Double mealSize = shepherd.getAnimals()
    .stream()
    .map(SpriteView::getScaleX)
    .reduce(0.0, Double::sum);

setScaleX(getScaleX() + mealSize * .2);
setScaleY(getScaleY() + mealSize * .2);
shepherd.getAnimals().clear();

We then take the sum (stored into the variable called mealSize) and use that to stretch the fox proportionally.  You can see the result of a very tasty meal for the fox in the following picture:

pic9

“Lamb”da Question 5: How can you change the code for the Fox to make him fatter when he eats?

In this article we covered the basic lambda syntax, including method references, extension methods, and functional interfaces.  Then we went into detail in the Streams API, showcasing some of the common operations such as iterate, filter, map, flatMap, and reduce.  As you have seen, Java 8 lambdas dramatically shifts the programming model, allowing you to write simpler and more elegant code, and opening up the possibility of new powerful APIs like Streams.  Now it is time to start taking advantage of these capabilities in your own development.

Reference: Java 8 Released! — Lambdas Tutorial from our JCG partner Stephen Chin at the Steve On Java blog.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button