
dtonhofer
Functional Programming in Java, Second Edition: All the code changes for Chapter 4 in two files
Again, some code suggestions for “Chapter 4”, similar to “Chapter 3”
(one would wish to have better markdown syntax highlighting to make the below more readable)
Person.java
package chapter4;
import java.util.List;
import java.util.Objects;
// ---
// The "Person" of "transforming/fpij/Person.java"
// on page 68 of "Functional Programming in Java".
// Basically no changes except a checking initializer.
// ---
public record Person(String firstName, String lastName, List<String> emailAddresses) {
public Person {
Objects.requireNonNull(firstName);
Objects.requireNonNull(lastName);
Objects.requireNonNull(emailAddresses);
}
public String fullName() {
return firstName + " " + lastName;
}
public static List<Person> getPeople() {
return List.of(
new Person("John", "Doe", List.of()),
new Person("Sara", "Walker", List.of("sara@example.com")),
new Person("Mike", "Baker",
List.of("mike@example.com", "baker@example.com")),
new Person("Dev", "Shah",
List.of("dev@example.com", "shah@example.com")),
new Person("Sara", "Lee",
List.of("slee@example.org", "lee@example.com")),
new Person("Nancy", "Xie",
List.of("nancy@example.com", "xie@example.com", "nx@example.com")),
new Person("Jill", "Smith", List.of("jill@example.com"))
);
}
}
TransformingData.java
package chapter4;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.DoubleSummaryStatistics;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.*;
// ---
// Various code based on Chapter 4 of "Functional Programming in Java"
// Instead of having multiple files and multiple main() we code it into a JUnit test case.
// ---
// Used in printing: A String and a Number
record PairN(String str, Number num) {
public String toString() {
return str + ": " + num + " (" + num.getClass().getSimpleName() + ")";
}
public static String listToString(List<PairN> pairs) {
return pairs.stream()
.map(PairN::toString)
.collect(Collectors.joining("\n"));
}
}
// Used in printing: A String and a Boolean
record PairB(String str, Boolean b) {
public String toString() {
return str + ": " + b;
}
public static String listToString(List<PairB> pairs) {
return pairs.stream()
.map(PairB::toString)
.collect(Collectors.joining("\n"));
}
}
public class TransformingData {
private static <T> String toStringListOfStuff(final List<T> list) {
// can even call this statically!
return list.stream().map(T::toString).collect(Collectors.joining("\n"));
}
// Originally "transforming/fpij/AverageNumberOfEmailAddresses.java" on page 69,
// which was given using its own class.
// Separated computation from the printing.
@Test
void averageNumberOfEmailAddresses() {
final var people = Person.getPeople();
final double res = people.stream()
.collect(averagingDouble(person -> person.emailAddresses().size()));
System.out.println("Average number of email addresses: " + res);
}
// Originally "transforming/fpij/Statistics.java" on page 70,
// which was given using its own class.
// Separated computation from the printing.
@Test
void statistics() {
final var people = Person.getPeople();
// https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/DoubleSummaryStatistics.html
final DoubleSummaryStatistics statistics =
people.stream()
.collect(
summarizingDouble(person -> person.emailAddresses().size()));
final var pairs = new ArrayList<PairN>();
pairs.add(new PairN("Number of people", statistics.getCount()));
pairs.add(new PairN("Number of email addresses", statistics.getSum()));
pairs.add(new PairN("Average number of email addresses", statistics.getAverage()));
pairs.add(new PairN("Max number of email addresses", statistics.getMax()));
pairs.add(new PairN("Min number of email addresses", statistics.getMin()));
System.out.println(PairN.listToString(pairs));
}
// Code on page 73
@Test
void flattenEmailAddresses() {
final var people = Person.getPeople();
final List<String> emails = people.stream()
.flatMap(person -> person.emailAddresses().stream())
.toList();
// System.out.println(emails);
System.out.println(toStringListOfStuff(emails));
}
// Code on page 74 but the criterium size() > 0 has been replaced by .isEmpty().
// Separated computation from the printing.
@Test
void checkingForCriteriaAny() {
final var people = Person.getPeople();
final var pairs = new ArrayList<PairB>();
pairs.add(new PairB(
"Anyone has email address",
people.stream().anyMatch(person -> !person.emailAddresses().isEmpty())));
pairs.add(new PairB(
"Anyone has more than 10 email address",
people.stream().anyMatch(person -> person.emailAddresses().size() > 10)));
System.out.println(PairB.listToString(pairs));
}
// Code on page 74 but the criterium size() > 0 has been replaced by .isEmpty().
// The criterium >= 0 should probably be > 1 for interestingness.
// Separated computation from the printing.
@Test
void checkingForCriteriaAll() {
final var people = Person.getPeople();
final var pairs = new ArrayList<PairB>();
// modified from the book code which uses allMatch() in all cases!
pairs.add(new PairB(
"Everyone has at least one email address (1)",
people.stream().noneMatch(person -> person.emailAddresses().isEmpty())));
pairs.add(new PairB(
"Everyone has at least one email address (2)",
people.stream().allMatch(person -> person.emailAddresses().size() > 1)));
// IDE warns: Condition 'person.emailAddresses().size() >= 0' is always 'true'
pairs.add(new PairB("Everyone has zero or more email address (always true)",
people.stream().allMatch(person -> person.emailAddresses().size() >= 0)));
System.out.println(PairB.listToString(pairs));
}
// Code on page 75
// Separated computation from the printing.
@Test
void partitioningByBoolean() {
final var people = Person.getPeople();
final Map<Boolean, List<Person>> partitions =
people.stream()
.collect(partitioningBy(person -> person.emailAddresses().size() > 1));
final var pairs = new ArrayList<PairN>();
pairs.add(new PairN("Number of people with at most one email address", partitions.get(false).size()));
pairs.add(new PairN("Number of people with multiple email addresses", partitions.get(true).size()));
System.out.println(PairN.listToString(pairs));
}
// Code on page 76 bottom, 77 top.
// Separated computation from the printing.
@Test
void countingOccurrencesWithLongResult() {
final var people = Person.getPeople();
final Map<String, Long> namesCount = people.stream()
.collect(
groupingBy(
Person::firstName,
counting()));
final List<PairN> pairs = mapToPairs(namesCount);
System.out.println(PairN.listToString(pairs));
}
// Code on page 77 middle.
// Separated computation from the printing.
@Test
void countingOccurrencesWithIntResult() {
final var people = Person.getPeople();
final Map<String, Integer> namesCount = people.stream()
.collect(
groupingBy(
Person::firstName,
collectingAndThen(counting(), Long::intValue)));
final List<PairN> pairs = mapToPairs(namesCount);
System.out.println(PairN.listToString(pairs));
}
// Helper for above
private static List<PairN> mapToPairs(final Map<String, ? extends Number> map) {
return map.entrySet().stream().map(entry -> new PairN(entry.getKey(), entry.getValue())).collect(toList());
}
// Code on page 78 top
@Test
void summingCountingNumberOfEmailAddressesByLastName() {
final var people = Person.getPeople();
final Map<String, Integer> namesAndEmailAddressesCount =
people.stream()
.collect(
groupingBy(
Person::lastName,
summingInt(person -> person.emailAddresses().size())));
final List<PairN> pairs = mapToPairs(namesAndEmailAddressesCount);
System.out.println(PairN.listToString(pairs));
}
// Code on page 79 top.
// The ".com" criterium has been moved to a name Predicate<> for legibility.
// The result's type Map<String, List<String>> is given for clarity.
// Separated computation from the printing, with dedicated formatting.
@Test
void filteringEmailAddressesEndingInDotComByLastName() {
final var people = Person.getPeople();
final Predicate<String> endsWithDotCom = (String s) -> s.endsWith(".com");
final Map<String, List<String>> lastNamesAndEmailAddressesFiltered =
people.stream()
.collect(
groupingBy(
Person::lastName,
flatMapping(
(person -> person.emailAddresses().stream()),
filtering(
endsWithDotCom,
toList()))));
var txt = lastNamesAndEmailAddressesFiltered.entrySet().stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("\n", "{\n", "\n}"));
System.out.println(txt);
// System.out.println(lastNamesAndEmailAddressesFiltered);
}
// Code on page 80 bottom.
// The common function in maxBy() and minBy() has been moved
// for legibility and suppression of repetition.
// The result's type Map<String, String> is given for clarity.
@Test
void teeingOperations() {
final var people = Person.getPeople();
final Function<Person, Integer> emailCountOfPerson = (Person person) -> person.emailAddresses().size();
final Map<String, String> leastAndMostEmailAddressPerson =
people.stream()
.collect(
teeing(
minBy(comparing(emailCountOfPerson)),
maxBy(comparing(emailCountOfPerson)),
(min, max) ->
Map.of("least", min.map(Person::fullName).orElse(""),
"most", max.map(Person::fullName).orElse(""))));
System.out.println(leastAndMostEmailAddressPerson);
}
}
Popular Pragmatic Bookshelf topics

