Functional Programming in Java
(notes from Functional Programming in Java: Harnessing the Power Of Java 8 Lambda Expressions)
- Chapter 1: Hello, Lambda Expressions!
- Chapter 2: Using Collections
- Chapter 3: Strings, Comparators, and Filters
- Chapter 4: Designing with Lambda Expressions
- Chapter 5: Working with Resources
- Chapter 6: Being Lazy
- Chapter 7: Optimizing Recursions
- Chapter 8: Composing with Lambda Expressions
- Chapter 9: Bringing It All Together
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:
- An anonymous inner class (the old way)
- A lambda expressions
- A method or constructor reference
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:
- how to make a result container (
ArrayList::new
) - how to add a single element (
ArrayList::add
) - 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