Exercises
- Enhance the lazy logging technique by providing conditional logging. A typical call would be logIf(Level.FINEST, () -> i == 10, () -> "a[10] = " + a[10]). Don’t evaluate the condition if the logger won’t log the message.
When you use a ReentrantLock, you are required to lock and unlock with the idiom
myLock.lock(); try { some action } finally { myLock.unlock(); }
Provide a method withLock so that one can call
withLock(myLock, () -> { some action })
- Java 1.4 added assertions to the language, with an assert keyword. Why were assertions not supplied as a library feature? Could they be implemented as a library feature in Java 8?
- How many functional interfaces with Filter in their name can you find in the Java API? Which ones add value over Predicate<T>?
Here is a concrete example of a ColorTransformer. We want to put a frame around an image, like this:
First, implement a variant of the transform method of Section 3.3, “Choosing a Functional Interface,” on page 50, with a ColorTransformer instead of an UnaryOperator<Color>. Then call it with an appropriate lambda expression to put a 10 pixel gray frame replacing the pixels on the border of an image.
Complete the method
public static <T> Image transform(Image in, BiFunction<Color, T> f, T arg)
from Section 3.4, “Returning Functions,” on page 53.
- Write a method that generates a Comparator<String> that can be normal or reversed, case-sensitive or case-insensitive, space-sensitive or space-insensitive, or any combination thereof. Your method should return a lambda expression.
- Generalize Exercise 5 by writing a static method that yields a ColorTransformer that adds a frame of arbitrary thickness and color to an image.
- Write a method lexicographicComparator(String... fieldNames) that yields a comparator that compares the given fields in the given order. For example, a lexicographicComparator("lastname", "firstname") takes two objects and, using reflection, gets the values of the lastname field. If they are different, return the difference, otherwise move on to the firstname field. If all fields match, return 0.
Why can’t one call
UnaryOperator op = Color::brighter; Image finalImage = transform(image, op.compose(Color::grayscale));
Look carefully at the return type of the compose method of UnaryOperator<T>. Why is it not appropriate for the transform method? What does that say about the utility of structural and nominal types when it comes to function composition?
- Implement static methods that can compose two ColorTransformer objects, and a static method that turns a UnaryOperator<Color> into a ColorTransformer that ignores the x- and y-coordinates. Then use these methods to add a gray frame to a brightened image. (See Exercise 5 for the gray frame.)
- Enhance the LatentImage class in Section 3.6, “Laziness,” on page 56, so that it supports both UnaryOperator<Color> and ColorTransformer. Hint: Adapt the former to the latter.
- Convolution filters such as blur or edge detection compute a pixel from neighboring pixels. To blur an image, replace each color value by the average of itself and its eight neighbors. For edge detection, replace each color value c with 4c – n – e – s – w, where the other colors are those of the pixel to the north, east, south, and west. Note that these cannot be implemented lazily, using the approach of Section 3.6, “Laziness,” on page 56, since they require the image from the previous stage (or at least the neighboring pixels) to have been computed. Enhance the lazy image processing to deal with these operations. Force computation of the previous stage when one of these operators is evaluated.
- To deal with lazy evaluation on a per-pixel basis, change the transformers so that they are passed a PixelReader object from which they can read other pixels in the image. For example, (x, y, reader) -> reader.get(width - x, y) is a mirroring operation. The convolution filters from the preceding exercises can be easily implemented in terms of such a reader. The straightforward operations would simply have the form (x, y, reader) -> reader.get(x, y).grayscale(), and you can provide an adapter from UnaryOperation<Color>. A PixelReader is at a particular level in the pipeline of operations. Keep a cache of recently read pixels at each level in the pipeline. If a reader is asked for a pixel, it looks in the cache (or in the original image at level 0); if that fails, it constructs a reader that asks the previous transform.
- Combine the lazy evaluation of Section 3.6, “Laziness,” on page 56, with the parallel evaluation of Section 3.7, “Parallelizing Operations,” on page 57.
- Implement the doInOrderAsync of Section 3.8, “Dealing with Exceptions,” on page 58, where the second parameter is a BiConsumer<T, Throwable>. Provide a plausible use case. Do you still need the third parameter?
- Implement a doInParallelAsync(Runnable first, Runnable second, Consumer<Throwable>) method that executes first and second in parallel, calling the handler if either method throws an exception.
- Implement a version of the unchecked method in Section 3.8, “Dealing with Exceptions,” on page 58, that generates a Function<T, U> from a lambda that throws checked exceptions. Note that you will need to find or provide a functional interface whose abstract method throws arbitrary exceptions.
- Look at the Stream<T> method <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner). Should U be declared as ? super U in the first type argument to BiFunction? Why or why not?
- Supply a static method <T, U> List<U> map(List<T>, Function<T, U>).
- Supply a static method <T, U> Future<U> map(Future<T>, Function<T, U>). Return an object of an anonymous class that implements all methods of the Future interface. In the get methods, invoke the function.
- Is there a flatMap operation for CompletableFuture? If so, what is it?
- Define a map operation for a class Pair<T> that represents a pair of objects of type T.
- Can you define a flatMap method for Pair<T>? If so, what is it? If not, why not?