Reader small image

You're reading from  Java Coding Problems - Second Edition

Product typeBook
Published inMar 2024
PublisherPackt
ISBN-139781837633944
Edition2nd Edition
Right arrow
Author (1)
Anghel Leonard
Anghel Leonard
author image
Anghel Leonard

Anghel Leonard is a Chief Technology Strategist and independent consultant with 20+ years of experience in the Java ecosystem. In daily work, he is focused on architecting and developing Java distributed applications that empower robust architectures, clean code, and high-performance. Also passionate about coaching, mentoring and technical leadership. He is the author of several books, videos and dozens of articles related to Java technologies.
Read more about Anghel Leonard

Right arrow

Functional Style Programming – Extending APIs

This chapter includes 24 problems covering a wide range of functional programming topics. We will start by introducing the JDK 16 mapMulti() operation, and continue with a handful of problems for working with predicates (Predicate), functions, and collectors.

If you don’t have a background in functional programming in Java, then I strongly recommend you postpone this chapter until you have spent some time getting familiar with it. You could consider reading Chapters 8 and 9 from Java Coding Problems, First Edition.

At the end of this chapter, you’ll be deeply skilled in functional programming in Java.

Problems

Use the following problems to test your programming prowess in functional programming in Java. I strongly encourage you to give each problem a try before you turn to the solutions and download the example programs:

  1. Working with mapMulti(): Explain and exemplify the JDK 16 mapMulti(). Provide a brief introduction, explain how it works in comparison with flatMap(), and point out when mapMulti() is a good fit.
  2. Streaming custom code to map: Imagine a class that shapes some blog posts. Each post is identified by a unique integer ID, and the post has several properties, including its tags. The tags of each post are actually represented as a string of tags separated by a hashtag (#). Whenever we need the list of tags for a given post, we can call the allTags() helper method. Our goal is to write a stream pipeline that extracts from this list of tags a Map<String, List<Integer>> containing, for each tag (key), the list of posts (value).
  3. Exemplifying...

185. Working with mapMulti()

Starting with JDK 16, the Stream API was enriched with a new intermediate operation, named mapMulti(). This operation is represented by the following default method in the Stream interface:

default <R> Stream<R> mapMulti (
  BiConsumer<? super T, ? super Consumer<R>> mapper)

Let’s follow the learning-by-example approach and consider the next classical example, which uses a combination of filter() and map() to filter even integers and double their value:

List<Integer> integers = List.of(3, 2, 5, 6, 7, 8);
List<Integer> evenDoubledClassic = integers.stream()
  .filter(i -> i % 2 == 0)
  .map(i -> i * 2)
  .collect(toList());

The same result can be obtained via mapMulti() as follows:

List<Integer> evenDoubledMM = integers.stream()
  .<Integer>mapMulti((i, consumer) -> {
     if (i % 2 == 0) {
       consumer.accept(i * 2);
     }
  })
  .collect(toList());

So instead...

186. Streaming custom code to map

Let’s assume that we have the following legacy class:

public class Post {
    
  private final int id;
  private final String title;
  private final String tags;
  public Post(int id, String title, String tags) {
    this.id = id;
    this.title = title;
    this.tags = tags;
  }
  ...
  public static List<String> allTags(Post post) {
        
    return Arrays.asList(post.getTags().split("#"));
  }
}

So we have a class that shapes some blog posts. Each post has several properties, including its tags. The tags of each post are actually represented as a string of tags separated by hashtag (#). Whenever we need the list of tags for a given post, we can call the allTags() helper. For instance, here is a list of posts and their tags:

List<Post> posts = List.of(
  new Post(1, "Running jOOQ", "#database #sql #rdbms"),
  new Post(2, "I/O files in Java", "#io #storage #rdbms"...

187. Exemplifying a method reference vs. a lamda

Have you ever written a lambda expression and your IDE advises you to replace it with a method reference? You probably have! And I’m sure that you preferred to follow the replacement because names matter, and method references are often more readable than lambdas. While this is a subjective matter, I’m pretty sure you’ll agree that extracting long lambdas in methods and using/reusing them via method references is a generally accepted good practice.

However, beyond some esoteric JVM internal representations, do they behave the same? Is there any difference between a lambda and a method reference that may affect how the code behaves?

Well, let’s assume that we have the following simple class:

public class Printer {
        
  Printer() {
    System.out.println("Reset printer ...");
  }
       
  public static void printNoReset() {
    System.out.println(
      "Printing (no reset...

188. Hooking lambda laziness via Supplier/Consumer

The java.util.function.Supplier is a functional interface capable of supplying results via its get() method. The java.util.function.Consumer is another functional interface capable of consuming the argument given via its accept() method. It returns no result (void). Both of these functional interfaces are lazy, so it is not that easy to analyze and understand code that uses them, especially when a snippet of code uses both. Let’s give it a try!

Consider the following simple class:

static class Counter {
  static int c;
  public static int count() {
    System.out.println("Incrementing c from " 
      + c + " to " + (c + 1));
    return c++; 
  }
}

And let’s write the following Supplier and Consumer:

Supplier<Integer> supplier = () -> Counter.count();
Consumer<Integer> consumer = c -> {
  c = c + Counter.count(); 
  System.out.println("Consumer: " + c )...

189. Refactoring code to add lambda laziness

In this problem, let’s have a refactoring session designed to transform a dysfunctional code into a functional one. We start from the following given code – a simple piece of class mapping information about application dependencies:

public class ApplicationDependency {
    
  private final long id;
  private final String name;
  private String dependencies;
  public ApplicationDependency(long id, String name) {
    this.id = id;
    this.name = name;
  }
  public long getId() {
    return id;
  }
  public String getName() {
    return name;
  }   
    
  public String getDependencies() {
    return dependencies;
  }  
    
  private void downloadDependencies() {
          
    dependencies = "list of dependencies 
      downloaded from repository " + Math.random();
  }    
}

Why did we highlight the getDependencies() method? Because this is the point in the application where there is dysfunction. More precisely...

190. Writing a Function<String, T> for parsing data

Let’s assume that we have the following text:

String text = """
  test, a, 1, 4, 5, 0xf5, 0x5, 4.5d, 6, 5.6, 50000, 345, 
  4.0f, 6$3, 2$1.1, 5.5, 6.7, 8, a11, 3e+1, -11199, 55 
  """;

The goal is to find a solution that extracts from this text only the numbers. Depending on a given scenario, we may need only the integers, or only the doubles, and so on. Sometimes, we may need to perform some text replacements before extraction (for instance, we may want to replace the xf characters with a dot, 0xf5 = 0.5).

A possible solution to this problem is to write a method (let’s name it parseText()) that takes as an argument a Function<String, T>. The Function<String, T> gives us the flexibility to shape any of the following:

List<Integer> integerValues 
  = parseText(text, Integer::valueOf);
List<Double> doubleValues 
  = parseText(text, Double::valueOf...

191. Composing predicates in a Stream’s filters

A predicate (basically, a condition) can be modeled as a Boolean-valued function via the java.util.function.Predicate functional interface. Its functional method is named test(T t) and returns a boolean.

Applying predicates in a stream pipeline can be done via several stream intermediate operations, but we are interested here only in the filter(Predicate p) operation. For instance, let’s consider the following class:

public class Car {
  private final String brand;
  private final String fuel;
  private final int horsepower;
  public Car(String brand, String fuel, int horsepower) {
    this.brand = brand;
    this.fuel = fuel;
    this.horsepower = horsepower;
  }
  
  // getters, equals(), hashCode(), toString()
}

If we have a List<Car> and we want to express a filter that produces all the cars that are Chevrolets, then we can start by defining the proper Predicate:

Predicate<Car> pChevrolets...

192. Filtering nested collections with Streams

This is a classical problem in interviews that usually starts from a model, as follows (we assume that the collection is a List):

public class Author {
  private final String name;
  private final List<Book> books;
  ...
}
public class Book {
    
  private final String title;
  private final LocalDate published;
  ...
}

Having List<Author> denoted as authors, write a stream pipeline that returns the List<Book> published in 2002. You already should recognize this as a typical problem for flatMap(), so without further details, we can write this:

List<Book> book2002fm = authors.stream()
  .flatMap(author -> author.getBooks().stream())
  .filter(book -> book.getPublished().getYear() == 2002)
  .collect(Collectors.toList());

From Problem 185, we know that wherever flatMap() is useful, we should also consider the JDK 16’s mapMulti(). Before checking the following snippet of code, challenge...

193. Using BiPredicate

Let’s consider the Car model and a List<Car> denoted as cars:

public class Car {
  private final String brand;
  private final String fuel;
  private final int horsepower;
  ...
}

Our goal is to see if the following Car is contained in cars:

Car car = new Car("Ford", "electric", 80);

We know that the List API exposes a method named contains(Object o). This method returns true if the given Object is present in the given List. So, we can easily write a Predicate, as follows:

Predicate<Car> predicate = cars::contains;

Next, we call the test() method, and we should get the expected result:

System.out.println(predicate.test(car)); // true

We can obtain the same result in a stream pipeline via filter(), anyMatch(), and so on. Here is via anyMatch():

System.out.println(
  cars.stream().anyMatch(p -> p.equals(car))
);

Alternatively, we can rely on BiPredicate. This is a functional...

194. Building a dynamic predicate for a custom model

Let’s consider the Car model and a List<Car> denoted as cars:

public class Car {
  private final String brand;
  private final String fuel;
  private final int horsepower;
  ...
}

Also, let’s assume that we need to dynamically produce a wide range of predicates that apply the operators <, >, <=, >=, !=, and == to the horsepower field. It will be cumbersome to hardcode such predicates, so we have to come up with a solution that can build, on the fly, any predicate that involves this field, and one of the comparison operators listed here.

There are a few approaches to accomplish this goal, and one of them is to use a Java enum. We have a fixed list of operators that can be coded as enum elements, as follows:

enum PredicateBuilder {
  GT((t, u) -> t > u),
  LT((t, u) -> t < u),
  GE((t, u) -> t >= u),
  LE((t, u) -> t <= u),
  EQ((t, u) -> t.intValue() == u...

195. Building a dynamic predicate from a custom map of conditions

Let’s consider the Car model and a List<Car> denoted as cars:

public class Car {
  private final String brand;
  private final String fuel;
  private final int horsepower;
  ...
}

Also, let’s assume that we receive a Map of conditions of type field : value, which could be used to build a dynamic Predicate. An example of such a Map is listed here:

Map<String, String> filtersMap = Map.of(
  "brand", "Chevrolet",
  "fuel", "diesel"
);

As you can see, we have a Map<String, String>, so we are interested in an equals() comparison. This is useful to start our development via the following Java enum (we follow the logic from Problem 194):

enum PredicateBuilder {
  EQUALS(String::equals);
  ...

Of course, we can add more operators, such as startsWith(), endsWith(), contains(), and so on. Next, based on the experience gained in...

196. Logging in predicates

We already know that the Predicate functional interface relies on its test() method to perform the given check, and it returns a Boolean value. Let’s suppose that we want to alter the test() method to log the failure cases (the cases that lead to the return of a false value).

A quick approach is to write a helper method that sneaks the logging part, as follows:

public final class Predicates {
  private static final Logger logger 
    = LoggerFactory.getLogger(LogPredicate.class);
  private Predicates() {
    throw new AssertionError("Cannot be instantiated");
  }
  public static <T> Predicate<T> testAndLog(
      Predicate<? super T> predicate, String val) {
    return t -> {
      boolean result = predicate.test(t);
      if (!result) {
        logger.warn(predicate + " don't match '" + val + "'");
      }
      return result;
    };
  }
}

Another approach consists of...

197. Extending Stream with containsAll() and containsAny()

Let’s assume that we have the following code:

List<Car> cars = Arrays.asList(
  new Car("Dacia", "diesel", 100),
  new Car("Lexus", "gasoline", 300), 
  ...
  new Car("Ford", "electric", 200)
);
       
Car car1 = new Car("Lexus", "diesel", 300);
Car car2 = new Car("Ford", "electric", 80);
Car car3 = new Car("Chevrolet", "electric", 150);
List<Car> cars123 = List.of(car1, car2, car3);

Next, in the context of a stream pipeline, we want to check if cars contains all/any of car1, car2, car3, or cars123.

The Stream API comes with a rich set of intermediate and final operations, but it doesn’t have a built-in containsAll()/containsAny(). So, it is our mission to provide the following final operations:

boolean contains(T item);
boolean containsAll(T... items);
boolean...

198. Extending Stream with removeAll() and retainAll()

Before reading this problem, I strongly recommend that you read Problem 197.

In Problem 197, we extended the Stream API with two final operations named containsAll() and containsAny() via a custom interface. In both cases, the resulting interface was named Streams. In this problem, we follow the same logic to implement two intermediate operations, named removeAll() and retainAll(), with the following signatures:

Streams<T> remove(T item);
Streams<T> removeAll(T... items);
Streams<T> removeAll(List<? extends T> items);
Streams<T> removeAll(Stream<? extends T> items);
Streams<T> retainAll(T... items);
Streams<T> retainAll(List<? extends T> items);
Streams<T> retainAll(Stream<? extends T> items);

Since removeAll() and retainAll() are intermediate operations, they have to return Stream. More precisely, they have to return Streams, which is our implementation...

199. Introducing stream comparators

Let’s assume that we have the following three lists (a list of numbers, a list of strings, and a list of Car objects):

List<Integer> nrs = new ArrayList<>();
List<String> strs = new ArrayList<>();
List<Car> cars = List.of(...);
public class Car {
  private final String brand;
  private final String fuel;
  private final int horsepower;
  ...
}

Next, we want to sort these lists in a stream pipeline.

Sorting via natural order

Sorting via natural order is very simple. All we have to do is to call the built-in intermediate operation, sorted():

nrs.stream()
   .sorted()
   .forEach(System.out::println);
strs.stream()
    .sorted()
    .forEach(System.out::println);

If nrs contains 1, 6, 3, 8, 2, 3, and 0, then sorted() will produce 0, 1, 2, 3, 3, 6, and 8. So, for numbers, the natural order is the ascending order by value.

If strs contains “book,” “old,” ...

200. Sorting a map

Let’s assume that we have the following map:

public class Car {
  private final String brand;
  private final String fuel;
  private final int horsepower;
  ...
}
Map<Integer, Car> cars = Map.of(
  1, new Car("Dacia", "diesel", 350),
  2, new Car("Lexus", "gasoline", 350),
  3, new Car("Chevrolet", "electric", 150),
  4, new Car("Mercedes", "gasoline", 150),
  5, new Car("Chevrolet", "diesel", 250),
  6, new Car("Ford", "electric", 80),
  7, new Car("Chevrolet", "diesel", 450),
  8, new Car("Mercedes", "electric", 200),
  9, new Car("Chevrolet", "gasoline", 350),
  10, new Car("Lexus", "diesel", 300)
);

Next, we want to sort this map into a List<String>, as follows:

  • If the horsepower values are different, then sort in descending...

201. Filtering a map

Let’s consider the following map:

public class Car {
  private final String brand;
  private final String fuel;
  private final int horsepower;
  ...
}
Map<Integer, Car> cars = Map.of(
  1, new Car("Dacia", "diesel", 100),
  ...
  10, new Car("Lexus", "diesel", 300)
);

In order to stream a map, we can start from the entrySet() of the Map, values(), or keyset(), followed by a stream() call. For instance, if we want to express a pipeline as Map -> Stream -> Filter -> String that returns a List<String> containing all the electric brands, then we can rely on entrySet() as follows:

String electricBrands = cars.entrySet().stream()
  .filter(c -> "electric".equals(c.getValue().getFuel()))
  .map(c -> c.getValue().getBrand())
  .collect(Collectors.joining(", "));

However, as you can see, this stream pipeline doesn’t use the map’s keys. This means...

202. Creating a custom collector via Collector.of()

Creating a custom collector is a topic that we covered in detail in Chapter 9, Problem 193, of Java Coding Problem, First Edition. More precisely, in that problem, you saw how to write a custom collector by implementing the java.util.stream.Collector interface.

Don’t worry if you haven’t read that book/problem; you can still follow this problem. First, we will create several custom collectors. This time, we will rely on two Collector.of() methods that have the following signatures:

static <T,R> Collector<T,R,R> of(
  Supplier<R> supplier, 
  BiConsumer<R,T> accumulator, 
  BinaryOperator<R> combiner, 
  Collector.Characteristics... characteristics)
static <T,A,R> Collector<T,A,R> of(
  Supplier<A> supplier, 
  BiConsumer<A,T> accumulator, 
  BinaryOperator<A> combiner, 
  Function<A,R> finisher, 
  Collector.Characteristics... characteristics...

203. Throwing checked exceptions from lambdas

Let’s suppose that we have the following lambda:

static void readFiles(List<Path> paths) {
  paths.forEach(p -> {
    try {
      readFile(p);
    } catch (IOException e) {
      ... // what can we throw here?
    }
  });
}

What can we throw in the catch block? Most of you will know the answer; we can throw an unchecked exception such as a RuntimeException:

static void readFiles(List<Path> paths) {
  paths.forEach(p -> {
    try {
      readFile(p);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  });
}

Also, most people know that we cannot throw a checked exception such as an IOException. The following snippet of code will not compile:

static void readFiles(List<Path> paths) {
  paths.forEach(p -> {
    try {
      readFile(p);
    } catch (IOException e) {
      throw new IOException(e);
    }
  });
}

Can we change this rule? Can we come up with a...

204. Implementing distinctBy() for the Stream API

Let’s suppose that we have the following model and data:

public class Car {
  private final String brand;
  private final String fuel;
  private final int horsepower;
  ...
}
List<Car> cars = List.of(
  new Car("Chevrolet", "diesel", 350),
  ...
  new Car("Lexus", "diesel", 300)
);

We know that the Stream API contains the distinct() intermediate operation, which is capable of keeping only the distinct elements based on the equals() method:

cars.stream()
    .distinct()
    .forEach(System.out::println);

While this code prints the distinct cars, we may want a distinctBy() intermediate operation that is capable of keeping only the distinct elements based on a given property/key. For instance, we may need all the cars distinct by brand. For this, we can rely on the toMap() collector and the identity function, as follows:

cars.stream()
    .collect(Collectors.toMap...

205. Writing a custom collector that takes/skips a given number of elements

In Problem 202, we wrote a handful of custom collectors grouped in the MyCollectors class. Now, let’s continue our journey, and let’s try to add two more custom collectors here to take and/or keep a given number of elements from the current stream.

Let’s assume the following model and data:

public class Car {
  private final String brand;
  private final String fuel;
  private final int horsepower;
  ...
}
List<Car> cars = List.of(
  new Car("Chevrolet", "diesel", 350),
  ... // 10 more
  new Car("Lexus", "diesel", 300)
);

The Stream API provides an intermediate operation named limit(long n), which can be used to truncate the stream to n elements. So, if this is exactly what we want, then we can use it out of the box. For instance, we can limit the resulting stream to the first five cars, as follows:

List<Car> first5CarsLimit...

206. Implementing a Function that takes five (or any other arbitrary number of) arguments

We know that Java already has java.util.function.Function and the specialization of it, java.util.function.BiFunction. The Function interface defines the method apply(T, t), while BiFunction has apply(T t, U u).

In this context, we can define a TriFunction, FourFunction, or (why not?) a FiveFunction functional interface, as follows (all of these are specializations of Function):

@FunctionalInterface
public interface FiveFunction <T1, T2, T3, T4, T5, R> {
    
  R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5);
}

As its name suggests, this functional interface takes five arguments.

Now, let’s use it! Let’s assume that we have the following model:

public class PL4 {
    
  private final double a;
  private final double b;
  private final double c;
  private final double d;
  private final double x;
  public PL4(double a, double b, 
             double c, double...

207. Implementing a Consumer that takes five (or any other arbitrary number of) arguments

Before continuing with this problem, I strongly recommend that you read Problem 206.

Writing a custom Consumer that takes five arguments can be done as follows:

@FunctionalInterface
public interface FiveConsumer <T1, T2, T3, T4, T5> {
    
  void accept (T1 t1, T2 t2, T3 t3, T4 t4, T5 t5);
}

This is the five-arity specialization of the Java Consumer, just as the built-in BiConsumer is the two-arity specialization of the Java Consumer.

We can use FiveConsumer in conjunction with the PL4 formula, as follows (here, we compute y for x = 40.3):

FiveConsumer<Double, Double, Double, Double, Double> 
  pl4c = (a, b, c, d, x) -> Logistics.pl4(a, b, c, d, x);
        
pl4c.accept(4.19, -1.10, 12.65, 0.03, 40.3);  

The Logistics.pl4() is the method that contains the formula and displays the result:

public static void pl4(Double a, Double b, 
                ...

208. Partially applying a Function

A Function that is partially applied is a Function that applies only a part of its arguments, returning another Function. For instance, here is a TriFunction (a functional function with three arguments) that contains the apply() method, next to two default methods that partially apply this function:

@FunctionalInterface
public interface TriFunction <T1, T2, T3, R> {
    
  R apply(T1 t1, T2 t2, T3 t3);
   
  default BiFunction<T2, T3, R> applyOnly(T1 t1) {
    return (t2, t3) -> apply(t1, t2, t3);
  }
    
  default Function<T3, R> applyOnly(T1 t1, T2 t2) {
    return (t3) -> apply(t1, t2, t3);
  }
}

As you can see, applyOnly(T1 t1) applies only the t1 argument and returns a BiFunction. On the other hand, applyOnly(T1 t1, T2 t2) applies only t1 and t2, returning a Function.

Let’s see how we can use these methods. For instance, let’s consider the formula (a+b+c)2 = a2+b2+c2+2ab+2bc+2ca, which can...

Summary

This chapter covered 24 problems. Most of them focused on working with predicates, functions, and collectors, but we also covered the JDK 16 mapMulti() operation, refactoring imperative code to functional code, and much more.

Join our community on Discord

Join our community’s Discord space for discussions with the author and other readers:

https://discord.gg/8mgytp5DGQ

lock icon
The rest of the chapter is locked
You have been reading a chapter from
Java Coding Problems - Second Edition
Published in: Mar 2024Publisher: PacktISBN-13: 9781837633944
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Author (1)

author image
Anghel Leonard

Anghel Leonard is a Chief Technology Strategist and independent consultant with 20+ years of experience in the Java ecosystem. In daily work, he is focused on architecting and developing Java distributed applications that empower robust architectures, clean code, and high-performance. Also passionate about coaching, mentoring and technical leadership. He is the author of several books, videos and dozens of articles related to Java technologies.
Read more about Anghel Leonard