Some minor things in the paper edition that says “3 2020” on the title page verso, not mentioned in the book’s errata online:
p. 186 But...
New

Hi Brian,
Looks like the api for tinydb has changed a little. Noticed while working on chapter 7 that the .purge() call to the db throws...
New

Title: Design and Build Great Web APIs - typo “https://company-atk.herokuapp.com/2258ie4t68jv” (page 19, third bullet in URL list)
Typo:...
New

Hi! I know not the intentions behind this narrative when called, on page XI:
mount() |> handle_event() |> render()
but the correc...
New

I ran this command after installing the sample application:
$ cards add do something --owner Brian
And got a file not found error:
Fil...
New

Hi, I have just acquired Michael Fazio’s “Kotlin and Android Development” to learn about game programming for Android. I have a game in p...
New

The markup used to display the uploaded image results in a Phoenix.LiveView.HTMLTokenizer.ParseError error.
lib/pento_web/live/product_l...
New

Skimming ahead, much of the following is explained in Chapter 3, but new readers (like me!) will hit a roadblock in Chapter 2 with their ...
New

@mfazio23
I’ve applied the changes from Chapter 5 of the book and everything builds correctly and runs. But, when I try to start a game,...
New

Getting an error when installing the dependencies at the start of this chapter:
could not compile dependency :exla, "mix compile" failed...
New
Other popular topics

