Skip to content

Instantly share code, notes, and snippets.

@prakhar1989
Last active April 18, 2018 16:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save prakhar1989/307700e84536dfa18c1962d938abc63b to your computer and use it in GitHub Desktop.
Save prakhar1989/307700e84536dfa18c1962d938abc63b to your computer and use it in GitHub Desktop.
Java Reading Notes

Java Lambdas and Closures

Chapter 1 - Intro

Google Guava Implementation of cloneWithoutNulls(List)

public static <A> List<A> cloneWithoutNulls(final List<A> list) {
    Collection<A> nonNulls = Collections2.filter(list, Predicates.notNull());
    return new ArrayList<>(nonNulls);
}

Java 8 Predicate Implementation of cloneWithoutNulls(List)

public static <A> List<A> cloneWithoutNulls(final List<A> list) {
    List<A> toReturn = new ArrayList<>(list);
    toReturn.removeIf(Predicate.isEqual(null));
    return toReturn;
}

Java 8 Lambdas Implementation of cloneWithoutNulls (list)

public static <T> List<T> cloneWithoutNulls(final List<T> list) {
    List<T> newList = new ArrayList<>(list);
    newList.removeIf(it -> it == null);
    return newList;
}

Storing a For-Loop in Java 8

Consumer<Iterable> printObjects = list -> list.forEach(System.out::println)

Inversion of Control: The basic idea is that you can get rid of boilerplate by having some library execute the boilerplate, and then calling back to client code with the relevant pieces of context. The “control” is “inverted” because calling into a library normally gives control to that library, but in this case the library is giving control back to the caller.


Chapter 2 - Understanding Lambdas in Java 8

A function that returns a lambda

// need to be wrapped in a method so that types can be inferred. (Java 8 idiomatic)
public static <T> Function<T, T> identityFunction() {
    return value -> value;
}

// identity function implemented using an anonymous inner class
// explicit types (and more verbosity) in this case
public static <T> Function<T, T> identityFunction() {
    return new Function<T, T>() {
        @Override
        public T apply(T value) {
            return value;
        }
    }
}

Note: As a general rule, define lambdas inline to the variable assignment if you know the concrete types, and define lambdas within methods to capture more complex type information.

Function<Number, Number> squareFn = it -> it * it; // know types, so direct assignment possible.

For Java developers, it is easiest to think of the term closure as meaning encapsulating function. In the same way that an object can encapsulate state and expose it through a particular API (such as properties exposing fields), a closure can also encapsulate some state and then act on that state wherever and whenever the closure is invoked

Java 8's lambda as a closure

public static void main(String[] args) {
    String greeting = "Hello, ";
    Function<String, String> greeter = whom -> greeting + whom + "!"; // encloses over greeting variable
    System.out.println(greeter.apply("World"));
}

Supplier: Zero argument functions - For lambdas without arguments, the functional interface is Supplier: the name was picked because they are used to supply values without needing any input.

Supplier<Number> giveOnes = () -> 1;
System.out.println(giveOnes.get());

// two arguments (the BiFunction interface)
BiFunction<String, String, String> concat = (a, b) -> a + b;

Java’s SDK does not contain support for lambdas with more than two arguments, because you really shouldn’t be using lambdas for that kind of complex case

Java doesn't support partial application.

// this is not valid
Function<String, String> prefixWithHello = concat.apply("Hello, "); 
// while this is
Function<String, String> prefixWithHello = (whom) -> concat.apply("Hello, ", whom);

However, we can write our applyPartial function which does this -

// usage: Function<String, String> = applyPartial(concat, "Hello, ");
public static <T, U, V> Function<U, V> applyPartial(BiFunction<T, U, V> fn, T firstArg) {
    return u -> fn.apply(firstArg, u);
}

Consumer: The functional interface for a lambda that produces nothing

Consumer<String> doGreet = name -> System.out.println("Hello, " + name);

Multi-body lambdas need curlies to wrap the body in and also an explicit return statement.

Function<File, Byte> firstByte = file -> {
    try (InputStream is = new FileInputStream(file)) {
        return (byte) is.read();
    } catch (IOException ioe) {
        throw new RuntimeException("Could not read " + file, ioe);
    }
};

Operators: Lambdas for which the types of arguments and return values are the same e.g. BinaryOperator and UnaryOperator.

// a slightly more concise type signature from the BiFunction definition above
BinaryOperator<String> concat = (l, r) -> l + r;

Predicate: Lambdas that take any type and returns a boolean

Predicate<String> notNullorEmpty = s -> s != null && s.length() > 0;
Making Methods into Lambdas

In Java 8, you can turn static methods, instance methods, and even constructors to lambdas (called method reference) using the :: operator.

// static methods (IntFunction is a functional interface that takes an primitive int)
IntFunction<String> intToString = Integer::toString;
Function<String, Integer> parseInt = Integer::valueOf;

// constructors
Function<String, BigInteger> newBigInt = BigInteger::new;

// instance methods into lambdas
IntSupplier randomInt = new Random()::nextInt;
Consumer<String> print = System.out::println;

As of Java 8, interfaces can also now provide static methods, just as any concrete or abstract class could provide static methods. Now that interfaces can have static methods, some Java interfaces have been extended to provide utility methods. By allowing static methods, Java 8 has outdated the “utility class” convention, where an interface Foo would have utility class Foos or FooUtils. Now those utility methods reside directly on the interface itself.

Functional Interface Helper Methods
  • Function.identity(): A function that simply returns its argument
  • Function.andThen(): Change the result type of a function
  • Function.compose(): Change the argument type of a function
UnaryOperator<Integer> doubleIt = e -> e * 2;
UnaryOperator<Integer> squareIt = e -> e * e;

doubleIt.compose(squareIt).apply(4);   // Returns 32 since 4 is squared first and then doubled
doubleIt.andThen(squareIt).apply(4);   // Returns 64 since 4 is doubled first and then squared

Note: Java provides a Consumer.andThen which can be used at the end of the pipeline to log / print etc.

  • Predicate.and and Predicate.or: Boolean operations on predicates.
Predicate<Integer> isEven = e -> e % 2 == 0;
Predicate<Integer> multipleOfSix = isEven.and(e -> e % 3 == 0);
  • BinaryOperator.minBy and BinaryOperator.maxBy: returns the argument that is largest (maxBy) or smallest (minBy)

A word of caution: Many of the structures built off of lambdas will perform concurrent executions, sometimes without much warning. Because of this, your lambdas always need to be threadsafe. Pay particular attention to this with instance method handles, since thread-dangerous state can often be hiding within those instances.


Chapter 3 - Lambda’s Domain: Collections, Maps, and Streams

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment