Jim Cheung

Functional Programming in Java

(notes from Functional Programming in Java: Harnessing the Power Of Java 8 Lambda Expressions)

Chapter 1: Hello, Lambda Expressions!

enforcing policies

// old style
Transaction transaction = getFromTransactionFactory();
// ...
checkProgressAndCommitOrRollbackTransaction();
updateAuditTrail();
// with lambda
runWithinTransaction((Transaction transaction) -> {
    // ...
});

if a method takes a functional interface as a parameter, then we can pass the following:

Chapter 2: Using Collections

Iterating through a List

very old way:

final List<String> friends = Arrays.asList("Brian", "Nate" ...);
for(int i = 0; i < friends.size(); i++) {
    System.out.println(friends.get(i));
}

old way:

for(String name : friends) {
    System.out.println(name);
}

using java 8 forEach:

friends.forEach(new Consumer<String>() {
    public void accept(final String name) {
        System.out.println(name);
    }
});

with lambda:

friends.forEach((final String name) -> System.out.println(name));

with type inference:

friends.forEach((name) -> System.out.println(name));

for single parameter, leave off the parentheses:

friends.forEach(name -> System.out.println(name));

with method reference: (final version)

friends.forEach(System.out::println);

some Stream examples:

// map
friends.stream()
    .map(name -> name.length())
    .forEach(count -> System.out.println(count + " "));
// method reference
friends.stream()
    .map(String::toUpperCase)
    .forEach(System.out::println);
// filter
final List<String> startsWithN =
    friends.stream()
        .filter(name -> name.startsWith("N"))
        .collect(Collectors.toList());

Lexical Scoping

final Function<String, Predicate<String>> startsWithLetter = (String letter) -> (String name) -> name.startsWith(letter);

final long countFriendsStartN =
    friends.stream()
        .filter(startsWithLetter("N"))
        .count();

Pick an element

public static void pickName(
    final List<String> names, final String startingLetter) {
    final Optional<String> foundName =
        names.stream()
        .filter(name -> name.startsWith(startingLetter))
        .findFirst();
    foundName.ifPresent(name -> System.out.println("hello " + name));
}

Reduce

final String steveOrLonger =
    friends.stream()
        .reduce("Steve", (name1, name2) ->
            name1.length() => name2.length() ? name1 : name2);

Join

System.out.println(
    friends.stream()
        .map(String::toUpperCase)
        .collect(joining(", "));
}

Chapter 3: Strings, Comparators, and Filters

Iterating a String

str.chars()
    .filter(ch -> Character.isDigit(ch))
    .forEach(ch -> System.out.println((char) ch));

Compare

people.stream()
    .sorted((person1, person2) -> person1.getName().compareTo(person2.getName()))
    .collect(toList());

Multiple Compare

final Function<Person, Integer> byAge = person -> person.getAge();
final Function<Person, String> byTheirName = person -> person.getName();

people.stream()
    .sorted(comparing(byAge)).thenComparing(byTheirName))
    .collect(toList());

Collect

the collect() method needs to know 3 things to gather results into a container:

  1. how to make a result container (ArrayList::new)
  2. how to add a single element (ArrayList::add)
  3. how to merge one result container into another (ArrayList::addAll)
    List<Person> olderThan20 =
        people.stream()
            .filter(person -> person.getAge() > 20)
            .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);

use Collectors utility class, just use toList()

// same as above
List<Person> olderThan20 =
    people.stream()
        .filter(person -> person.getAge() > 20)
        .collect(Collectors.toList());

more examples:

Map<Integer, List<Person>> peopleByAge =
    people.stream()
        .collect(Collectors.groupingBy(Person::getAge));
Map<Integer, List<String>> nameOfPeopleByAge =
    people.stream()
        .collect(
            groupingBy(Person::getAge, mapping(Person::getName, toList())));
Comparator<Person> byAge = Comparator.comparing(Person::getAge);
Map<Character, Optional<Person>> oldestPersonOfEachLetter = people.stream()
    .collect(groupingBy(person -> person.getName().charAt(0),
        reducing(BinaryOperator.maxBy(byAge))));

List files in a directory

Files.list(Paths.get("."))
    .filter(Files::isDirectory)
    .forEach(System.out::println);
Files.newDirectoryStream(Paths.get("/tmp"), path -> path.toString().endsWith(".java"))
    .forEach(System.out::println);
new File(".").listFiles(File::isHidden);

list immediate subdirectories

List<File> files =
    Stream.of(new File(".").listFiles())
        .flatMap(file -> file.listfiles() == null ?
            Stream.of(file) : Stream.of(file.listFiles()))
        .collect(toList());

Watching file change

final Path path = Paths.get(".");
final WatchService watchService = path.getFileSystem().newWatchService();
path.register(watchService, ENTRY_MODIFY);
final WatchKey watchKey = watchService.poll(1, TimeUnit.MINUTES);
if (watchKey != null) {
    watchKey.pollEvents()
        .stream()
        .forEach(event -> System.out.println(event.context()));
}

Chapter 4: Designing with Lambda Expressions

Separating Concerns Using Lambda Expressions
Delegating Using Lambda Expressions
Decorating Using Lambda Expressions
A Peek into the default Methods
Creating Fluent Interfaces Using Lambda Expressions
Dealing with Exceptions
Recap

Chapter 5: Working with Resources

Cleaning Up Resources
Using Lambda Expressions to Clean Up Resources
Managing Locks
Creating Concise Exception Tests
Recap

Chapter 6: Being Lazy

Delayed Initialization
Lazy Evaluations
Leveraging the Laziness of Streams
Creating Infinite, Lazy Collections
Recap

Chapter 7: Optimizing Recursions

Using Tail-Call Optimization
Speeding Up with Memoization
Recap

Chapter 8: Composing with Lambda Expressions

Using Function Composition
Using MapReduce
Taking a Leap to Parallelize
Recap

Chapter 9: Bringing It All Together

Essential Practices to Succeed with the Functional Style
Performance Concerns
Adopting the Functional Style