Home > Articles > Programming > Java

  • Print
  • + Share This
This chapter is from the book

This chapter is from the book

3.6 Processing Lambda Expressions

Up to now, you have seen how to produce lambda expressions and pass them to a method that expects a functional interface. In the following sections, you will see how to write your own methods that can consume lambda expressions.

3.6.1 Implementing Deferred Execution

The point of using lambdas is deferred execution. After all, if you wanted to execute some code right now, you’d do that, without wrapping it inside a lambda. There are many reasons for executing code later, such as:

  • Running the code in a separate thread
  • Running the code multiple times
  • Running the code at the right point in an algorithm (for example, the comparison operation in sorting)
  • Running the code when something happens (a button was clicked, data has arrived, and so on)
  • Running the code only when necessary

Let’s look at a simple example. Suppose you want to repeat an action n times. The action and the count are passed to a repeat method:

repeat(10, () -> System.out.println("Hello, World!"));

To accept the lambda, we need to pick (or, in rare cases, provide) a functional interface. In this case, we can just use Runnable:

public static void repeat(int n, Runnable action) {
    for (int i = 0; i < n; i++) action.run();
}

Note that the body of the lambda expression is executed when action.run() is called.

Now let’s make this example a bit more sophisticated. We want to tell the action in which iteration it occurs. For that, we need to pick a functional interface that has a method with an int parameter and a void return. Instead of rolling your own, I strongly recommend that you use one of the standard ones described in the next section. The standard interface for processing int values is

public interface IntConsumer {
    void accept(int value);
}

Here is the improved version of the repeat method:

public static void repeat(int n, IntConsumer action) {
    for (int i = 0; i < n; i++) action.accept(i);
}

And here is how you call it:

repeat(10, i -> System.out.println("Countdown: " + (9 - i)));

3.6.2 Choosing a Functional Interface

In most functional programming languages, function types are structural. To specify a function that maps two strings to an integer, you use a type that looks something like Function2<String, String, Integer> or (String, String) -> int. In Java, you instead declare the intent of the function using a functional interface such as Comparator<String>. In the theory of programming languages this is called nominal typing.

Of course, there are many situations where you want to accept “any function” without particular semantics. There are a number of generic function types for that purpose (see Table 3–1), and it’s a very good idea to use one of them when you can.

Table 3–1 Common Functional Interfaces

Functional Interface

Parameter types

Return type

Abstract method name

Description

Other methods

Runnable

none

void

run

Runs an action without arguments or return value

Supplier<T>

none

T

get

Supplies a value of type T

Consumer<T>

T

void

accept

Consumes a value of type T

andThen

BiConsumer<T, U>

T, U

void

accept

Consumes a value of type T and U

andThen

Function<T, R>

T

R

apply

A function with argument of type T

compose, andThen, identity

BiFunction<T, U, R>

T, U

R

apply

A function with argument of type T and U

andThen

UnaryOperator<T>

T

T

apply

A unary operator on the type T

compose, andThen, identity

BinaryOperator<T>

T, T

T

apply

A binary operator on the type T

andThen, maxBy, minBy

Predicate<T>

T

boolean

test

A boolean-valued function

and, or, negate, isEqual

BiPredicate<T, U>

T, U

boolean

test

A boolean-valued function with two arguments

and, or, negate

For example, suppose you write a method to process files that match a certain criterion. Should you use the descriptive java.io.FileFilter class or a Predicate<File>? I strongly recommend that you use the standard Predicate<File>. The only reason not to do so would be if you already have many useful methods producing FileFilter instances.

Table 3–2 lists the 34 available specializations for primitive types int, long, and double. It is a good idea to use these specializations to reduce autoboxing. For that reason, I used an IntConsumer instead of a Consumer<Integer> in the example of the preceding section.

Table 3–2 Functional Interfaces for Primitive Types p, q is int, long, double; P, Q is Int, Long, Double

Functional Interface

Parameter types

Return type

Abstract method name

BooleanSupplier

none

boolean

getAsBoolean

PSupplier

none

p

getAsp

PConsumer

p

void

accept

ObjPConsumer<T>

T, p

void

accept

PFunction<T>

p

T

apply

PToQFunction

p

q

applyAsQ

ToPFunction<T>

T

p

applyAsP

ToPBiFunction<T, U>

T, U

p

applyAsP

PUnaryOperator

p

p

applyAsP

PBinaryOperator

p, p

p

applyAsP

PPredicate

p

boolean

test

3.6.3 Implementing Your Own Functional Interfaces

Ever so often, you will be in a situation where none of the standard functional interfaces work for you. Then you need to roll your own.

Suppose you want to fill an image with color patterns, where the user supplies a function yielding the color for each pixel. There is no standard type for a mapping (int, int) -> Color. You could use BiFunction<Integer, Integer, Color>, but that involves autoboxing.

In this case, it makes sense to define a new interface

@FunctionalInterface
public interface PixelFunction {
    Color apply(int x, int y);
}

Now you are ready to implement a method:

BufferedImage createImage(int width, int height, PixelFunction f) {
    BufferedImage image = new BufferedImage(width, height,
        BufferedImage.TYPE_INT_RGB);

    for (int x = 0; x < width; x++)
        for (int y = 0; y < height; y++) {
            Color color = f.apply(x, y);
            image.setRGB(x, y, color.getRGB());
        }
    return image;
}

To call it, supply a lambda expression that yields a color value for two integers:

BufferedImage frenchFlag = createImage(150, 100,
    (x, y) -> x < 50 ? Color.BLUE : x < 100 ? Color.WHITE : Color.RED);
  • + Share This
  • 🔖 Save To Your Account