Java 8 Lambdas: Functional Programming For The Masses by Richard Warburton is a short but self-contained book on new features of lambdas and streams for Java 8. I found this book a pleasant read especially with short and easy to understand code snippets. More to it, the author is able to deliver many best practices enabled by streams and lambdas, which are quite useful. This post is a list of notes that I find useful and might need to come back and check again.

Basics

  • Java needs to change again because of programming paradigms change as a result of computer technology advance
  • Function programming mean different things to different people. In the book it means ways to write better Java programs enabled by lambdas and streams

Lambdas

  • Try to spot -> in the code, as well as ::,
  • They are objects of functional interfaces, i.e., interfaces with one single abstract methods

Streams

  • Good candidates to replace simple loops
  • Enables lazy evaluation, extremely powerful when a list of operations are chained
  • Common operations
    • collect
    • map
    • filter
    • flatMap
    • max and min
    • reduce
  • Very powerful to refactor existing code
  • Enables abstraction of higher-order functions

Library Support

  • Enables lazy evaluation, abstraction of an action to be performed
  • When possible, use primitive specific versions of stream API such as IntStream, LongStream
  • Provide a type hint when type inference does not work
  • @FunctionalInterface annotates interfaces that define lambda types
  • One motivation for default interface was to make existing library compatible, for instance, making Collection implement some interface without needing to change every Collection implementation.
  • Class wins over interface when method name resolution happens
  • Subtype wins over supertype
  • Enhances super syntax: Carriage.super.rock() to access default rock() method in Carriage interface.
  • Stream.of(...) is a static method on an interface, a new feature that helps library designers but can also replace a util class that is all static methods.
  • Optional is new in Java 8. Use it well and profit.

Collections and Collectors

  • Method reference
    • artist -> artist.getName() vs Artist::getName
    • (name, nationality) -> new Artist(name, nationality) vs Artist::new
    • String[]::new
  • Encounter order of stream elements
  • collect
    • into other collections: collect(toList()) or with custom collection: stream.collect(toCollection(TreeSet::new));
    • to values: artists.collect(maxBy(comparing(getCount))); or albums.stream().collect(averagingInt(album -> album.getTrackList().size()));
  • Partitioning the data
    • by a predicate, yielding a Map<Boolean, List<Artist>>: artists.collect(partitioningBy(artist -> artist.isSolo()));
    • grouping by an object: albums.collect(groupingBy(album -> album.getMainMusician()));
  • String specific collectors
    • strStream.collect(Collectors.joining(", ", "[", "]"));
  • Composing collectors together
    • albums.collect(groupingBy(album -> album.getMainMusician(), counting())); This line first do a groupingBy, then act on each grouped bucket with another collector counting, yielding a Map<Artist, Long> where the value is the result of counting
    • albums.collect(groupingBy(Album::getMainMusician, mapping(Album::getName, toList()))); this line first do a groupingBy, then act on each grouped bucket with another collector defined as mapping(Album::getName, toList()): yielding a Map<Artist, List<String>> where each bucket of grouped List<Album> is processed by this second collector mapping(...) and each element in the bucket is mapped by Album::getName, and then collected (again!) by toList()
  • Customer collectors are based on Collectors.reducing (reread this part when it becomes necessary)
  • The book mentioned a new method computeIfPresent function for Map in Java 8. Digression: here are all new methods for Map interface since Java 8: getOrDefault, forEach, replaceAll, putIfAbsent, remove, replace, computeIfAbsent, computeIfPresent, compute, merge. (Source)

Data Parallelism

  • One must be very cautious when to use the .parallelStream() function provided by Java 8 streams. It is not always faster; if fact in many cases it isn’t.
  • The kinds of problems / data structures that parallelism may provide a speed boost:
    • Monte Carlo simulation
    • Data sources that support random access, e.g., ArrayList
    • Specific parallel operations on arrays: parallelPrefix, parallelSetAll, parallelSort

Testing & Debugging

  • Core idea is to take a lambda as a unit of computation
    • If the lambda is chaining or complex, extract out smaller functions and test those
  • When debugging, use .peak() method to take a look at the elements in question, such as .peek(nation -> System.out.println("Found nationality: " + nation)). This works for logging as well.
  • Rely on IDE breakpoints support

Design and Architecture Principles

  • Some design patterns now save a lot of boiler-place code with the use of lambda expressions or method references, such Command Pattern, Strategy Pattern, Observer Pattern, and Template Method Pattern
  • Domain specific languages are also made a lot easier with lambda expressions, such as some testing frame works (the book has some interesting elaboration on how those DSLs are constructed, too)
  • Design principles
    • Single Responsibility Principle: using lambdas makes supporting separation of responsibility easier.
    • Open Close Principle: extending functionalities of a class can be done by passing in abstracted computations into high-order functions of the class.
    • Dependency Inversion Principle: details should depend on abstraction, rather than abstraction depending on details. In the context of lambda expressions, a higher-order function provides a natural abstraction with details to be filled in.

Lambda-Enabled Concurrency

  • Lambdas encapsulate the notion of “callback”, which enables non-blocking IO more naturally.
  • Works well in CompletableFutures and supports event driven systems.