Reading something? Working on something? Planning something? Changing jobs even!?
If you’re up for sharing, please let us know what you’...
New

What chair do you have while working… and why?
Is there a ‘best’ type of chair or working position for developers?
New

I’m thinking of buying a monitor that I can rotate to use as a vertical monitor?
Also, I want to know if someone is using it for program...
New

Curious to know which languages and frameworks you’re all thinking about learning next :upside_down_face:
Perhaps if there’s enough peop...
New

Thanks to @foxtrottwist’s and @Tomas’s posts in this thread: Poll: Which code editor do you use? I bought Onivim! :nerd_face:
https://on...
New

Learn different ways of writing concurrent code in Elixir and increase your application's performance, without sacrificing scalability or...
New

Think Again 50% Off Sale »
The theme of this sale is new perspectives on familiar topics.
Enter coupon code ThinkAgain2021 at checkout t...
New

A few weeks ago I started using Warp a terminal written in rust. Though in it’s current state of development there are a few caveats (tab...
New

Author Spotlight
Jamis Buck
@jamis
This month, we have the pleasure of spotlighting author Jamis Buck, who has written Mazes for Prog...
New

If you’re getting errors like this:
psql: error: connection to server on socket “/tmp/.s.PGSQL.5432” failed: No such file or directory ...
New
Categories:
Sub Categories:
Popular Portals
- /elixir
- /rust
- /ruby
- /wasm
- /erlang
- /phoenix
- /keyboards
- /rails
- /python
- /js
- /security
- /go
- /swift
- /vim
- /clojure
- /haskell
- /emacs
- /java
- /svelte
- /onivim
- /typescript
- /kotlin
- /crystal
- /c-plus-plus
- /tailwind
- /react
- /gleam
- /ocaml
- /flutter
- /elm
- /vscode
- /ash
- /html
- /opensuse
- /centos
- /php
- /deepseek
- /zig
- /scala
- /lisp
- /textmate
- /sublime-text
- /react-native
- /nixos
- /debian
- /agda
- /kubuntu
- /arch-linux
- /django
- /revery
- /ubuntu
- /spring
- /manjaro
- /nodejs
- /diversity
- /lua
- /deno
- /julia
- /slackware
- /c