Functional Interfaces in Java PDF
Functional Interfaces in Java PDF
Interfaces
in Java
Fundamentals and Examples
—
Ralph Lecessi
Functional Interfaces
in Java
Fundamentals and Examples
Ralph Lecessi
Functional Interfaces in Java: Fundamentals and Examples
Ralph Lecessi
Kendall Park, NJ, USA
iii
Table of Contents
Chapter 3: Predicates��������������������������������������������������������������������������������������������� 45
Section 3.1: The java.util.function Package�������������������������������������������������������������������������������� 45
Section 3.2: The Predicate Interface������������������������������������������������������������������������������������������� 45
Section 3.3: Passing a Predicate to a Method����������������������������������������������������������������������������� 47
Section 3.4: Chains of Functional Interfaces������������������������������������������������������������������������������� 50
Section 3.5: Predicate Chaining Creates Complex Logical Expressions������������������������������������� 50
Section 3.5.1: Chains Involving the OR Operation����������������������������������������������������������������� 50
Section 3.5.2: Chains Involving the AND Operation��������������������������������������������������������������� 52
Section 3.5.3: Chains Involving the ! Operation��������������������������������������������������������������������� 53
Section 3.5.4: Using Predicate.isEqual���������������������������������������������������������������������������������� 54
Section 3.5.5: Using Predicate.not [JAVA 11]������������������������������������������������������������������������ 55
Section 3.6: Overriding Predicate Default Methods�������������������������������������������������������������������� 56
Section 3.7: Specializations of Predicates���������������������������������������������������������������������������������� 57
Section 3.8: Binary Predicates���������������������������������������������������������������������������������������������������� 59
PROJECT 3: Discount Dave���������������������������������������������������������������������������������������������������������� 59
Problem Statement���������������������������������������������������������������������������������������������������������������� 59
Solution��������������������������������������������������������������������������������������������������������������������������������� 60
Short Problems��������������������������������������������������������������������������������������������������������������������������� 67
Long Problems���������������������������������������������������������������������������������������������������������������������������� 67
Chapter 4: Functions���������������������������������������������������������������������������������������������� 69
Section 4.1: The Function Interface�������������������������������������������������������������������������������������������� 69
Section 4.2: Passing a Generic Function to a Method����������������������������������������������������������������� 70
Section 4.2.1: Passing a Function with Restricted or Known Type Parameters�������������������� 71
iv
Table of Contents
Chapter 5: Operators���������������������������������������������������������������������������������������������� 95
Section 5.1: The UnaryOperator Interface����������������������������������������������������������������������������������� 95
Section 5.2: Specializations of UnaryOperator���������������������������������������������������������������������������� 96
Section 5.2.1: Chains Involving UnaryOperator Specializations�������������������������������������������� 97
Section 5.3: The BinaryOperator Interface���������������������������������������������������������������������������������� 98
Section 5.4: Non-generic Specializations of BinaryOperator����������������������������������������������������� 99
PROJECT 5: Calculator�������������������������������������������������������������������������������������������������������������� 100
Problem Statement�������������������������������������������������������������������������������������������������������������� 100
Solution������������������������������������������������������������������������������������������������������������������������������� 100
Short Problems������������������������������������������������������������������������������������������������������������������������� 106
Long Problems�������������������������������������������������������������������������������������������������������������������������� 107
v
Table of Contents
Section 6.3: Using Consumers with println as the Terminal Operation������������������������������������� 112
Section 6.4: Non-generic Specializations of Consumers���������������������������������������������������������� 112
Section 6.5: The BiConsumer Interface������������������������������������������������������������������������������������� 114
Section 6.6: Specializations of BiConsumer������������������������������������������������������������������������������ 115
PROJECT 6: Bank Transactions������������������������������������������������������������������������������������������������� 116
Problem Statement�������������������������������������������������������������������������������������������������������������� 116
Solution������������������������������������������������������������������������������������������������������������������������������� 117
Short Problems������������������������������������������������������������������������������������������������������������������������� 128
Long Problems�������������������������������������������������������������������������������������������������������������������������� 129
vi
Table of Contents
vii
Table of Contents
viii
Table of Contents
ix
Table of Contents
x
Table of Contents
Index��������������������������������������������������������������������������������������������������������������������� 405
xi
About the Author
Ralph Lecessi is a software engineer with over 30 years’
professional programming experience in the aerospace,
telecommunications, and payment industries at companies
including Lockheed Martin, Alcatel-Lucent, AT&T, and
Northrop Grumman. He is currently lead embedded
software developer at TranSendIT, Inc in Mount Laurel,
New Jersey.
Ralph is also the author of JAVATM - The Beginnings—a
text on basic Java programming that includes many examples and diagrams.
Ralph is an adjunct professor of programming at Middlesex County College, where
he teaches basic and object-oriented programming in Java. He lives in South Brunswick,
New Jersey, with his wife and two children.
xiii
About the Technical Reviewer
Manuel Jordan Elera is an autodidactic developer and
researcher who enjoys learning new technologies for his
own experiments and creating new integrations. Manuel
won the 2010 Springy Award Community Champion and
Spring Champion 2013. In his little free time, he reads
the Bible and composes music on his guitar. Manuel is
known as dr_pompeii. He has tech-reviewed numerous
books for Apress, including Pro Spring, Fourth Edition
(2014); Practical Spring LDAP (2013); Pro JPA 2, Second
Edition (2013); and Pro Spring Security (2013). Read his
13 detailed tutorials about many Spring technologies, contact him through his blog at
www.manueljordanelera.blogspot.com, and follow him on his Twitter account,
@dr_pompeii.
xv
Foreword
Functional Interfaces in Java shows how to organize and simplify Java programs through
the use of functional interfaces.
Chapters 1 and 2 introduce functional interfaces and discuss their implementation
using lambda expressions.
Chapters 3 through 7 discuss the functional interfaces present in the java.util.
function package.
Chapters 8 through 15 show how to use functional interfaces to perform various
programming tasks.
The appendix discusses method references and how they can be used in the
implementation of functional interfaces.
The text presents each topic with detailed descriptions and many examples. Each
chapter also contains a project through which I will guide you step by step to the
solution. Each chapter also contains short homework problems which reinforce the
subject matter and longer assignments which utilize the current topic to solve a real-
world problem.
Knowledge of basic Java programming is needed to understand the examples in this
text. If you need a primer on basic Java, I recommend JAVATM- The Beginnings.
Here are some notes regarding the programming style used in this book:
All examples have been tested in Java 9, Java 10, and Java 11 unless otherwise noted.
I hope you enjoy reading Functional Interfaces in Java as much as I have enjoyed
writing it.
—Ralph Lecessi
xvii
CHAPTER 1
Functional Interfaces
Section 1.1: Interfaces in Java
In Java, an interface specifies one or more methods. The interface is a contract which
must be honored by all implementing classes. The interface defined in Listing 1-1
specifies methods method1 and method2.
interface I1
{
void method1();
String method2(String x);
}
Any class that implements an interface must provide implementations for all the
methods declared in the interface (or the class must be declared as abstract). Since the
class defined in Listing 1-2 provides implementations for both method1 and method2,
objects of class C1 can be instantiated.
class C1 implements I1
{
@Override
public void method1() {}
@Override
public String method2(String x) { return x; }
}
1
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0_1
Chapter 1 Functional Interfaces
interface I2
{
String s = "I2";
static void method1()
{
System.out.println(s);
}
2
Chapter 1 Functional Interfaces
class C2 implements I2
{
@Override
public String method2(String x) { return x; }
}
class C3 implements I2 {}
class TestI2
{
public static void main(String[] args)
{
I2.method1();
I2 objC2 = new C2();
I2 objC3 = new C3();
System.out.println(objC2.method2("Hello"));
System.out.println(objC3.method2("World"));
}
}
PROGRAM OUTPUT:
I2
Hello
I2World
In Java 9, interfaces were allowed to have private methods. Private methods are
useful to call from default methods. The program in Listing 1-4 concatenates a random
integer between 0 and 99 to the string “Hello”. Interface I3 contains private method
getNumber which generates the integer. This method is called by default method M1.
import java.util.Random;
interface I3
{
private int getNumber() { return (new Random()).nextInt(100); }
default String M1(String s)
3
Chapter 1 Functional Interfaces
{
return s + getNumber();
}
}
class C4 implements I3
{
}
class TestI3
{
public static void main(String[] args)
{
I3 objC4 = new C4();
System.out.println(objC4.M1("Hello"));
}
}
PROGRAM OUTPUT:
Hello21
In Java 9, interfaces can also have private static methods. Since the static methods of
an interface can be called without creation of an implementing object, these methods
can only be called by public static methods defined in the interface.
Interface I4 in the following program defines public static method getName which
calls private static method getPrefix to add the prefix “Mr. ” or “Ms. ” based on gender.
The getName method can be called from the main method of class TestI4. The getPrefix
method can be called only from inside method getName.
interface I4
{
private static String getPrefix(String p)
{
return p.equals("male")? "Mr. " : "Ms. ";
}
public static String getName(String n, String p)
4
Chapter 1 Functional Interfaces
{
return getPrefix(p) + n;
}
}
class TestI4
{
public static void main(String[] args)
{
System.out.println(I4.getName("Smith", "female"));
System.out.println(I4.getName("Jones", "male"));
}
}
PROGRAM OUTPUT:
Ms. Smith
Mr. Jones
5
Chapter 1 Functional Interfaces
Functional interfaces are ideal for defining a single problem or operation. In Java 8,
the API was enhanced to utilize functional interfaces. Many of the functional interfaces
can contain static and default methods, making them extendable by the user.
6
Chapter 1 Functional Interfaces
The program in Listing 1-9 provides both named class and anonymous class
implementations for functional interface StringProcessor.
PROGRAM OUTPUT:
Hello
HELLO
7
Chapter 1 Functional Interfaces
class Receipt
{
String item;
double price;
double discount;
double tax;
public Receipt(String i, double a, double d, double s)
{
item = i;
price = a;
discount = d;
tax = s;
}
public Receipt(Receipt r)
{
item = r.item;
price = r.price;
discount = r.discount;
tax = r.tax;
}
}
8
Chapter 1 Functional Interfaces
on price, discount, and tax, but allows implementing classes to override the calculation.
The default method calls private method getDiscounterPrice which applies the
discount to the price.
@FunctionalInterface
interface ReceiptPrinter
{
void print(Receipt receipt);
9
Chapter 1 Functional Interfaces
The following ReceiptPrinter implementation is for merchants who are exempt from
tax. It provides its own implementation of the total calculation which does not include tax.
OUTPUT:
Item : shirt
Amount: 20.0
Disc: 0.05
Tax: 0.07
Total: 20.33
Item : shirt
Amount: 20.0
Disc: 0.05
Total: 19.0
10
Chapter 1 Functional Interfaces
@FunctionalInterface
interface StringProcessor
{
String process(String s);
11
Chapter 1 Functional Interfaces
The static methods can be used to verify the case of the processed string.
String s = toLowerCase.process("FUNCTIONALINTERFACES");
System.out.println(s);
System.out.println("Lower case: " + StringProcessor.isLowerCase(s));
System.out.println("Upper case: " + StringProcessor.isUpperCase(s));
String t = toUpperCase.process(s);
System.out.println("\n" + t);
System.out.println("Lower case: " + StringProcessor.isLowerCase(t));
System.out.println("Upper case: " + StringProcessor.isUpperCase(t));
OUTPUT:
functionalinterfaces
Lower case: true
Upper case: false
FUNCTIONALINTERFACES
Lower case: false
Upper case: true
12
Chapter 1 Functional Interfaces
@FunctionalInterface
interface TwoArgsProcessor<X> {
X process(X arg1, X arg2);
}
class TestTwoArgsProcessor {
public static void main(String[] args)
{
TwoArgsProcessor<Integer> multiplyInts
= new TwoArgsProcessor<>() {
@Override
public Integer process(Integer arg1, Integer arg2)
{
return arg1 * arg2;
}
};
13
Chapter 1 Functional Interfaces
TwoArgsProcessor<Double> addDoubles
= new TwoArgsProcessor<>() {
@Override
public Double process(Double arg1, Double arg2)
{
return arg1 + arg2;
}
};
TwoArgsProcessor<String> compareStrings
= new TwoArgsProcessor<>() {
@Override
public String process(String arg1, String arg2)
{
return arg1.compareTo(arg2) > 0? arg1: arg2;
}
};
System.out.println(multiplyInts.process(2,3));
System.out.println(addDoubles.process(4.1,5.2));
System.out.println(compareStrings.process("ace","age"));
}
}
PROGRAM OUTPUT:
6
9.3
age
14
Chapter 1 Functional Interfaces
@FunctionalInterface
public interface ReceiptPrinter<X extends Receipt>
{
void print(X receipt);
15
Chapter 1 Functional Interfaces
ReceiptPrinter<Receipt> simpleReceiptPrinter
= new ReceiptPrinter<> () {
@Override
public void print(Receipt receipt)
{
System.out.println("\nItem :\t" + receipt.item);
System.out.println("Price:\t" + receipt.price);
System.out.println("Disc:\t" + receipt.discount);
System.out.println("Tax:\t" + receipt.tax);
System.out.println("Total:\t" + computeTotal(receipt));
}
};
ReceiptPrinter<CountyReceipt> countyReceiptPrinter
= new ReceiptPrinter<> () {
@Override
public void print(CountyReceipt receipt)
{
System.out.println("\nItem :\t" + receipt.item);
System.out.println("Price:\t" + receipt.price);
System.out.println("Disc:\t" + receipt.discount);
System.out.println("Tax:\t" + receipt.tax);
System.out.println("CnTax:\t" + receipt.countyTax);
System.out.println("Total:\t" + computeTotal(receipt));
}
@Override
public double computeTotal(CountyReceipt receipt)
{
double discountedPrice = receipt.price
- (receipt.price * receipt.discount);
16
Chapter 1 Functional Interfaces
return discountedPrice
+ (discountedPrice * receipt.tax)
+ (discountedPrice * receipt.countyTax);
}
};
The following code uses the two implementations defined previously to print two
receipts from the same transaction. Only the second receipt contains county tax.
OUTPUT:
Item : shirt
Price: 20.0
Disc: 0.05
Tax: 0.07
Total: 20.33
Item : shirt
Price: 20.0
Disc: 0.05
Tax: 0.07
CnTax: 0.04
Total: 21.09
17
Chapter 1 Functional Interfaces
@FunctionalInterface
public interface TwoIntsProcessor
extends TwoArgsProcessor<Integer>
{
}
@FunctionalInterface
public interface TwoIntsProcessor
extends TwoArgsProcessor<Integer>
{
}
18
Chapter 1 Functional Interfaces
{
TwoIntsProcessor multiplyInts = new TwoIntsProcessor() {
@Override public Integer process(Integer arg1,
Integer arg2)
{
return arg1 * arg2;
}
};
TwoIntsProcessorAbstract divideInts
= new TwoIntsProcessorAbstract() {
@Override public Integer process(Integer arg1,
Integer arg2)
{
return arg1 / arg2;
}
};
System.out.println(multiplyInts.process(2,3));
System.out.println(subtractInts.process(5,2));
System.out.println(divideInts.process(10,2));
}
}
PROGRAM OUTPUT:
6
3
5
19
Chapter 1 Functional Interfaces
Solution
A Pet class which stores information pertaining to each pet can be written. The Pet class
can contain a list of pets. It should define an equals method to compare two pets’ animal
and breed.
import java.util.*;
public class Pet
{
String name;
String animal;
String breed;
String color;
double price;
static List<Pet> pets = new ArrayList<>();
public Pet(String n, String a, String b, String c, double p)
{
name = n;
animal = a;
breed = b;
color = c;
price = p;
}
@Override
public String toString()
20
Chapter 1 Functional Interfaces
{
return name + ":" + " a " + color + " " + breed
+ " " + animal + " for $" + price;
}
@Override
public boolean equals(Object o)
{
Pet p = (Pet)o;
return animal.equals(p.animal) && breed.equals(p.breed);
}
}
@FunctionalInterface
interface PetMatcher
{
List<Pet> match(Pet pet);
default Pet first(Pet pet)
{
int index = Pet.pets.indexOf(pet);
return index > -1? Pet.pets.get(index) : null;
}
}
21
Chapter 1 Functional Interfaces
22
Chapter 1 Functional Interfaces
import java.util.*;
@FunctionalInterface
interface PetMatcher
{
List<Pet> match(Pet pet);
default Pet first(Pet pet)
{
int index = Pet.pets.indexOf(pet);
return index > -1? Pet.pets.get(index) : null;
}
}
class PlayfulPets
{
private static void matchPet(String criteria,
PetMatcher matcher, Pet pet)
{
System.out.println("\n" + criteria + ":");
System.out.println("First: " + matcher.first(pet));
System.out.println("All matches:");
List<Pet> matches = matcher.match(pet);
for (Pet p : matches)
System.out.println(p);
}
23
Chapter 1 Functional Interfaces
24
Chapter 1 Functional Interfaces
matchPet("Poodles",breedMatcher,
new Pet(null, "dog", "poodle", null, 0.0));
matchPet("Pets for $800 or less",priceMatcher,
new Pet(null, null, null, null, 800.0));
}
}
PROGRAM OUTPUT:
Poodles:
First: Scruffy: a white poodle dog for $895.0
All matches:
Scruffy: a white poodle dog for $895.0
Max: a black poodle dog for $540.5
Short Problems
1) Write a functional interface named InputStreamOpener whose
functional method, named open, accepts a String argument
and returns an InputStream. Write an implementation that
opens a DataInputStream. Write a second implementation that
opens an ObjectInputStream. Write a third implementation that
opens a BufferedInputStream. Use anonymous classes for all
implementation. Demonstrate the implementation in a main
program.
2) Write a generic functional interface named Summer, and use it to
compute the sum of two Integers, the sum of two Doubles, and the
sum of two Longs.
25
Chapter 1 Functional Interfaces
Long Problems
1) Write a functional interface named ToString that is generic for
type T. Its functional method, named convert, converts a T to a
String.
26
Chapter 1 Functional Interfaces
3) A typical used car’s list value is its original price minus $1000 per
year since its manufacturing minus $500 for every 10,000 miles
driven. A used sports car’s list value adds back a $250 vintage
factor for every year since its manufacturing.
27
CHAPTER 2
Lambda Expressions
If your applications require many implementations of functional interfaces, you would
have to create many named classes or inline many anonymous class implementations.
This would make your code excessively large and difficult to read. Fortunately, Java 8 has
provided lambda expressions to represent functional interfaces using a very succinct syntax.
@FunctionalInterface
interface StringProcessor
{
String process(String x);
}
29
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0_2
Chapter 2 Lambda Expressions
x -> x
When the functional method of the object is called, the lambda expression executes
passing “Hello” into parameter x in lambda_arg_list. The expression x is evaluated and
“Hello” is provided as the return value of the process method.
System.out.println(lambdaSP.process("Hello"));
OUTPUT:
Hello
If the functional method represented by a lambda has return type void, the body
of the lambda must be an expression that results in a void type. Listing 2-1 defines
functional interface FIVoid whose functional method method1 has return type void.
@FunctionalInterface
interface FIVoid
{
void method1(int i);
}
The body of any lambda expression that represents FIVoid must then be an
expression that results in a void type. The following example uses a System.out.
println statement, which has void return type, as the body of a lambda that represents
functional interface FIVoid. Lambda argument x represents the i formal parameter of
the method1 in interface FIVoid.
30
Chapter 2 Lambda Expressions
OUTPUT:
1: class TestLambdaScope
2: {
3: private static int myField = 2;
4: public static void main(String[] args)
5: {
6: int myLocal = 7;
7: FIVoid lambdaVoid = x -> System.out.println(
8: x + myField + myLocal);
9:
10: lambdaVoid.method1(5);
11: }
12: }
PROGRAM OUTPUT:
14
31
Chapter 2 Lambda Expressions
int myLocal = 7;
myLocal++;
FIVoid lambdaVoid
= x -> System.out.println( ERROR: local variables used
x + myField + myLocal); in lambdas must be
final or effectively
final
x -> x
A type may be provided for the argument, but then lambda_arg_list must be
enclosed in parentheses.
(String x) -> x
The following example attempts to pass an instance of Z<A> to static method m2, but
the compiler is unable to infer from lambda argument x that its type is A, and an error is
generated.
class A { int i; }
@FunctionalInterface
interface Z<T>
{
int m(T t);
}
class Inference
32
Chapter 2 Lambda Expressions
{
static <T> void m2(Z<T> arg) {}
public static void main(String[] args)
{
m2(x -> x.i); // ERROR
}
}
(x, y) -> x + y
In the following example, functional method method1 takes a String and an int
argument:
@FunctionalInterface
interface FI2
{
String method1(String x, int y);
}
The compiler is able to infer the types of the two lambda arguments, so the types do
not need to be provided.
System.out.println(fi2Lambda1.method1("Hello",1));
System.out.println(fi2Lambda2.method1("Hello",1));
OUTPUT:
Hello1
Hello2
33
Chapter 2 Lambda Expressions
() -> 5
The functional interface defined in Listing 2-3 specifies functional method method1
that takes no arguments.
The argument list for any lambda that represents FI3 must consist of an empty set
of parentheses. The following example represents FI3 with a lambda expression that
accepts no arguments and returns the Integer value 99:
OUTPUT:
99
Expression Form:
Block Form:
34
Chapter 2 Lambda Expressions
In block form, the lambda body may consist of multiple statements, each ending in a
semicolon. The following example uses a lambda expression in block form to represent
functional interface FIVoid. The lambda_body contains two statements: the first
statement increments lambda parameter x, and the second statement prints x. Note that
each statement ends in a semicolon and that a semicolon must follow the curly brace
that encloses the lambda expression.
lambdaBlock.method1(5);
OUTPUT:
System.out.println(spBlock.process("Hello"));
OUTPUT:
Hello HelloHello
A lambda expression in block form is similar to a method body in that it may contain
local variables. However, since the lambda is just a series of expressions and not a
separate scope, local variable names must be unique in the current scope. The lambda
expression in the following example declares local variable z. Since a variable named z
already exists in the current scope, a compilation error occurs.
35
Chapter 2 Lambda Expressions
int z = 2;
FIVoid lambdaLocalBad = x -> {
int z =4; ERROR: z already defined
System.out.println(x + z);
};
The lambda expression must choose something else for the name of its local
variable.
int z = 2;
FIVoid lambdaLocalGood = x -> {
int y =4; // OK
System.out.println(x + y);
};
lambdaLocalGood.method1(8);
OUTPUT:
12
A lambda expression may also contain if statements and loops. The lambda
expression in the following example contains an if statement inside a for loop that
increments local variable oddSum each time the loop counter is odd.
lambdaOddSum.method1(7);
OUTPUT:
16
36
Chapter 2 Lambda Expressions
A lambda expression may also contain exception handling. The lambda expression
in the following example contains a try/catch block that protects its code from an
ArrayIndexOutOfBounds exception. The exception is thrown and caught when FIVoid
implementation lambdaSubscript is called with the value 5.
lambdaSubscript.method1(5);
lambdaSubscript.method1(4);
OUTPUT:
37
Chapter 2 Lambda Expressions
Note that it is not possible to call computeTotal from inside the lambda expression.
This is because the lambda expression is just simple code that has no idea it is part of
an implementation of interface ReceiptPrinter, so calling an instance method is not
possible.
Instead, computeTotal can be explicitly called from outside the lambda expression.
38
Chapter 2 Lambda Expressions
OUTPUT:
Item : shirt
Price: 20.0
Disc: 0.05
Tax: 0.07
Total: 20.33
String s = toLowerCase.process("FUNCTIONALINTERFACES");
System.out.println(s);
System.out.println("Lower case: " + StringProcessor.isLowerCase(s));
System.out.println("Upper case: " + StringProcessor.isUpperCase(s));
String t = toUpperCase.process(s);
System.out.println("\n" + t);
System.out.println("Lower case: " + StringProcessor.isLowerCase(t));
System.out.println("Upper case: " + StringProcessor.isUpperCase(t));
OUTPUT:
functionalinterfaces
Lower case: true
Upper case: false
FUNCTIONALINTERFACES
Lower case: false
Upper case: true
39
Chapter 2 Lambda Expressions
Solution
An implementation of the ActionListener interface is registered with a Swing JButton
in order to specify the action performed when the button is pressed.
40
Chapter 2 Lambda Expressions
In this program, the button needs an ActionListener that reads the string entered
in a text field, converts it to an integer, and then displays the square of the integer in
another text field. The listener also needs to detect if the entered text is not a number
and to display an error message if so.
Since lambda expressions can contain exception handling, its code can be protected
from the NumberFormatException that occurs when a non-numeric string is parsed as an
integer, and the caught exception can be used to print an error message.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
class ComputeSquare extends JFrame
{
JTextField number;
JTextField square;
JButton submit;
JPanel panel;
public ComputeSquare()
{
41
Chapter 2 Lambda Expressions
submit.addActionListener( x -> {
try {
int num = Integer.parseInt(number.getText());
square.setText(Integer.toString(num*num));
}
catch (NumberFormatException e) {
System.out.println(number.getText()
+ " is not a number");
}
});
42
Chapter 2 Lambda Expressions
S
hort Problems
1) Rewrite the functional interface implementations from short
problem 1 from Chapter 1 using lambda functions. Demonstrate
in a main program.
L ong Problems
1) Implement long homework problem 1 from Chapter 1 using
lambda expressions.
43
CHAPTER 3
Predicates
Section 3.1: The java.util.function Package
In Java 8, the java.util.function package was added to the Java API. It contains many
functional interfaces designed to assist with functional programming. These functional
interfaces follow one of four basic models:
@FunctionalInterface
public interface Predicate<T>
{
boolean test(T t);
45
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0_3
Chapter 3 Predicates
Predicate<Integer> p1;
Predicate p1's test method will accept an Integer argument, and return true if
its condition results in true.
Predicate p1 can be used to check if an integer is greater than 7. The following
statement uses a lambda expression to represent this condition:
The lambda accepts a single argument x, and its expression results in true when x is
greater than 7.
Since the Predicate interface’s test method returns a boolean result, it can be used
as the boolean expression of an if statement.
if ( p1.test(9))
System.out.println("Predicate x > 7 is true for x==9.");
The program in Listing 3-1 demonstrates. The first boolean expression results in
true, while the second boolean expression results in false.
import java.util.function.*;
class TestTest
{
public static void main(String[] args)
{
Predicate<Integer> p1 = x -> x > 7;
if (p1.test(9))
System.out.println("Predicate x > 7 is true for x==9.");
else
System.out.println("Predicate x > 7 is false for x==9.");
46
Chapter 3 Predicates
if (p1.test(3))
System.out.println("Predicate x > 7 is true for x==3.");
else
System.out.println("Predicate x > 7 is false for x==3.");
}
}
PROGRAM OUTPUT:
import java.util.function.*;
class PredicateMethods
{
public static void result(Predicate<Integer> p, Integer arg)
{
if (p.test(arg))
System.out.println("The Predicate is true for " + arg);
else
System.out.println("The Predicate is false for " + arg);
47
Chapter 3 Predicates
}
public static void main(String[] args)
{
Predicate<Integer> p1 = x -> x == 5;
result(p1,5);
result(y -> y%2 == 0, 5);
}
}
PROGRAM OUTPUT:
The result method in Listing 3-2 can only accept Predicate<Integer> objects. This
method can be made generic by defining a type parameter in its header as shown in
Listing 3-3. Then, the result method can accept Predicate objects of any type.
import java.util.function.*;
class PredicateHelper
{
public static <X> void result(Predicate<X> p, X arg)
{
if (p.test(arg))
System.out.println("The Predicate is true for " + arg);
else
System.out.println("The Predicate is false for " + arg);
}
}
48
Chapter 3 Predicates
import java.util.function.*;
import static PredicateHelper.result;
class TestPredicateHelper
{
public static void main(String[] args)
{
Predicate<Integer> p1 = x -> x > 2;
Predicate<String> p2 = s -> s.charAt(0) == 'H';
result(p1, 6);
result(p2, "Hello");
}
}
PROGRAM OUTPUT:
If a reference variable is not provided, Java will attempt to infer the data type of
the Predicate object. In the following example, Java is able to infer the Integer type
because the int literal 6 is passed as parameter arg.
In the following example, Java is able to infer the String type because the string
literal “Hello” is passed as arg.
In the following example, a compilation error occurs because the String type is
inferred due to string literal “Hello” which cannot be compared relationally to the int
literal 2.
49
Chapter 3 Predicates
p1.method2(p2) // p2 second
.method3(p3) // p3 third
.method4(p4) // p4 fourth
.test(5); // p1 first
50
Chapter 3 Predicates
OUTPUT:
true
Since the logical OR operation is short-circuited, and condition x > 7 is true when x
equals 9, the condition x < 3 is not evaluated and true is displayed.
The following example evaluates the same predicate without declaring a reference
variable through the use of a typecast:
OUTPUT:
true
The program in Listing 3-5 further demonstrates the use of the or method in
predicate chains. The composed predicate in the first call to result is short-circuited
because condition 9 > 7 is true. The predicates in the second and third calls are not
short-circuited, and condition 9 < 3 is evaluated.
import java.util.function.*;
import static PredicateHelper.result;
class TestOr
{
public static void main(String[] args)
{
Predicate<Integer> p1 = x -> x > 7;
51
Chapter 3 Predicates
PROGRAM OUTPUT:
The composed predicate which evaluates x > 7 && x%2 == 1 is written as follows:
OUTPUT:
true
The System.out.println statement prints true because both conditions 9 > 7 and
9%2 == 1 are true.
The program in Listing 3-6 further demonstrates the use of the and method in
predicate chains. The composed predicates in the first two calls to result are short-
circuited because their arguments are not greater than 7. The predicates in the third and
fourth calls are not short-circuited because their arguments are greater than 7, so each
argument must be tested for oddness.
52
Chapter 3 Predicates
import java.util.function.*;
import static PredicateHelper.result;
class TestAnd
{
public static void main(String[] args)
{
Predicate<Integer> p1 = x -> x > 7;
PROGRAM OUTPUT:
In the following example, the condition of the current predicate, 9 > 7, is true. The
negate method returns a new predicate with the opposite result, and false is displayed.
53
Chapter 3 Predicates
OUTPUT:
false
System.out.println(p1.negate()
.and(x -> x%2 == 1)
.test(8) );
OUTPUT:
true
false
In the first println statement, the composed predicate of p1 with the and predicate
is negated. This results in the expression !( (x > 7) && (x%2 == 1) ), which results in
true when x is 8.
In the second println statement, only Predicate p1 is negated. This results in the
expression ( !(x > 7) && (x%2 == 1) ), which results in false when x is 8.
In the following example, the isEqual method uses the equals method of the
Integer class to create a Predicate<Integer> that checks if an integer is equal to 3.
Since Predicate p2's test method is called with the value 3, the string “The Predicate
is true” is printed.
54
Chapter 3 Predicates
Predicate<Integer> p2 = Predicate.isEqual(3);
if (p2.test(3))
System.out.println("The Predicate is true");
OUTPUT:
OUTPUT:
true
In the following example, the and method creates a composed predicate containing
the logical AND of the condition x > 7 and the NOT of the condition x%2 == 1. Since 8
> 7 is true and !(8%2 == 1) is true, the string “true” is displayed.
OUTPUT:
true
55
Chapter 3 Predicates
System.out.println(lengthGr4.and(char0isA)
.test("alpha"));
OUTPUT:
true
try {
System.out.println(lengthGr4.and(char0isA)
.test(null));
}
catch (NullPointerException e) {
System.out.println("NullPointerException");
}
OUTPUT:
NullPointerException
The predicates can be made safe for null strings if the and method were to first check
if the string is null before calling the test method of the Predicate objects. A named or
anonymous class must be provided which overrides the default and method in addition
to the test method (note that a lambda expression cannot be used in this case, since a
default method is being overridden).
Now, if the string tested is null, the and method returns false for the composed
Predicate without calling either test method.
System.out.println(nullProtectedLengthGr4.and(char0isA)
.test("alpha"));
System.out.println(nullProtectedLengthGr4.and(char0isA)
.test(null));
OUTPUT:
true
false
@FunctionalInterface
public interface IntPredicate
{
boolean test(int value);
// other default methods
...
}
57
Chapter 3 Predicates
@FunctionalInterface
public interface LongPredicate
{
boolean test(long value);
// other default methods
...
}
@FunctionalInterface
public interface DoublePredicate
{
boolean test(double value);
// other default methods
...
}
The isEqual method is not defined for predicate specializations since a lambda
expression is easily created using the == operator and an autoboxed number.
The following example demonstrates predicate specializations. IntPredicate i
tests if int literal 2 is greater than 5, which results in false. LongPredicate l tests if
long literal 10 is either even or equal to 6, which results in true. DoublePredicate d
tests if double literal 8.5 is both greater than 8.0 and less than 9.0, which results in true.
System.out.println(i.test(2));
System.out.println(l.or(a -> a == 6L)
.test(10L));
System.out.println(d.and(b -> b < 9.0)
.test(8.5));
OUTPUT:
false
true
true
58
Chapter 3 Predicates
OUTPUT:
true
Question 1 Are you female? Are you male? Are you female?
Question 2 Do you live in New Jersey Do you live in New York? Do you live in New Jersey?
or Pennsylvania?
Question 3 Are you between 40 and Are you between 20 and 30 Are you older than 40
50 years of age? years of age? years of age?
Question 4 Do you have a bachelor’s Do you have a bachelor’s Do you have a master’s
degree or higher? degree? degree or PhD?
Dave has instructed his sales team to ask a customer their gender, state of residence,
age, and level of education when they walk into the showroom. This information will
then be used to qualify the customer for one of the cars as follows: if the answer to three
or more of the questions in the qualifier is yes, then the customer is likely to buy the car,
and the salesperson should attempt to sell it to them.
Three customers entered Dave’s showroom today:
Solution
A Customer class which stores each customer’s gender, state of residence, age, and
highest level of education can be written.
60
Chapter 3 Predicates
class Customer
{
String gender;
String state;
int age;
Education ed;
public Customer(String g, String st, int a, Education e)
{
gender = g;
state = st;
age = a;
ed = e;
}
@Override
public String toString()
{
return age + " year old " + gender + " from "
+ state + " with " + ed;
}
}
The qualification questions for the cars can then be organized as an ArrayList of
type Predicate<Customer>. The logic in the first column of Table 3-2 is implemented
in the elantraQualifier. The logic in the second and third columns of Table 3-2 is
implemented in the priusQualifier and the odysseyQualifier, respectively.
61
Chapter 3 Predicates
62
Chapter 3 Predicates
The complete program is shown and demonstrated as follows. The first customer
qualifies for an Elantra since she is a female between 40 and 50 years of age with a
bachelor’s degree or higher. She doesn’t qualify for a Prius since she only matches
two criteria (New Yorker with a BS). She doesn’t qualify for an Odyssey since she only
matches two criteria (female older than 40). The second customer qualifies for a Prius
since he is male from New York with a bachelor’s degree. He doesn’t qualify for an
Elantra since he only matches two criteria (40 to 50 years old with a BS or higher). He
doesn’t qualify for an Odyssey since he only matches one criterion (between 40 and 50
years of age). The third customer qualifies for an Odyssey since she is a female older
than 40 with a master’s degree or higher. She doesn’t qualify for an Elantra since she only
matches two criteria (female with a BS or higher). She doesn’t qualify for a Prius since
she only matches one criterion (is a New Yorker).
class DiscountDave
{
private static boolean qualifyCustomer(
Customer c,
ArrayList< Predicate<Customer> > pList)
{
int count = 0;
for (Predicate<Customer> p : pList)
{
if (p.test(c))
{
count++;
System.out.println("MATCH " + count);
}
}
return count >= 3;
}
64
Chapter 3 Predicates
if (qualifyCustomer(c1,odysseyQualifier))
System.out.println("Sell customer a Honda Odyssey");
65
Chapter 3 Predicates
if (qualifyCustomer(c3,odysseyQualifier))
System.out.println("Sell customer a Honda Odyssey");
}
}
PROGRAM OUTPUT:
66
Chapter 3 Predicates
Short Problems
1) Write a predicate that tests if a string contains only digits.
Demonstrate in a main program.
Long Problems
1) Using a single chain of predicates, determine if a five-letter word
is a palindrome or not. A palindrome is a word that is spelled
the same backward and forward. For example, “kayak” is a
palindrome, while “apple” is not.
–– Did not file a joint tax return or filed a joint return only to claim a
refund
67
Chapter 3 Predicates
–– A Straight: the face values of the cards in the hand form a series of
consecutive values.
–– A Flush: the suites of the cards in the hand are the same.
–– A Full House: three of the cards in the hand have the same face
value, while the remaining cards match a different face value.
68
CHAPTER 4
Functions
Section 4.1: The Function Interface
Function is a functional interface with two type parameters T and R. Its functional
method, called apply, takes an argument of type T and returns an object of type R.
Functions are ideal for converting an object of type T to one of type R.
@FunctionalInterface
public interface Function<T, R>
{
R apply(T t);
...
}
Function<String, Integer> f;
Function f’s apply method will accept a String argument and return an Integer.
The following statement represents this function using a lambda expression:
The lambda expression accepts a single String argument x and returns the Integer
that results from autoboxing the result of the Integer.parseInt method called on x.
Calling Function f’s apply method with argument “100” assigns the value 100 to
Integer variable i.
Integer i = f.apply("100");
System.out.println(i);
OUTPUT:
100
69
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0_4
Chapter 4 Functions
The program in Listing 4-1 demonstrates. The first call to transform accepts
a Function<String, Integer> and converts the String argument to an Integer
using the Integer class’s parseInt method. The second call to transform accepts a
Function<Integer, String> and converts the Integer argument to a String using the
Integer class’s toString method.
import java.util.function.Function;
class Transformer
{
private static <T,R> R transform(T t, Function<T, R> f)
{
return f.apply(t);
}
PROGRAM OUTPUT:
100
200
The program in Listing 4-2 demonstrates. It loops through the list and calls the parse
method to transform an array of Strings to an array of numeric wrapper objects. The
value returned from each of the wrapper class’s parse methods is autoboxed, and the
result is polymorphically assigned to an entry in the array of Numbers.
71
Chapter 4 Functions
PROGRAM OUTPUT:
10
20
30
40
50.0
60.0
72
Chapter 4 Functions
The following example creates a chain of functions by using the andThen method
in conjunction with the apply method. The apply method converts the string “true” to
Boolean value true, which is input to the function executed by the andThen method. This
function converts Boolean value true to Integer value 1.
The transformations along the function chain are listed on the right in the
comments.
System.out.println(
fsb.andThen(x -> x==true? 1:0) // Function<Boolean, Integer>
.apply("true")); // Function<String, Boolean>
OUTPUT:
73
Chapter 4 Functions
Functions that convert from Boolean to Integer and from String to Boolean are
defined as follows:
In the following example, the compose method uses Function fsb to convert string
“true” to Boolean value true. Then, the apply method uses Function fbi to convert
true to Integer value 1, which is displayed.
OUTPUT:
Several methods in the Java API expect mapping functions which will change the
input. Passing the Function.identity method to one of these methods will cause the
methods to process without changing the input.
The following example creates a Function which returns the string passed to the
apply method. Therefore, the string “HELLO WORLD” is displayed.
Function<String,String> f = Function.identity();
System.out.println(f.apply("HELLO WORLD"));
OUTPUT:
HELLO WORLD
74
Chapter 4 Functions
@FunctionalInterface
public interface IntFunction<R> {
R apply(int value);
}
@FunctionalInterface
public interface LongFunction<R> {
R apply(long value);
}
@FunctionalInterface
public interface DoubleFunction<R> {
R apply(long value);
}
System.out.println(fi.apply(5));
System.out.println(fd.apply(4.5));
System.out.println(fl.apply(20L));
OUTPUT:
5
false
20
75
Chapter 4 Functions
@FunctionalInterface
public interface ToIntFunction<T> {
int applyAsInt(T value);
}
@FunctionalInterface
public interface ToLongFunction<T> {
long applyAsLong(T value);
}
@FunctionalInterface
public interface ToDoubleFunction<T> {
double applyAsDouble(T value);
}
System.out.println(ti.applyAsInt("5"));
System.out.println(tl.applyAsLong(5.1));
System.out.println(td.applyAsDouble(7));
OUTPUT:
5
5
7.0
76
Chapter 4 Functions
System.out.println(di.applyAsInt(4.1));
System.out.println(dl.applyAsLong(5.2));
System.out.println(id.applyAsDouble(6));
System.out.println(il.applyAsLong(7));
System.out.println(ld.applyAsDouble(8));
System.out.println(li.applyAsInt(9));
OUTPUT:
4
5
6.0
7
8.0
9
77
Chapter 4 Functions
@FunctionalInterface
public interface BiFunction<T,U,R> {
R apply(T t, U u);
}
System.out.println(bi.apply(4,'U'));
OUTPUT:
EVEN
A function which converts the String to Double can be added to the result of the previous
example using the andThen method, causing a Double to be printed instead of a String.
System.out.println(d);
OUTPUT:
3.0
78
Chapter 4 Functions
@FunctionalInterface
public interface ToIntBiFunction<T,U> {
int applyAsInt(T t, U u);
}
@FunctionalInterface
public interface ToLongBiFunction<T,U> {
long applyAsLong(T t, U u);
}
@FunctionalInterface
public interface ToDoubleBiFunction<T,U> {
double applyAsDouble(T t, U u);
}
System.out.println(tib.applyAsInt("5",4.2));
System.out.println(tlb.applyAsLong(5.1, "6"));
System.out.println(tdb.applyAsDouble(7, 8L));
79
Chapter 4 Functions
OUTPUT:
9
11
15.0
Solution
A Customer class containing fields for a customer’s name, phone number, favorite sport,
favorite team, grade point average, favorite subject, number of friends, and a list of his or
her friends can be written.
class Customer
{
String name;
String phoneNum;
String sport;
String team;
double gpa;
String subject;
int numFriends;
String friends;
80
Chapter 4 Functions
On Sundays, the sales rep will query the database for customers who enjoy football
by matching the following Customer object:
81
Chapter 4 Functions
On Tuesdays, the sales rep will query the database for customers with grade point
averages of 3.75 or higher by matching the following Customer object:
On Fridays, the sales rep will query the database for customers with one or more
friends on record by matching the following Customer object:
The query needs to generate a record that the sales rep will use during the phone
call with the targeted customer containing the customer’s name, phone number, and
item of personal interest. The record should also keep track of the customer’s position
in the database. The records are different for each query, but the common fields can be
grouped inside abstract class Record.
82
Chapter 4 Functions
{
super(n, pn, i);
team = t;
}
@Override
public String toString() {
return super.toString() + " favorite team is the " + team;
}
}
84
Chapter 4 Functions
The BiFunctions can be stored in an ArrayList where the third BiFunction type
parameter is wildcarded for Record and its subclasses.
To perform the queries, a method that accepts the customer query and the
BiFunction list as arguments can be written. The method will execute each BiFunction
for all customers in the database using the customer query. The record generated by the
BiFunction is printed for the sales rep. The index stored in the record is used to keep
track of the current position in the database which is used in the next execution of the
BiFunction.
85
Chapter 4 Functions
System.out.println(record);
index = record.index + 1;
}
} while (record != null);
}
System.out.println();
}
The program in Listing 4-3 demonstrates. The sales rep first runs the Sunday
football promotion by passing a Customer object whose sport is football to method
matchCustomer. The fsport BiFunction matches customer John Smith and outputs
a SportRecord that tells the rep that John’s favorite team is the Giants. The record also
stores John’s customer index, 0, so that the next iteration of fsport will begin at 1. The
fsport BiFunction then matches customer Sarah Johnson and outputs a SportsRecord
that tells the rep that Sarah’s favorite team is the Eagles. The record also stores Sarah’s
customer index, 2, so that the next iteration of fsport will begin at 3. The fgpa and
ffriends BiFunctions return no records because the matching Customer has the
maximum values for GPA and number of friends.
Next, the sales rep runs the high-tech promotion by passing a Customer object whose
GPA is 3.75 to method matchCustomer. The fgpa BiFunction matches customer Indira
Patel and outputs a GpaRecord that tells the rep that Indira’s favorite subject is Java. The
record also stores Indira’s customer index, 1, so that the next iteration of fgpa will begin
at 2. The fgpa BiFunction then matches customer Javier Jones and outputs a GpaRecord
that tells the rep that Javier’s favorite subject is physics. The fsport and ffriends
BiFunctions match no records because the matching Customer has a null sport and the
maximum value for number of friends.
Finally, the sales rep runs the bring-a-friend promotion by passing a Customer object
whose number of friends is 1. The ffriends BiFunction matches customer Sarah
Johnson and outputs a FriendsRecord that tells the rep that Sarah has a friend named
Jane Hernandez. The record also stores Sarah’s customer index, 2, so that the next
iteration of ffriends will begin at 3. The ffriends BiFunction then matches customer
Javier Jones and outputs a FriendsRecord that tells the rep that Javier has a friend
named Maria Regina. The fsport and fgpa BiFunctions match no records because the
matching Customer has a null sport and the maximum value for GPA.
86
Chapter 4 Functions
import java.util.function.*;
import java.util.ArrayList;
class Customer
{
String name;
String phoneNum;
String sport;
String team;
double gpa;
String subject;
int numFriends;
String friends;
public Customer(String n, String pn, String sp, String t,
double g, String s, int nf, String f)
{
name = n;
phoneNum = pn;
sport = sp;
team = t;
gpa = g;
subject = s;
numFriends = nf;
friends = f;
}
}
87
Chapter 4 Functions
name = n;
phoneNum = pn;
index = i;
}
@Override
public String toString() {
return "name: " + name + " phone number: " + phoneNum;
}
}
class SalesPromotion
{
final static Customer[] customers = {
new Customer("John Smith", "9084321212", "football",
"Giants", 3.61, null, 0, null),
new Customer("Indira Patel", "7325551234", "tennis",
null, 3.92, "Java", 0, null),
new Customer("Sarah Johnson", "2123231245", "football",
"Eagles", 3.71, null, 1,
"Jane Hernadez,2017765765"),
new Customer("Javier Jones", "8568768765", "golf",
null , 3.85, "Physics", 1,
"Maria Regina,9086547654")
};
89
Chapter 4 Functions
int index = 0;
do
{
record = f.get(j).apply(c,index);
if (record != null)
{
System.out.println(record);
index = record.index + 1;
}
} while (record != null);
}
System.out.println();
}
public static void main(String[] args)
{
BiFunction<Customer,Integer,SportRecord> fsport = (x,z) -> {
SportRecord sport = null;
90
Chapter 4 Functions
System.out.println(
"SUNDAY FOOTBALL PROMOTION - Call the following customers:");
matchCustomers(new Customer(null, null, "football", null,
Double.MAX_VALUE, null, Integer.MAX_VALUE, null), list);
System.out.println(
"TUESDAY HIGH-TECH PROMOTION - Call the following customers:");
matchCustomers(new Customer(null, null, null, null, 3.75,
null, Integer.MAX_VALUE, null), list);
System.out.println(
"FRIDAY BRING A FRIEND PROMOTION - Call the following customers:");
matchCustomers(new Customer(null, null, null, null,
Double.MAX_VALUE, null, 1, null), list);
}
}
91
Chapter 4 Functions
PROGRAM OUTPUT:
Short Problems
1) Given the following two classes,
class A class B
{ {
Double d; Double a;
String s; String b;
Integer i; String c;
public A(Double x, String y, public B(Double x, String y,
Integer z) String z)
{ {
d = x; a = x;
s = y; b = y;
i = z; c = z;
} }
public String toString() public String toString()
{ {
return d + " " + s + " " return a + " " + b + " "
+ i; + c;
} }
} }
92
Chapter 4 Functions
Long Problems
1) A deck of cards consists of 52 playing cards, 1 for each of the 13
face values (2–10, Jack, Queen, King, and Ace) and each of the 4
suites (Hearts, Clubs, Spades, and Diamonds). Write a single loop
that uses a function to create a deck of playing cards.
Use a function chain to switch the keys and values of each entry
(Hint: first convert the map to a list, and then back to a map with
the keys and values switched).
93
CHAPTER 5
Operators
When a Function object’s input type is the same as its output type, an Operator interface
can be used in place of a Function. Operator interfaces define a single type parameter.
@FunctionalInterface
public interface UnaryOperator<T>
extends Function<T,T> {
static <T> UnaryOperator<T> identity();
}
UnaryOperator<String> concat = x -> x + x;
UnaryOperator<Integer> increment = x -> ++x;
UnaryOperator<Long> decrement = x -> --x;
95
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0_5
Chapter 5 Operators
System.out.println(concat.apply("My"));
System.out.println(increment.apply(4));
System.out.println(decrement.apply(4L));
OUTPUT:
MyMy
5
3
My
@FunctionalInterface
public interface IntUnaryOperator {
int applyAsInt(int operand);
...
}
@FunctionalInterface
public interface LongUnaryOperator {
long applyAsLong(long operand);
...
}
@FunctionalInterface
public interface DoubleUnaryOperator {
double applyAsDouble(double operand);
...
}
96
Chapter 5 Operators
OUTPUT:
9
3
8.61
@FunctionalInterface
public interface BinaryOperator<T>
extends BiFunction<T,T,T> {
// some static methods
...
}
System.out.println(concat.apply("AB", "CD"));
System.out.println(subtract.apply(4, 1));
System.out.println(multiply.apply(4L, 3L));
OUTPUT:
ABCD
3
12
98
Chapter 5 Operators
@FunctionalInterface
public interface IntBinaryOperator {
int applyAsInt(int left,int right);
}
@FunctionalInterface
public interface LongBinaryOperator {
long applyAsLong(long left, long right);
}
@FunctionalInterface
public interface DoubleBinaryOperator {
double applyAsDouble(double left, double right);
}
OUTPUT:
11
4
12.0
99
Chapter 5 Operators
PROJECT 5: Calculator
Problem Statement
Write a calculator program that interacts with a user through the command line interface.
The calculator should prompt the user to select one of the following operations:
Operation Description
+ Addition
- Subtraction
* Multiplication
/ Division
POW Raise a number to a power
LOG log10 of a number
EXP e raised to a number
SIN The sine of a number in degrees
COS The cosine of a number in degrees
TAN The tangent of a number in degrees
QUI Quit the program
If the operation selected does not match any of those listed here, the program
should print an error message. If QUI is selected, the program terminates. Otherwise,
the program should prompt the user for the first operand. If the operation is a binary
operation, it should prompt the user for the second operand. Then, the program should
perform the operation and print the result. Then the program should prompt the user to
select another operation. The process continues until the user selects QUI.
Solution
All operations should be performed using doubles. DoubleBinaryOperators should be
written for addition, subtraction, multiplication, division, and raising a number to a power.
A method that prompts the user to enter an operand can be written. The method
should return the value of the operand as a double.
The program should switch on the selected operation, and call the applyAsDouble
method of the corresponding operator. Each case in the switch statement calls the getOp
method once or twice depending on how many operands the corresponding operation
requires.
switch (op)
{
case "+" : System.out.println(
sum.applyAsDouble(getOp(),getOp()));
break;
case "-" : System.out.println(
dif.applyAsDouble(getOp(),getOp()));
break;
case "*" : System.out.println(
mul.applyAsDouble(getOp(),getOp()));
break;
101
Chapter 5 Operators
The program in Listing 5-1 demonstrates. The user selects the “+” operation which
has two operands. The user is prompted to enter both operands, and the program prints
9.1 which is the sum of operands 4.0 and 5.1. The user then selects the “SIN” operation
which has only one operand. The user is prompted to enter the angle, and the program
prints 0.49999999999999994 which is the sine of 30 degrees. The user then selects “QUI”
and the program terminates.
102
Chapter 5 Operators
import java.util.function.*;
import java.util.Scanner;
class Calculator
{
public static double getOp()
{
Scanner scan = new Scanner(System.in);
System.out.print("Enter an operand:");
return Double.parseDouble(scan.nextLine());
}
public static void main(String[] args)
{
Scanner scan = new Scanner(System.in);
final String[] ops = {"+","-","*","/","POW","ABS","LOG",
"EXP","SIN","COS","TAN","QUI"};
103
Chapter 5 Operators
System.out.println(s);
String op = scan.nextLine();
switch (op)
{
case "+" : System.out.println(
sum.applyAsDouble(getOp(),getOp()));
break;
case "-" : System.out.println(
dif.applyAsDouble(getOp(),getOp()));
break;
case "*" : System.out.println(
mul.applyAsDouble(getOp(),getOp()));
break;
case "/" : System.out.println(
div.applyAsDouble(getOp(),getOp()));
break;
case "POW": System.out.println(
pow.applyAsDouble(getOp(),getOp()));
break;
case "ABS": System.out.println(
abs.applyAsDouble(getOp()));
break;
case "LOG": System.out.println(
log.applyAsDouble(getOp()));
break;
case "EXP": System.out.println(
exp.applyAsDouble(getOp()));
break;
case "SIN": System.out.println(
sin.applyAsDouble(getOp()));
break;
case "COS": System.out.println(
cos.applyAsDouble(getOp()));
break;
case "TAN": System.out.println(
104
Chapter 5 Operators
tan.applyAsDouble(getOp()));
break;
case "QUI": done = true;
break;
default: System.out.println("Invalid operation");
}
}
}
}
105
Chapter 5 Operators
EXP
SIN
COS
TAN
QUI
SIN
Enter an operand:30.0
0.49999999999999994
Select an operation (select QUI to quit):
+
-
*
/
POW
ABS
LOG
EXP
SIN
COS
TAN
QUI
QUI
Short Problems
1) Write an operator that returns a string containing the odd
characters of an input string (in other words, the characters
located at positions 1, 3, 5, etc.).
-4 * (x + 2)
3) Class A has int field x and double field y. Write a BinaryOperator
that add the x field of two A objects while subtracting the y field of
the two objects.
106
Chapter 5 Operators
Long Problems
1) Using a single chain of operators, solve the following equation for
x = 6 and y = 3:
5 * (2x + 3y) / 2
2) Given the string “The fault lies not from our stars,” use a single
chain of operators to perform the following operations:
107
CHAPTER 6
Consumers
Section 6.1: The Consumer Interface
Consumer is a functional interface that is used to process data. A Consumer object is
specified with type parameter T. Its functional method, called accept, takes an argument
of type T and has return type void.
@FunctionalInterface
public interface Consumer<T>
{
void accept(T t);
...
}
A Consumer of Integer type whose accept method adds its argument to a static field
called sum is defined as follows:
The program in Listing 6-1 uses the consumer to add the numbers 4 and 5 and then
prints the sum:
import java.util.function.Consumer;
public class TestConsumer
{
private static int sum = 0;
public static void main(String[] args)
{
Consumer<Integer> con = x -> sum += x;
con.accept(4);
109
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0_6
Chapter 6 Consumers
con.accept(5);
System.out.println(sum);
}
}
PROGRAM OUTPUT:
The following program computes the sum and the product of 4 and 5. The consumer
chain creates a composed consumer consisting of the code in both consum and conprod.
The code in the calling Consumer object, consum, is executed first, and the argument 4 is
added to sum. Then the code in conprod, the consumer passed to the andThen method,
is executed, and the argument 4 is multiplied by prod. The process is repeated for the
argument 5.
import java.util.function.Consumer;
public class TestConsumerAndThen
{
private static int sum = 0;
private static int prod = 1;
public static void main(String[] args)
{
Consumer<Integer> consum = x -> sum += x;
Consumer<Integer> conprod = x -> prod *= x;
consum.andThen(conprod)
.accept(4);
consum.andThen(conprod)
110
Chapter 6 Consumers
.accept(5);
PROGRAM OUTPUT:
PROGRAM OUTPUT:
166
@FunctionalInterface
public interface IntConsumer
{
void accept(int value);
...
}
@FunctionalInterface
public interface LongConsumer
{
void accept(long value);
...
}
@FunctionalInterface
public interface DoubleConsumer
{
void accept(double value);
...
}
112
Chapter 6 Consumers
The program in Listing 6-4 demonstrates. IntConsumer ic assigns the sum of int
argument 2 and the value 3 to static field a. LongConsumer lc assigns the quotient of
long argument 6 and the value 2 to static field b. DoubleConsumer dc assigns the product
of double argument 4.0 and static field c to static field c.
import java.util.function.*;
public class ConsumerSpecials
{
private static int a = 0;
private static long b = 0;
private static double c = 1.0;
public static void main(String[] args)
{
IntConsumer ic = x -> a = x + 3;
LongConsumer lc = x -> b = x / 2L;
DoubleConsumer dc = x -> c = x * c;
PROGRAM OUTPUT:
5
3
4.0
113
Chapter 6 Consumers
@FunctionalInterface
public interface BiConsumer<T,U>
{
void accept(T t, U u);
...
}
import java.util.function.BiConsumer;
public class TestBiConsumer
{
private static int sum = 0;
private static String components = "";
public static void main(String[] args)
{
BiConsumer<Integer,String> bi = (x,y) -> {
sum += x;
components += y;
};
114
Chapter 6 Consumers
PROGRAM OUTPUT:
6 Term 1
add 7 ,Term 2 result = 13 Term 1,Term 2
@FunctionalInterface
public interface ObjIntConsumer<T> {
void accept(T t, int value);
}
@FunctionalInterface
public interface ObjLongConsumer<T> {
void accept(T t, long value);
}
@FunctionalInterface
public interface ObjDoubleConsumer<T> {
void accept(T t, double value);
}
115
Chapter 6 Consumers
oic.accept("Value", 4);
olc.accept("7", 2L);
odc.accept("DBL", 4.1);
OUTPUT:
Value = 4
9
DBL4.1
116
Chapter 6 Consumers
Solution
First, a BankAccount class with fields name, id, and balance should be written.
class BankAccount
{
String name;
int id;
double balance;
public BankAccount(String n, int i, double b)
{
name = n;
id = i;
balance = b;
}
@Override
public boolean equals(Object ba)
{
return id == ((BankAccount)ba).id;
}
@Override
public String toString()
{
return "name = " + name + " id = " + id
+ " balance = " + balance;
}
}
Next, a few static fields shared by the consumers need to be created. These include
the accounts database, the number of accounts, and the previous balance.
117
Chapter 6 Consumers
Accounts are opened using the customer’s name and an initial deposit amount.
Therefore, a BiConsumer<String, Double> object should be created. The BiConsumer will
first save the current number of bank accounts in the database. Then, it will generate
a random number between 0 and 10000, and use the customer’s name, the random
number, and the initial balance to create a new bank account and add it to the database.
The account creation is then verified by checking if an entry has been added to the
accounts database and that the name field in the added entry matches the customer’s
name. If the entry matches, the corresponding bank account information is displayed.
open.andThen(openVerification)
.accept(promptName(), promptAmount());
118
Chapter 6 Consumers
Deposits, withdrawals, and account closures need to look up a bank account based
on id. Since the BankAccount class’s equals method tests the id field, a BankAccount
object containing the entered id can be passed to the indexOf method of ArrayList
accounts.
Deposits are performed using the customer’s id and the deposit amount. Therefore,
they can be implemented by a BiConsumer<Integer, Double> object. If an account
matching the id is found, the previous balance in the account is stored. Then the deposit
amount is added to the balance.
119
Chapter 6 Consumers
deposit.andThen(depositVerification)
.accept(promptId(), promptAmount());
Withdrawals are performed using the customer’s id and the withdrawal amount.
Therefore, they can be implemented by a BiConsumer<Integer, Double> object. If
an account matching the id is found, the previous balance in the account is stored. If
sufficient funds exist in the account, the withdrawal amount is subtracted from the
balance.
120
Chapter 6 Consumers
withdraw.andThen(withdrawVerification)
.accept(promptId(), promptAmount());
Accounts are closed using the customer’s id. A Consumer<Integer> can be used. If an
account matching the id is found, it is removed from the database.
The account closure is then verified by checking if an entry has been removed from
the accounts database.
close.andThen(closeVerification)
.accept(promptId());
121
Chapter 6 Consumers
import java.util.function.*;
import java.util.*;
class BankAccount
{
String name;
int id;
double balance;
public BankAccount(String n, int i, double b)
{
name = n;
id = i;
balance = b;
}
@Override
public boolean equals(Object ba)
{
return id == ((BankAccount)ba).id;
}
@Override
public String toString()
{
return "name = " + name + " id = " + id
+ " balance = " + balance;
}
}
122
Chapter 6 Consumers
class BankTransactions
{
private static ArrayList<BankAccount> accounts
= new ArrayList<>();
private static int numAccounts = 0;
private static double prevBalance = 0.0;
private static Scanner scan = new Scanner(System.in);
public static String promptTransaction()
{
System.out.println("Enter Transaction Type:");
System.out.println("OPENACCOUNT");
System.out.println("DEPOSIT");
System.out.println("WITHDRAWAL");
System.out.println("CLOSEACCOUNT");
System.out.print("QUIT:");
return scan.nextLine();
}
public static double promptAmount()
{
System.out.print("Enter amount:");
return Double.parseDouble(scan.nextLine());
}
public static String promptName()
{
System.out.print("Enter name:");
return scan.nextLine();
}
public static int promptId()
{
System.out.print("Enter id:");
return Integer.parseInt(scan.nextLine());
}
public static BankAccount findBankAccount(int id)
{
int index = accounts.indexOf(new BankAccount(null,id,0.0));
123
Chapter 6 Consumers
124
Chapter 6 Consumers
125
Chapter 6 Consumers
accounts.remove(account);
};
126
Chapter 6 Consumers
default:
System.out.println("Invalid selection");
}
}
}
}
Short Problems
1) Use a chain of consumers to do the following:
128
Chapter 6 Consumers
Long Problems
1) A dictionary is implemented as a map of keys and values. Using a
single chain of consumers, create a string that contains a comma-
separated list of the first word of each value whose key is five
characters in length.
129
CHAPTER 7
Suppliers
Section 7.1: The Supplier Interface
Supplier is a functional interface that is used to generate data. A Supplier object is
specified with type parameter T. Its functional method, called get, takes no arguments
and returns an object of type T.
@FunctionalInterface
public interface Supplier<T>
{
T get();
}
131
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0_7
Chapter 7 Suppliers
The program in Listing 7-1 demonstrates the use of these two suppliers. The get
method of generateInteger is called and integer value 30 is generated randomly.
It is called again and integer value 24 is randomly generated. The get method of
generateString is called which first prompts the user to enter a string and then uses
a Scanner to enter the string “Hello” from the user. GenerateString's get method is
called again, and the string “World” is obtained from the user.
System.out.println(generateInteger.get());
System.out.println(generateInteger.get());
System.out.println(generateString.get());
System.out.println(generateString.get());
}
}
132
Chapter 7 Suppliers
30
24
Enter a string:Hello
Hello
Enter a string:World
World
133
Chapter 7 Suppliers
Since the while loop and prompt have been moved to the Supplier implementation,
the main loop is simplified considerably.
An execution of the main loop is shown as follows. The user selects 0 and is told that
the operation is invalid. Then, the user selects 2 and the program performs Operation 2.
Then, the user selects 4 and the program terminates.
Select an operation:
1: Operation 1
2: Operation 2
3: Operation 3
4: Quit
0
Invalid operation
Select an operation:
1: Operation 1
2: Operation 2
3: Operation 3
4: Quit
2
Performing Operation 2
134
Chapter 7 Suppliers
Select an operation:
1: Operation 1
2: Operation 2
3: Operation 3
4: Quit
4
@FunctionalInterface
public interface BooleanSupplier {
boolean getAsBoolean();
}
@FunctionalInterface
public interface IntSupplier {
int getAsInt();
}
@FunctionalInterface
public interface LongSupplier {
long getAsLong();
}
@FunctionalInterface
public interface DoubleSupplier {
double getAsDouble();
}
import java.util.function.*;
import java.util.Random;
class TestSpecials
{
public static Random rand = new Random();
public static void main(String[] args)
{
BooleanSupplier genBol = () ->
(rand.nextInt(2) == 1)? true:false;
IntSupplier genInt = () -> rand.nextInt();
LongSupplier genLng = () -> rand.nextLong();
DoubleSupplier genDbl = () -> rand.nextDouble();
System.out.println(genBol.getAsBoolean());
System.out.println(genInt.getAsInt());
System.out.println(genLng.getAsLong());
System.out.println(genDbl.getAsDouble());
}
}
PROGRAM OUTPUT:
true
-1883978156
95506489147983601
0.050671447138405656
136
Chapter 7 Suppliers
All tickets contain the customer’s name, the customer’s id, a description of the
problem, and an estimated completion date. Hardware tickets document problems with
the cell phone itself. They also contain the device name, its model, and serial number.
Software tickets document either bugs in a software app or problems using the web site.
They also contain the application name, its version, and where the application is hosted
(on phone or on the Internet).
Each day, a tech support specialist attempts to work on all the problems documented
in the trouble ticket database; the order of tickets worked is based on estimated completion
dates. When the tech works on a problem, it is marked as having been serviced today. If the
tech can solve the problem, it is removed from the database. The tech proceeds until all the
tickets in the database have been serviced or his or her shift is over. If the tech can service
all the tickets in the database in a single day, he or she earns a $50 bonus.
Solution
A Ticket class that is comparable by due date can be written. The class should store the
last date the ticket was serviced. When a ticket is created, the estimated repair time is
added to the current date forming the due date. The service date is set to yesterday, so
that the ticket will be serviced today by the tech specialist.
137
Chapter 7 Suppliers
138
Chapter 7 Suppliers
139
Chapter 7 Suppliers
BooleanSuppliers can be written to prompt the specialist if he or she can close the
ticket or if his or her shift is over.
140
Chapter 7 Suppliers
Supplier nextTicket's get method is called in a loop that terminates when either
all the tickets have been serviced or the tech specialist’s shift is over. If the tech can
solve the problem, it is removed from the database. The BooleanSuppliers provide the
necessary user input.
Ticket next;
boolean done = false;
do
{
next = nextTicket.get();
if (next != null)
{
System.out.println("\n" + next);
if (canClose.getAsBoolean())
tickets.remove(next);
if (isQuittingTime.getAsBoolean())
done = true;
}
} while(next != null && !done);
141
Chapter 7 Suppliers
if (next == null)
System.out.println(
"\nCongrats, you get a $50 bonus today!");
else
System.out.println("\nSee you tomorrow");
The complete program is shown and demonstrated as follows. The tech support
specialist starts her day, and the nextTicket Supplier returns the first ticket due that
has not been worked on yet today. A SoftwareTicket which is due tomorrow for Britney
Delmonica concerning the web site is displayed by the program. The specialist works on
the ticket and then closes it, which causes the ticket to be removed from the database.
NextTicket then displays a SoftwareTicket which is due 2 days from now for Chester
Rodriguez concerning a cell phone app. The specialist works on the ticket and then
closes it. NextTicket then displays a HardwareTicket which is due 5 days from now for
Kalpana Patel concerning cell phone power. The specialist is unable to close this ticket.
NextTicket then displays a HardwareTicket for Kalpana Patel which is due 7 days from
now concerning the cell phone’s screen. The specialist is unable to close this ticket.
NextTicket then returns null since the specialist has serviced all open tickets today, and
the loop terminates. Since the specialist got through all the tickets in the database, she
gets a $50 bonus for the day.
import java.util.ArrayList;
import java.util.Scanner;
import java.time.LocalDate;
import java.util.function.Supplier;
import java.util.function.BooleanSupplier;
142
Chapter 7 Suppliers
{
customerName = cn;
id = i;
description = d;
dueDate = LocalDate.now().plusDays(due);
servicedDate = LocalDate.now().minusDays(1);
}
@Override
public String toString()
{
return "NAME: " + customerName
+ "\nID: " + id
+ "\nDESCRIPTION: " + description
+ "\nDUE DATE: " + dueDate
+ "\nSERVICED DATE: " + servicedDate;
}
@Override
public int compareTo(Ticket t)
{
return dueDate.compareTo(t.dueDate);
}
}
143
Chapter 7 Suppliers
@Override
public String toString()
{
return super.toString()
+ "\nDEVICE: " + device
+ "\nMODEL: " + model
+ "\nSERIAL NUMBER: " + serialNumber;
}
}
class TicketingSystem
{
144
Chapter 7 Suppliers
145
Chapter 7 Suppliers
populateDatabase();
Ticket next;
boolean done = false;
do
{
next = nextTicket.get();
if (next != null)
{
System.out.println("\n" + next);
if (canClose.getAsBoolean())
tickets.remove(next);
if (isQuittingTime.getAsBoolean())
done = true;
146
Chapter 7 Suppliers
}
} while(next != null && !done);
NAME: Britney Delmonica
ID: 91472
DESCRIPTION: Can't change banking info on website
DUE DATE: 2018-05-26
SERVICED DATE: 2018-05-25
APPLICATION: awesomecheapcellphones.com
VERSION: 2.65
DOMAIN: WEB_HOSTED
NAME: Chester Rodriguez
ID: 89034
DESCRIPTION: MapApp can't find grandma's house
DUE DATE: 2018-05-27
SERVICED DATE: 2018-05-25
APPLICATION: MapApp
VERSION: 1.01
DOMAIN: PHONE_HOSTED
NAME: Kalpana Patel
ID: 54641
147
Chapter 7 Suppliers
NAME: Kalpana Patel
ID: 54641
DESCRIPTION: Cell phone's screen goes black
DUE DATE: 2018-06-01
SERVICED DATE: 2018-05-25
DEVICE: Rover
MODEL: RV100
SERIAL NUMBER: SN456742-R31
Short Problems
1) The Fibonacci sequence is the following series of numbers:
0, 1, 1, 2, 3, 5, 8, 13, 21 ...
where Fn = Fn-2 + Fn-1 is true for n >= 2. Write a supplier that
generates the Fibonacci numbers in order. Demonstrate in a main
program.
L ong Problems
1) File entertainmentCollection.txt contains records that
describe the entertainment collection discussed in homework
problem #2 from Chapter 6. The first field in each comma-
separated record is a tag documenting if the record pertains to a
DVD, an audio file, or an E-book. For example:
Articles: the, a, an
Prepositions: of, in, on, beside, under, above
Verbs: is, was, compiled
Noun: (all other words)
149
Chapter 7 Suppliers
The: ARTICLE
programmer: NOUN
compiled: VERB
a: ARTICLE
program: NOUN
150
CHAPTER 8
class Car
{
private String make;
private String model;
public Car(String ma, String mo)
{
make = ma;
model = mo;
}
@Override
public String toString() {return make + " " + model; }
}
151
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0_8
Chapter 8 Use in Traversing Objects
The traditional way to traverse an object is to call its Iterator's hasNext and next
methods in a loop.
Iterator<Car> it = cars.iterator();
while (it.hasNext())
System.out.println(it.next());
OUTPUT:
Nissan Sentra
Chevrolet Vega
Hyundai Elantra
OUTPUT:
Nissan Sentra
Chevrolet Vega
Hyundai Elantra
152
Chapter 8 Use in Traversing Objects
Suppose a program needs a class that can traverse a Java array of ints. This can be
accomplished by implementing the PrimitiveIterator interface where the first type is
Integer and the second type is IntConsumer.
The class should have a copy of the array and a cursor as fields.
@Override
public void forEachRemaining(IntConsumer c)
{
while (hasNext())
{
c.accept(array[cursor]);
cursor++;
}
}
153
Chapter 8 Use in Traversing Objects
The hasNext and next methods inherited from Iterator also need to be defined.
@Override
public boolean hasNext() { return cursor < array.length; }
@Override
public Integer next()
{
int i = 0;
if (hasNext())
{
i = array[cursor];
cursor++;
}
return i;
}
The class can now be used to traverse any Java array of ints. The Consumer needs to
be cast to IntConsumer to avoid an ambiguous reference, since the Iterator interface’s
forEachRemaining method accepts a Consumer<X>.
import java.util.*;
import java.util.function.*;
class IntIteratorGen implements
PrimitiveIterator<Integer,IntConsumer>
{
private int[] array;
private int cursor;
public IntIteratorGen(int... a)
{
cursor = 0;
154
Chapter 8 Use in Traversing Objects
array = Arrays.copyOf(a,a.length);
}
@Override
public void forEachRemaining(IntConsumer c)
{
while (hasNext())
{
c.accept(array[cursor]);
cursor++;
}
}
@Override
public boolean hasNext() { return cursor < array.length; }
@Override
public Integer next()
{
int i = 0;
if (hasNext())
{
i = array[cursor];
cursor++;
}
return i;
}
}
class TestPrimitiveIteratorGen
{
public static void main(String[] args)
{
IntIteratorGen it = new IntIteratorGen(1, 2, 3, 4, 5);
it.forEachRemaining((IntConsumer)x -> System.out.println(x));
}
}
155
Chapter 8 Use in Traversing Objects
PROGRAM OUTPUT:
1
2
3
4
5
The forEachRemaining, remove, and next methods have been defaulted, so only
hasNext, nextInt, nextLong, and nextDouble need to be defined.
The IntIterator, LongIterator, and DoubleIterator classes defined as follows can
be used to traverse Java arrays of int, long, and double primitive types, respectively.
156
Chapter 8 Use in Traversing Objects
import java.util.*;
import java.util.function.*;
class IntIterator implements PrimitiveIterator.OfInt
{
private int[] array;
private int cursor;
public IntIterator(int... a)
{
cursor = 0;
array = Arrays.copyOf(a,a.length);
}
@Override
public boolean hasNext() { return cursor < array.length; }
@Override
public int nextInt()
{
int i = 0;
if (hasNext())
{
i = array[cursor];
cursor++;
}
return i;
}
}
import java.util.*;
import java.util.function.*;
class LongIterator implements PrimitiveIterator.OfLong
{
private long[] array;
private int cursor;
157
Chapter 8 Use in Traversing Objects
public LongIterator(long... a)
{
cursor = 0;
array = Arrays.copyOf(a,a.length);
}
@Override
public boolean hasNext() { return cursor < array.length; }
@Override
public long nextLong()
{
long l = 0;
if (hasNext())
{
l = array[cursor];
cursor++;
}
return l;
}
}
import java.util.*;
import java.util.function.*;
class DoubleIterator implements PrimitiveIterator.OfDouble
{
private double[] array;
private int cursor;
public DoubleIterator(double... a)
{
cursor = 0;
array = Arrays.copyOf(a,a.length);
}
@Override
public boolean hasNext() { return cursor < array.length; }
@Override
158
Chapter 8 Use in Traversing Objects
import java.util.function.*;
class TestPrimitiveIteratorSpecializations
{
public static void main(String[] args)
{
IntIterator iit = new IntIterator(1, 2, 3, 4, 5);
iit.forEachRemaining((IntConsumer)x ->
System.out.println(x));
System.out.println();
LongIterator lit = new LongIterator(6, 7, 8, 9, 10);
lit.forEachRemaining((LongConsumer)x ->
System.out.println(x));
System.out.println();
DoubleIterator dit = new DoubleIterator(
20.1, 21.2, 22.3, 23.4, 24.5);
159
Chapter 8 Use in Traversing Objects
dit.forEachRemaining((DoubleConsumer)x ->
System.out.println(x));
}
}
PROGRAM OUTPUT:
1
2
3
4
5
6
7
8
9
10
20.1
21.2
22.3
23.4
24.5 java
160
Chapter 8 Use in Traversing Objects
A Spliterator object can be made to traverse the list through the use of the
spliterator’s forEachRemaining method. Note that this spliterator contains all elements
in the list.
OUTPUT:
The Spliterator interface’s trySplit method can be used to partition the list. The
result of the trySplit method is placed in a new spliterator named firstHalf. The
original spliterator contains the remainder of the list elements (the second half of the
list).
spliterator = cars.spliterator();
Spliterator<Car> firstHalf = spliterator.trySplit();
The Spliterator objects can then be traversed to display the first half and then the
second half of the list.
firstHalf.forEachRemaining (x ->
System.out.println("In 1st half: " + x));
spliterator.forEachRemaining(x ->
System.out.println("In 2nd half: " + x));
161
Chapter 8 Use in Traversing Objects
OUTPUT:
import java.util.*;
import java.util.function.Consumer;
class ArraySpliterator implements Spliterator<Integer> {
Integer[] array;
int cursor;
public ArraySpliterator(Integer... a)
{
array = Arrays.copyOf(a,a.length);
cursor = 0;
}
@Override
public int characteristics() {
return SIZED|SUBSIZED|ORDERED|NONNULL;
}
@Override
public long estimateSize() {
return array.length;
}
@Override
162
Chapter 8 Use in Traversing Objects
163
Chapter 8 Use in Traversing Objects
PROGRAM OUTPUT:
First half: 1
First half: 2
Second half: 3
Second half: 4
Second half: 5
OUTPUT:
Nissan Sentra
Chevrolet Vega
Hyundai Elantra
164
Chapter 8 Use in Traversing Objects
public MyInts(int... a)
{
array = Arrays.copyOf(a,a.length);
}
...
Class IntIter is a private inner class of MyInts that directly accesses the array field
and uses a cursor to navigate.
165
Chapter 8 Use in Traversing Objects
@Override
public void forEachRemaining(IntConsumer c)
{
while (hasNext())
{
c.accept(array[cursor]);
cursor++;
}
}
The non-default hasNext and next methods inherited from Iterator must also be
defined.
@Override
public boolean hasNext() { return cursor < array.length; }
@Override
public Integer next()
{
int i = 0;
if (hasNext())
{
i = array[cursor];
cursor++;
}
return i;
}
The MyInts class can be used to traverse any Java array of ints by calling its forEach
method and providing a consumer.
166
Chapter 8 Use in Traversing Objects
Note that the array can also be iterated by obtaining the MyInts object’s Iterator
and calling its forEachRemaining method.
my.iterator().forEachRemaining((IntConsumer)x ->
System.out.println(x));
The program in Listing 8-8 contains the complete definition of the MyInts class and
demonstrates its usage. A MyInts object is used to traverse and print an array of ints
containing the numbers 1 through 5. The first traversal is performed using the MyInts
object’s forEach method. The second traversal is performed by obtaining the MyInts
object’s Iterator and calling the Iterator's forEachRemaining method.
while (hasNext())
{
c.accept(array[cursor]);
cursor++;
}
}
@Override
public boolean hasNext() { return cursor < array.length; }
@Override
public Integer next()
{
int i = 0;
if (hasNext())
{
i = array[cursor];
cursor++;
}
return i;
}
}
}
class TestMyInts
{
public static void main(String[] args)
{
MyInts my = new MyInts(1, 2, 3, 4, 5);
my.forEach(x -> System.out.println(x));
System.out.println();
my.iterator().forEachRemaining((IntConsumer)x ->
System.out.println(x));
}
}
168
Chapter 8 Use in Traversing Objects
PROGRAM OUTPUT:
1
2
3
4
5
1
2
3
4
5
public MyIntsP(int... a)
{
array = Arrays.copyOf(a,a.length);
}
public PrimitiveIterator.OfInt iterator()
{
169
Chapter 8 Use in Traversing Objects
@Override
public int nextInt()
{
int i = 0;
if (hasNext())
{
i = array[cursor];
cursor++;
}
return i;
}
}
}
class TestMyIntsP
{
public static void main(String[] args)
{
MyIntsP my = new MyIntsP(1, 2, 3, 4, 5);
my.forEach(x -> System.out.println(x));
System.out.println();
my.iterator().forEachRemaining((IntConsumer)x ->
System.out.println(x));
}
}
170
Chapter 8 Use in Traversing Objects
PROGRAM OUTPUT:
1
2
3
4
5
1
2
3
4
5
The following example uses the Map interface’s forEach method to traverse the map
and print each employee’s name and salary:
OUTPUT:
171
Chapter 8 Use in Traversing Objects
PROGRAM OUTPUT:
blue
green
red
PROJECT 8: Payroll
Problem Statement
The MomAndPop startup company is committed to sharing profits with all their
employees. In addition to their base salary and sign-on bonus, employees share in
annual profits as follows:
–– The CEO receives 15%.
–– The Vice President receives 15%.
–– 30% is split between two district managers.
–– 40% is split between four programmers.
172
Chapter 8 Use in Traversing Objects
MomAndPop earned $120,000 last year. Write a program that will traverse each
employee of the MomAndPop company adding the portion of the profit to the bonus as
described in the preceding list and printing the employee’s name, base salary, and bonus
to the display.
Solution
An Employee class can be written to model each employee of MomAndPop. In addition to
name, baseSalary, and bonus, the class can have a multiplier field which determines
the portion of the profit to add to the sign-on bonus.
class Employee
{
String name;
double baseSalary;
double bonus;
double multiplier;
public Employee(String n, double bs, double bn, double m)
{
name = n;
baseSalary = bs;
bonus = bn;
multiplier = m;
}
@Override
public String toString()
{
return name + ": base salary = " + baseSalary
+ " bonus = " + bonus;
}
}
A MomAndPop class can be written to represent the company. The class contains
Employee fields for the CEO and Vice President and lists of employees for the district
managers and programmers.
173
Chapter 8 Use in Traversing Objects
0 CEO
1 Vice President
2–3 District managers
4–7 Programmers
174
Chapter 8 Use in Traversing Objects
A consumer which adds a portion of the profit to an Employee ’s bonus using its
multiplier can be written.
Company's forEach method can be called to iterate each Employee object. The
argument to forEach is a chain of consumers composed of applyProfit and a consumer
that prints the Employee object.
company.forEach(applyProfit
.andThen(x -> System.out.println(x)));
The complete program is shown and demonstrated as follows. The first iteration of
company selects Employee CEO and adds 15% of the $120,000 profit to his bonus resulting
in $93,000. The second iteration selects Employee vicePresident and adds 15% of the
profit to his or her bonus resulting in $43,000. The next two iterations select the two
district managers and distribute 30% of the profit among the two managers resulting in
$28,000 and $27,000, respectively. The next four iterations select the four programmers
and distribute 40% of the profit among the four programmers resulting in $14,000,
$13,500, $13,200, and $13,300, respectively.
import java.util.*;
import java.util.function.Consumer;
class Employee
{
String name;
double baseSalary;
double bonus;
double multiplier;
public Employee(String n, double bs, double bn, double m)
{
name = n;
baseSalary = bs;
176
Chapter 8 Use in Traversing Objects
bonus = bn;
multiplier = m;
}
@Override
public String toString() {
return name + ": base salary = " + baseSalary
+ " bonus = " + bonus;
}
}
177
Chapter 8 Use in Traversing Objects
178
Chapter 8 Use in Traversing Objects
class Payroll {
public static void main(String[] args)
{
Consumer<Employee> applyProfit = x ->
x.bonus += MomAndPop.profit * x.multiplier;
company.forEach(applyProfit
.andThen(x -> System.out.println(x)));
}
}
PROGRAM OUTPUT:
Short Problems
1) Use the forEachRemaining method to create a string containing
a comma-separated list of models from the list of cars shown in
Section 1 of this chapter.
179
Chapter 8 Use in Traversing Objects
L ong Problems
1) Write a class that iterates a string. Use its forEach method to
convert each character in the string to uppercase, and then print
the character (Hint: the class implements Iterable<Character>).
3) Write a Deck class that iterates a deck of cards. Use its forEach
method to print each card, and then produce a poker hand every
five cards. Use the predicates defined in homework problem #3
of Chapter 3 to determine if the hand is a straight, a flush, or a full
house.
180
CHAPTER 9
Use in Collections
Functional interfaces can be used to perform several important operations on
collections.
If the predicate supplied to the removeIf method is true for an element of the
collection, that element is removed from the collection. If the Collection object’s
Iterator does not support the remove operation, an UnsupportedOperationException
will be thrown. In the following example, a Predicate<String> is used to remove all
elements beginning with “S” from the collection.
OUTPUT:
Random
181
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0_9
Chapter 9 Use in Collections
The following example sets each element of int array iarr equal to its subscript.
OUTPUT:
0
1
2
3
The following example sets each element of long array larr equal to 5.
IntToLongFunction gen5 = x -> 5;
long[] larr = new long[4];
Arrays.setAll(larr, gen5);
for (long l : larr)
System.out.println(l);
OUTPUT:
5
5
5
5
182
Chapter 9 Use in Collections
The following example sets each element of double array darr equal to a random
number between 0.0 and 1.0.
OUTPUT:
0.40630531311035156
0.9486522674560547
0.8432348370552063
0.7855687737464905
The following example populates an array of strings such that each element contains
the letter “S” repeated the number of subscript times.
IntFunction<String> is = x -> {
String s = "";
for (int i=0; i<=x; ++i)
s += "S";
return s;
};
OUTPUT:
S
SS
SSS
SSSS
183
Chapter 9 Use in Collections
OUTPUT:
4
3
2
1
All the element in a map can also be modified using the default replaceAll method
and a BiFunction.
The following example adds the prefix “Mr. ” to each entry of a map.
map.replaceAll(bi);
map.forEach( (x,y) -> System.out.println(y + " " + x));
OUTPUT:
184
Chapter 9 Use in Collections
Arrays.parallelPrefix(arr, op);
for (int i: arr)
System.out.println(i);
185
Chapter 9 Use in Collections
OUTPUT:
2
6
24
72
The Map interface’s default compute method performs its computation on an entry in
a map using the specified BiFunction. If the function results in null, the entry is removed
from the map.
In the following example, a mapping BiFunction is defined that returns null if the
value is null (or if the key is not present), and returns the value divided by 4 otherwise.
The first call to the compute method divides the value 32 by 4 resulting in 8. The
second call results in null since the value corresponding to the key “GREEN” is null,
and the entry “GREEN” is removed from the map. The third call operates on the key
“YELLOW” which is not present and results in null, so nothing is added to the Map.
186
Chapter 9 Use in Collections
System.out.println(map.compute("GREEN", bin));
System.out.println(map.compute("YELLOW", bin));
System.out.println();
map.forEach( (x,y) -> System.out.println(x + " " + y));
OUTPUT:
8
null
null
RED 8
System.out.println();
map.forEach( (x,y) -> System.out.println(x + " " + y));
187
Chapter 9 Use in Collections
OUTPUT:
2
null
null
GREEN null
RED 2
The default computeIfAbsent method performs its computation if the entry is not
present or its value is null. If the function results in null, no entry is added to the map.
It accepts a Function object instead of a BiFunction, since no existing value needs to be
processed.
In the following example, the value resulting from the mapping function fi is the
length of the key and the value resulting from the mapping function finull is null.
The first call to the computeIfAbsent method performs no computation since entry
“RED” is present and its value is not null. The second call computes a new value 5
which is the length of the key “GREEN”, since the original value was null. The third call
computes 6 which is the length of the key “YELLOW” which is absent from the map.
Although entry “BLACK” is absent from the map, the fourth call does not add an entry
for it because mapping function finull results in null.
System.out.println();
map.forEach( (x,y) -> System.out.println(x + " " + y));
188
Chapter 9 Use in Collections
OUTPUT:
2
5
6
null
GREEN 5
RED 2
YELLOW 6
class MyClass
{
int i1;
int i2;
String s;
public MyClass(int x, int y, String z)
{
i1 = x;
i2 = y;
s = z;
}
@Override
public String toString() { return i1 + " " + i2 + " " + s; }
}
189
Chapter 9 Use in Collections
a program can create a map whose values are of the type MyClass and whose keys are of
type String.
A BiFunction can be written that returns a new MyClass composed of the i1 and s
fields of the existing MyClass value and the i2 field of a new MyClass value.
BiFunction<MyClass,MyClass,MyClass> changeI2 =
(ov,nv) -> new MyClass(ov.i1, nv.i2, ov.s);
A different mapping BiFunction can be written that returns a new MyClass composed
of the i1 and i2 fields of the existing MyClass value and the s field of a new MyClass value.
BiFunction<MyClass,MyClass,MyClass> changeS =
(ov,nv) -> new MyClass(ov.i1, ov.i2, nv.s);
BiFunction changeI2 and the merge method can be used to change field i1 of entry
“k1” to 5. BiFunction changeS and the merge method can be used to change field s of
entry “k1” to “Cat”. Entry “k2” does not exist, so a new entry is created with the specified
value without using the mapping function.
System.out.println();
m.forEach( (x,y) -> System.out.println(x + " " + y));
OUTPUT:
1 5 Dog
1 5 Cat
6 7 Bird
k1 1 5 Cat
k2 6 7 Bird
190
Chapter 9 Use in Collections
OUTPUT:
Rose
Unlike the List interface, the Set interface does not provide a replaceAll method.
191
Chapter 9 Use in Collections
Solution
A Vehicle class can be written which contains fields for the make, model, and year of a
vehicle.
class Vehicle
{
String make;
String model;
int year;
public Vehicle(String ma, String mo, int y)
{
make = ma;
model = mo;
year = y;
}
@Override
public String toString()
{
return year + " " + make + " " + model;
}
}
A License class can be written with fields for the expiration date, insurance
policy, privilege status, and the list of vehicles associated with the license. The class
needs constructors that will accept either a variable length list of vehicles or an
ArrayList<Vehicle> object. The class should also have a copy constructor.
192
Chapter 9 Use in Collections
expDate = e;
insurance = i;
status = STATUS.ACTIVE;
vehicles = new ArrayList<>();
for (Vehicle r : v)
vehicles.add(r);
}
public License(LocalDate e, String i, STATUS s,
ArrayList<Vehicle> vs)
{
expDate = e;
insurance = i;
status = s;
vehicles = new ArrayList<>(vs);
}
public License(License l)
{
expDate = l.expDate;
insurance = l.insurance;
status = l.status;
vehicles = new ArrayList<>(l.vehicles);
}
@Override
public String toString()
{
return expDate + " " + insurance + " " + status
+ " " + vehicles;
}
}
The program can use a map to store the driver’s license database where the key is the
license id and the value is a license record.
Suppliers can be written to prompt the user for the driver’s license id, the insurance
policy, the expiration date, and the vehicle information.
193
Chapter 9 Use in Collections
The Map interface’s merge method can be used to add a driver to the database. If an entry
whose key matches the license id is not present in the map, an entry will be added with the
provided value that only specifies the expiration date. If the entry is present, the BiFunction
returns the existing License value, so attempting to add the driver will have no effect.
194
Chapter 9 Use in Collections
The Map class’s computeIfPresent method can be used to remove a driver from the
database. If an entry whose key matches the license id is present, the BiFunction returns
a null value, and the ComputeIfPresent method removes the entry from the map.
To change a driver’s insurance policy, the Map class’s merge method can be used
to merge an existing License value with one containing a new insurance policy. The
BiFunction creates a License object using the insurance field from the new value and
the remaining fields from the existing value.
Since suspending an existing license does not require input of any License fields,
the Map class’s computeIfPresent method can be used. The BiFunction modifies the
existing value, setting the status field to STATUS.SUSPENDED.
To renew a driver’s license, a new License value containing a new expiration date
can be merged with an existing value. The BiFunction can also set the status field in the
new License object to STATUS.ACTIVE.
195
Chapter 9 Use in Collections
System.out.println(d.merge(licenseId.get(),
new License(expDate.get(), null),
renew));
To add a vehicle to a driver’s license, an existing License value can be merged with
one containing the new vehicle. The BiFunction creates a License object that adds the
vehicle from the new value to any existing vehicles.
System.out.println(d.merge(licenseId.get(),
new License(null, null,vehicle.get()),
addVehicle));
To add a vehicle to a driver’s license, an existing License value can be merged with
one containing the vehicle to be removed. The BiFunction can use the ArrayLists
object’s removeIf method to remove the vehicle from the list.
196
Chapter 9 Use in Collections
June 11, 2022. Then, a 2017 Honda Accord is added to the driver’s license. Next, the
Accord is removed from the license. Then, driver 123 is removed from the database.
The user then quits, and the program terminates.
import java.util.*;
import java.util.function.*;
import java.time.*;
enum STATUS {ACTIVE, SUSPENDED}
class Vehicle
{
String make;
String model;
int year;
class License
{
LocalDate expDate;
String insurance;
STATUS status;
ArrayList<Vehicle> vehicles;
197
Chapter 9 Use in Collections
expDate = e;
insurance = i;
status = STATUS.ACTIVE;
vehicles = new ArrayList<>();
for (Vehicle r : v)
vehicles.add(r);
}
public License(License l)
{
expDate = l.expDate;
insurance = l.insurance;
status = l.status;
vehicles = new ArrayList<>(l.vehicles);
}
@Override
public String toString()
{
return expDate + " " + insurance + " " + status
+ " " + vehicles;
}
}
class DMV
{
public static void main(String[] args)
{
Map<String,License> d = new TreeMap<>();
198
Chapter 9 Use in Collections
System.out.println(
"Welcome to the Department of Motor Vehicles.");
199
Chapter 9 Use in Collections
System.out.print("Enter month:");
Month month = Month.valueOf(input.nextLine());
System.out.print("Enter day:");
int day = Integer.parseInt(input.nextLine());
return LocalDate.of(year, month, day);
};
int operation = 0;
while (operation != 7)
{
operation = selectOperation.get();
switch (operation)
{
case 0: // Add Driver
BiFunction<License,License,License> addDriver
= (ov,nv) -> ov;
System.out.println(
d.merge(licenseId.get(),
new License(expDate.get(), null),
addDriver));
break;
200
Chapter 9 Use in Collections
System.out.println(
d.computeIfPresent(licenseId.get(),remove));
break;
System.out.println(
d.computeIfPresent(licenseId.get(),suspend));
break;
System.out.println(
d.merge(licenseId.get(),
new License(expDate.get(), null),
renew));
break;
201
Chapter 9 Use in Collections
System.out.println(
d.merge(licenseId.get(),
new License(null, null,vehicle.get()),
addVehicle));
break;
System.out.println(
d.merge(licenseId.get(),
new License(null, null,vehicle.get()),
removeVehicle));
}
}
}
}
202
Chapter 9 Use in Collections
203
Chapter 9 Use in Collections
Short Problems
1) Use a setAll method of the Arrays class to populate each
element in a Java array of ints with the length of the array minus its
subscript. Then, use the removeIf method to remove the element
whose value is 1. Print the resulting array.
204
Chapter 9 Use in Collections
2) Write a map with Integer keys and Integer values. Both the keys
and the values are initially the numbers 1 through 5. Using the
techniques described in this chapter, triple the values of all entries
in the map whose keys contain odd values. Print the resulting
map.
class Car
{
String make;
String model;
public Car(String ma, String mo)
{
make = ma;
model = mo;
}
@Override
public String toString() {return make + " " + model; }
}
–– Insert a Hyundai Excel with serial number “S123” into the map
–– Insert a Buick Skylark with serial number “S456” into the map
–– Insert a Toyota Prius with serial number “S789” into the map
When the merge operations are finished, print the resulting map.
205
Chapter 9 Use in Collections
Long Problems
1) Simulate a round of poker where one player competes against the
dealer. The player randomly draws five cards and adds them to his
or her hand. If any of the cards drawn are already present in the
hand, the card is removed and a new card is drawn. After the cards
are drawn, it is desirable to remove cards from the hand whose
face value is ten or less, and replace them with new cards drawn
from the deck. The player may remove up to three cards and
replace them with new ones. If any of the cards drawn are already
present in the hand, the card is removed and a new card is drawn.
When then player has five cards after the second series of draws,
the round is finished. If the player has a straight, a flush, or a full
house, he or she wins. Otherwise, the dealer wins.
206
Chapter 9 Use in Collections
–– Add the book Dracula by Bram Stoker for $14.99 with 13 copies available
207
CHAPTER 10
@FunctionalInterface
public interface Comparator<T>
{
int compare(T o1, T o2);
...
}
Suppose a program needs to compare two names using consonants only. It can
define a method to remove all vowels from a name.
209
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0_10
Chapter 10 Use in Comparing Objects
System.out.println(byConsonants.compare("Larry", "Libby"));
OUTPUT:
16
The following example creates a Comparator<Integer> object that uses the Integer
class’s compareTo method. The compareTo method returns 1 if the calling object is greater
than its argument, -1 if the calling object is less than its argument, and 0 if they are the
same.
If the integer values 1000 and 1002 are compared using this comparator, -1 will be
displayed.
System.out.println(byIntCompareTo.compare(1000,1002));
OUTPUT:
-1
In order to see the difference of two integers as a result of the comparison, another
comparator can be written that subtracts the numbers and displays -2 instead.
OUTPUT:
-2
210
Chapter 10 Use in Comparing Objects
A comparator can be created that compares two names using natural order which
for String objects lexicographically compares each character. The following example
displays -8 since “a” precedes “i” in the alphabet by eight letters.
OUTPUT:
-8
OUTPUT:
Passing a null string to the Comparator object’s compare method will cause a
NullPointerException. This can be prevented by passing the comparator to the
nullsFirst method which creates a new comparator. A comparator built by the
nullsFirst method treats nulls as being less than non-null objects, so the following
statement displays 1.
System.out.println(Comparator.nullsFirst(byConsonants)
.compare("Larry", null));
OUTPUT:
211
Chapter 10 Use in Comparing Objects
The nullsLast method creates a comparator that treats nulls as being greater than
non-null objects, so the following statement displays -1.
System.out.println(Comparator.nullsLast(byConsonants)
.compare("Larry", null));
OUTPUT:
-1
System.out.println(byConsonants.reversed()
.compare("Larry", "Libby"));
OUTPUT:
-16
212
Chapter 10 Use in Comparing Objects
class Student
{
String name;
Integer id;
Double gpa;
public Student(String n, int i, double g)
{
name = n;
id = i;
gpa = g;
}
@Override
public String toString() { return name + " " + id + " " + gpa; }
Since the Student class does not implement the Comparable<Student> interface,
a comparator cannot be built by simply calling the object’s compareTo method, as was
done by the byConsonants comparator. Also, a program may need to compare two
students based on name, id, or GPA.
Suppose a program needs to compare students based on grade point average. The
program should write a function that extracts the gpa field from a Student object. This
will be the key used for comparison of two students.
The byGpa Comparator displays 1 when comparing students s1 and s2, since 3.82 is
greater than 3.76.
213
Chapter 10 Use in Comparing Objects
OUTPUT:
Since the Java compiler is able to infer the types of the Function object based on the
type of the Comparator object and the return type of the function’s lambda, the lambda
expression is typically written in the Comparator object’s argument list.
OUTPUT:
-1
A third comparator can be defined that compares students based on name. This
comparator lexicographically compares each character in the two name strings and
displays -8, since “a” precedes “i” in the alphabet by eight letters.
Comparator<Student> byName
= Comparator.comparing(x -> x.name);
System.out.println(byName.compare(s1,s2));
OUTPUT:
-8
Since the Double, Integer, and String classes implement the Comparable interface,
the previous examples were able to use the comparing method signature that accepts
a single argument which is the key extraction function. If the key is not Comparable, or
the program needs to override the natural ordering of the Comparable key, a comparator
must be provided as the second parameter to the comparing method.
214
Chapter 10 Use in Comparing Objects
Comparator<Student> byNameConsonants =
Comparator.comparing( x -> x.name,
(x,y) -> removeVowels(x).compareTo(
removeVowels(y)));
System.out.println(byNameConsonants.compare(s1,s2));
OUTPUT:
16
Comparator<Student> byGpaCeil =
Comparator.comparing( x -> x.gpa,
(x,y) -> (int)(Math.ceil(x)
- Math.ceil(y)));
System.out.println(byGpaCeil.compare(s1,s2));
OUTPUT:
class ListWrapper
{
List<Integer> list;
215
Chapter 10 Use in Comparing Objects
public ListWrapper(Integer... i)
{
list = Arrays.asList(i);
}
}
If a comparator is written using only a function which extracts the list from a
ListWrapper object, a compilation error occurs because lists are not comparable.
Comparator<ListWrapper> byList
= Comparator.comparing(x -> x.list); // ERROR: list not
// comparable
A comparator must be provided to specify how to compare the lists. The following
comparator compares two List<Integer> objects by their first elements:
Comparator<List<Integer>> byElement0 =
(x,y) -> x.get(0).compareTo(y.get(0));
This comparator can be used as the second argument to the comparing method
in Comparator byList to specify that the lists extracted from the ListWrapper objects
should be compared by their first elements. The following example outputs 1 since 2
(list1.list element 0) is 1 greater than 1 (list2.list element 0).
Comparator<ListWrapper> byList
= Comparator.comparing(x -> x.list, byElement0);
System.out.println(byList.compare(list1,list2));
OUTPUT:
216
Chapter 10 Use in Comparing Objects
OUTPUT:
The comparingInt method compares two Integers based on natural ordering using
the key extracted by a ToIntFunction.
The comparingInt method expects an argument of type ToIntFunction<? super
T>. If a lambda expression were passed as the argument instead of a reference to
ToIntFunction<Student>, the compiler would not be able to infer that the type of
lambda parameter x is Student, and an error would be generated.
217
Chapter 10 Use in Comparing Objects
The type of lambda parameter x can be provided in order to avoid the error. In the
following example, -1 is displayed because the id value 1000 is less than 1001.
OUTPUT:
-1
The comparingLong method compares two Longs based on natural ordering using
the key extracted by a ToLongFunction.
The following LongWrapper class wrap a Long value.
class LongWrapper
{
Long l;
public LongWrapper(long a) { l = a; }
}
In the following example, 0 is displayed because the value of l is 4 for both Longs.
OUTPUT:
default Comparator<T>
thenComparing(Comparator<? super T> other);
default <U extends Comparable<? super U>> Comparator<T>
218
Chapter 10 Use in Comparing Objects
Suppose two students were named “Joseph” and had with the same grade point
average.
System.out.println(byName.compare(s1, s2));
OUTPUT:
The students could then be further compared by grade point average by specifying
the byGpa Comparator in the thenComparing call of the comparator’s method chain. The
comparisons are performed in the reverse of the order they are listed, and only if the
prior comparison returns 0. Since comparison by name returns 0, comparison by GPA is
performed. This also returns 0.
System.out.println(byName.thenComparing(byGpa)//byName->byGpa
.compare(s1, s2));
OUTPUT:
219
Chapter 10 Use in Comparing Objects
System.out.println(byName.thenComparing(byId)//byName->byGpa->byId
.thenComparing(byGpa)
.compare(s1, s2));
OUTPUT:
-1
If the order of the thenComparing calls in the comparator chain were switched,
the byGpa comparison would not be performed because the byId comparison already
resulted in a non-zero value. In the following example, byName is performed first
resulting in 0, and then byId is performed resulting in -1 since 1000 is less than 1002. The
byGpa comparison is not performed.
System.out.println(byName.thenComparing(byGpa)
.thenComparing(byId)
.compare(s1, s2));
OUTPUT:
-1
The previous examples have used the thenComparing signature that accepts a
comparator. If natural order comparisons are desired, the thenComparing signature that
accepts a function can be used.
OUTPUT:
-1
220
Chapter 10 Use in Comparing Objects
Comparator<Student> byNameConsonants
= Comparator.comparing( x -> x.name,
(x,y) ->
removeVowels(x).compareTo(removeVowels(y)));
Comparator<Double> byCeil =
(x,y) -> (int)(Math.ceil(x) - Math.ceil(y));
OUTPUT:
-2
221
Chapter 10 Use in Comparing Objects
In the following example, the byGpa Comparator result determines that students s5
and s6 are equal. Next, thenComparingInt extracts the id field and compares 1006 to
1007 by natural ordering, which results in -1.
OUTPUT:
-1
In the following example, the byName Comparator result determines that students s7
and s8 are equal. Next, thenComparingDouble extracts the gpa field and compares 3.86 to
3.69 by natural ordering, which results in 1.
OUTPUT:
222
Chapter 10 Use in Comparing Objects
Suppose a program needs to sort the following list of students first by GPA ceiling,
then by name, and then by id:
In its first version, the program sorts the list by GPA ceiling only. A comparator is
written that performs the GPA ceiling comparison. The comparator is passed to the List
object’s sort method. The resulting list is grouped by GPA ceiling (contrasted by bold),
but the entries within the groups are unsorted.
Comparator<Student> byGpaCeil =
Comparator.comparing( x -> x.gpa,
(x,y) -> (int)(Math.ceil(x)
- Math.ceil(y)));
students.sort(byGpaCeil);
students.forEach(x-> System.out.println(x));
OUTPUT:
In its next version, the program further sorts the list by name. This is accomplished
by adding a thenComparing call that extracts the student’s name to the end of the
comparator chain. The 4.0 and 2.0 GPA ceiling groups are now sorted, but the 3.0 GPA
ceiling group is unsorted since both students are named “Annie”.
223
Chapter 10 Use in Comparing Objects
students.sort(byGpaCeil
.thenComparing(x -> x.name));
students.forEach(x-> System.out.println(x));
OUTPUT:
In its final version, the program further sorts the list by id. This is accomplished by
adding a thenComparing call that extracts the student’s id to the end of the comparator
chain. The list is now fully sorted.
students.sort(byGpaCeil
.thenComparing(x -> x.id)
.thenComparing(x -> x.name));
students.forEach(x-> System.out.println(x));
OUTPUT:
Student[] students = {
new Student("Joseph" , 1623, 3.54),
new Student("Annie" , 1923, 2.94),
224
Chapter 10 Use in Comparing Objects
The students array is copied in order to perform two different sorts on its elements.
The Array class’s static sort method sorts a Java array using a specified comparator.
In the following example, the Java array is sorted in the same order as the list.
Comparator<Student> byGpaCeil =
Comparator.comparing( x -> x.gpa,
(x,y) -> (int)(Math.ceil(x)
- Math.ceil(y)));
Arrays.sort(students,
byGpaCeil
.thenComparing(x -> x.id)
.thenComparing(x -> x.name));
OUTPUT:
The Array class’s sort method can be used to sort a range of elements. In the following
example, the second through fourth elements of the Java array are sorted by name.
Arrays.sort(studentsCopy, 2, 5,
Comparator.comparing(x -> x.name));
225
Chapter 10 Use in Comparing Objects
OUTPUT:
A binary search can be performed on a sorted Java array using the Arrays class if a
comparator is provided. Suppose a program models a student body that consists of 1000
students named “S000” through “S999”.
The Array class’s binarySearch method can be used to quickly search for the
student with name “S647” if a comparator which compares students by name is
specified. In the following code, the student with name “S647” is found at index 647 of
the student body array.
OUTPUT:
226
Chapter 10 Use in Comparing Objects
OUTPUT:
Comparator<Map.Entry<String,String>> cmap =
Map.Entry.comparingByKey();
System.out.println(cmap.compare(cat, chicken));
OUTPUT:
-7
227
Chapter 10 Use in Comparing Objects
Comparator<Map.Entry<String,String>> cmapCons =
Map.Entry.comparingByKey(byConsonants);
System.out.println(cmapCons.compare(cat, chicken));
OUTPUT:
12
Comparator<Map.Entry<String,String>> cval =
Map.Entry.comparingByValue();
System.out.println(cval.compare(cat, chicken));
OUTPUT:
-5
Comparator<Integer> abscompare =
Comparator.comparing(x -> Math.abs(x));
BinaryOperator<Integer> bigint =
228
Chapter 10 Use in Comparing Objects
BinaryOperator.maxBy(abscompare);
BinaryOperator<Integer> smallint =
BinaryOperator.minBy(abscompare);
System.out.println(bigint.apply(2,-5));
System.out.println(smallint.apply(2,-5));
OUTPUT:
-5
2
–– Price
–– Number of bedrooms
–– Property size
Customers find it very frustrating when they are shown homes in which they are
not interested, so Reba wants a program that will eliminate homes that are not in their
desired community, if any, and list the remaining homes in order of the top three of the
customer’s priorities. The fractional components of both property size and distance are
ignored while sorting the listing.
229
Chapter 10 Use in Comparing Objects
Solution
A class that contains values for the community and the customer priorities can be
written.
class Home
{
String community;
double price;
int numBedrooms;
double acres;
double schoolDistance;
double trainDistance;
public Home(String co, double pr, int bed, double ac,
double sd, double td)
{
community = co;
price = pr;
numBedrooms = bed;
acres = ac;
schoolDistance = sd;
trainDistance = td;
}
@Override
public String toString()
{
return numBedrooms + " bedroom house in " + community
+ " for $" + price + " on " + acres + " acres\n "
+ schoolDistance + " miles from school "
+ trainDistance + " miles from train";
}
}
The Home class can be used to create a list of homes that Reba has available for sale.
A separate Comparator can be created for each priority using the Comparator.
comparing method and a function that selects the appropriate field in the Home object.
The byPrice Comparator compares homes by price in ascending order. Since homes
with more bedrooms should be listed first, the byBedrooms Comparator calls the
reversed method to compare homes by number of bedrooms in descending order.
Comparator<Home> byPrice
= Comparator.comparing(x -> x.price);
Comparator<Home> byBedroomsAsc
= Comparator.comparing(x -> x.numBedrooms);
Comparator<Home> byBedrooms
= byBedroomsAsc.reversed();
Since the fractional component of property size, school distance, and train distance
can be ignored, a comparator that extracts the ceiling of each floating point number
can be passed into each comparing method. Note that the order is reversed in the
byProperty Comparator so that larger properties are listed first.
Comparator<Home> byProperty =
Comparator.comparing( x -> x.acres,
(x,y) -> (int)(Math.ceil(y) -
Math.ceil(x)));
Comparator<Home> bySchool =
Comparator.comparing( x -> x.schoolDistance,
(x,y) -> (int)(Math.ceil(x) -
Math.ceil(y)));
Comparator<Home> byTrain =
Comparator.comparing( x -> x.trainDistance,
(x,y) -> (int)(Math.ceil(x) -
Math.ceil(y)));
231
Chapter 10 Use in Comparing Objects
Reba only wants to show customers homes in the community in which they are
interested, if any. Therefore, a copy of the home listing with homes from the other
communities can be created. This can be accomplished using the ArrayList class’s
removeIf method with a predicate that checks the home’s community. The comm object
is a supplier that prompts the Reba to enter the customer’s desired community.
Reba then asks the customer their three top priorities and selects from one of the five
available Comparators. PriorityNumber is a supplier that prompts Reba to enter one of
the customer’s priorities.
Reba can now create a listing sorted by the three priorities in order using the
thenComparing method.
232
Chapter 10 Use in Comparing Objects
homesTemp.sort(priority1
.thenComparing(priority2)
.thenComparing(priority3)); homesTemp.forEach(x ->
System.out.println(x));
233
Chapter 10 Use in Comparing Objects
class RealEstateBroker
{
public static void main(String[] args)
{
System.out.println("Home Listings for Forest Acres,"
+ " Happy Gardens, and Comfy Condos\n");
List<Home> homes = Arrays.asList(
new Home( "Forest Acres" , 425000.0, 4, 1.7, 5.1, 4.5),
new Home( "Happy Gardens", 510000.0, 4, 2.3, 5.1, 4.5),
new Home( "Comfy Condos" , 190000.0, 2, 0.9, 2.1, 4.5),
new Home( "Comfy Condos" , 190000.0, 2, 0.9, 0.7, 4.5),
new Home( "Happy Gardens", 470000.0, 4, 2.1, 5.1, 4.5),
new Home( "Forest Acres" , 345000.0, 3, 1.5, 3.2, 5.9),
new Home( "Happy Gardens", 375000.0, 3, 1.5, 2.3, 2.4),
new Home( "Comfy Condos" , 190000.0, 2, 0.3, 0.5, 2.4)
);
Comparator<Home> byPrice =
Comparator.comparing(x -> x.price);
Comparator<Home> byBedrooms =
Comparator.comparing(x -> x.numBedrooms);
Comparator<Home> byBedroomsRev =
byBedrooms.reversed();
Comparator<Home> byProperty =
Comparator.comparing( x -> x.acres,
(x,y) -> (int)(Math.ceil(y) - Math.ceil(x)));
Comparator<Home> bySchool =
Comparator.comparing( x -> x.schoolDistance,
(x,y) -> (int)(Math.ceil(x) - Math.ceil(y)));
Comparator<Home> byTrain =
Comparator.comparing( x -> x.trainDistance,
(x,y) -> (int)(Math.ceil(x) - Math.ceil(y)));
234
Chapter 10 Use in Comparing Objects
235
Chapter 10 Use in Comparing Objects
priority1 = priority.get();
priority2 = priority.get();
priority3 = priority.get();
homesTemp.sort(priority1
.thenComparing(priority2)
.thenComparing(priority3));
homesTemp.forEach(x -> System.out.println(x));
};
}
Reba’s first customer wants a home in Happy Gardens and is most concerned with
the number of bedrooms, then the property size, and then the price. Only the Happy
Gardens homes are listed with the four-bedroom homes displayed before the three-
bedroom homes. Within the four-bedroom home group, the ceilings of both property
sizes are the same, so price is used to list the 470,000 home first.
236
Chapter 10 Use in Comparing Objects
Home Listings for Forest Acres, Happy Gardens, and Comfy Condos
Select community:
0 - Forest Acres
1 - Happy Gardens
2 - Comfy Condos
3 - any: 1
Select a Priority:
0 - price
1 - number of bedrooms
2 - property size
3 - distance to nearest school
4 - distance to train station: 1
Select a Priority:
0 - price
1 - number of bedrooms
2 - property size
3 - distance to nearest school
4 - distance to train station: 2
Select a Priority:
0 - price
1 - number of bedrooms
2 - property size
3 - distance to nearest school
4 - distance to train station: 0
4 bedroom house in Happy Gardens for $470000.0 on 2.1 acres
5.1 miles from school 4.5 miles from train
4 bedroom house in Happy Gardens for $510000.0 on 2.3 acres
5.1 miles from school 4.5 miles from train
3 bedroom house in Happy Gardens for $375000.0 on 1.5 acres
2.3 miles from school 2.4 miles from train
237
Chapter 10 Use in Comparing Objects
Reba’s next customer doesn’t care which community the home is in and cares mostly
about the number of bedrooms, then the home price, and then the distance to the
nearest school. All homes are listed with the four-bedroom homes listed first followed by
the three-bedroom homes and then the two-bedroom homes. Within the two-bedroom
home group, all homes are the same price, so distance to school is used to list the 0.7 and
0.5 mile homes before the 2.1 mile home.
Home Listings for Forest Acres, Happy Gardens, and Comfy Condos
Select community:
0 - Forest Acres
1 - Happy Gardens
2 - Comfy Condos
3 - any :3
Select a Priority:
0 - price
1 - number of bedrooms
2 - property size
3 - distance to nearest school
4 - distance to train station: 1
Select a Priority:
0 - price
1 - number of bedrooms
2 - property size
3 - distance to nearest school
4 - distance to train station: 0
Select a Priority:
0 - price
1 - number of bedrooms
2 - property size
3 - distance to nearest school
4 - distance to train station: 3
4 bedroom house in Forest Acres for $425000.0 on 1.7 acres
5.1 miles from school 4.5 miles from train
4 bedroom house in Happy Gardens for $470000.0 on 2.1 acres
238
Chapter 10 Use in Comparing Objects
Short Problems
1) Write a comparator that compares two integers based on their
natural ordering but treats all even integers as equal.
@Override
public int compareTo(A a) { return s1.compareTo(a.s1); }
}
239
Chapter 10 Use in Comparing Objects
3) Sort the following list of A objects first by field s1, then by the
reverse ordering of field s2, and then by field i:
Sally Jones 3
Sally Seashell 4
Harry Jones 1
Harry Homeowner 5
Harry Homeowner 2
Long Problems
1) Design a rock-paper-scissors game that uses a comparator
to compare a player’s selection vs. the computer’s selection
according to the following rules:
Write a supplier which prompts the user for his or her selection,
another supplier which randomly generates the computer’s
selection, and a third supplier which prompts the user if he or she
wants to play again. Keep score of the winner of each round. Print
the final score and the overall winner at the end of the competition.
240
Chapter 10 Use in Comparing Objects
Avg Hits HR RBI
Ty Cobb .367 4191 117 1961
Rogers Hornsby .358 2930 301 1584
Tris Speaker .346 3514 117 1529
Ted Williams .345 2654 521 1839
Babe Ruth .344 2873 714 2213
Lou Gehrig .340 2721 493 1995
George Sisler .340 2812 102 1178
Tony Gwynn .338 3141 135 1138
Stan Musial .331 3630 475 1951
Wade Boggs .328 3010 118 1024
Rod Carew .328 3053 92 1015
Honus Wagner .327 3415 101 1732
Joe DiMaggio .325 2214 361 1539
Pete Rose .303 4256 160 1314
Hank Aaron .305 3771 755 2297
Derek Jeter .310 3465 260 1311
Willie Mays .302 3283 660 1903
Alex Rodriguez .295 3115 696 2086
Barry Bonds .298 2935 762 1996
Albert Pujols .302 3081 644 1995
241
Chapter 10 Use in Comparing Objects
Write a comparator that sorts the deck by face value and then by
suite. In other words, the first card should be the Two of Hearts
followed by the Two of Clubs, then the Two of Spades, then the
Two of Diamonds, then the Three of Hearts followed by the Three
of Clubs, etc.
242
CHAPTER 11
Use in Optionals
It is useful to wrap an object inside an Optional to avoid checking for nullness.
Optionals can also be used inside method chains to simplify the logic of a program.
The Optional class provides the following static methods which are used to create
Optionals of type T:
The of method creates an Optional from a non-null argument. If called with a null
argument, it throws a NullPointerException.
try {
Optional<String> o1 = Optional.of(null);
} catch (NullPointerException e) {
System.out.println("NullPointerException");
}
Optional<String> o2 = Optional.of("Hello");
System.out.println("OK");
243
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0_11
Chapter 11 Use in Optionals
OUTPUT:
NullPointerException
OK
If the object wrapped by an Optional can be null, the ofNullable method should be
used.
Optional<String> o3 = Optional.ofNullable(null);
System.out.println("OK");
Optional<String> o4 = Optional.ofNullable("Hello");
System.out.println("OK");
OUTPUT:
OK
OK
The empty method can also be used to create an Optional that wraps a null object.
Optional<String> o5 = Optional.empty();
boolean isPresent();
if (o5.isPresent())
System.out.println("o5 is non-null");
In Java 11, the isEmpty method was added. This method returns true if the Optional
wraps a null object and false otherwise. This method gives the opposite result of
isPresent.
244
Chapter 11 Use in Optionals
The following example displays “Empty” since Optional imNull contains a null
value.
OUTPUT:
Empty
T get();
try {
o5.get();
} catch (NoSuchElementException e) {
System.out.println("NoSuchElementException");
}
OUTPUT:
NoSuchElementException
if (o4.isPresent())
System.out.println(o4.get());
if (o5.isPresent())
System.out.println(o5.get());
else
System.out.println("o5 is null");
if (!o5.isEmpty())
245
Chapter 11 Use in Optionals
System.out.println(o5.get());
else
System.out.println("o5 is null");
OUTPUT:
Hello
o5 is null
o5 is null
T orElse(T other);
T orElseGet(Supplier<? extends T> supplier);
Optional<T> or(Supplier<? extends Optional<? extends T> > supplier);
<X extends Throwable> T orElseThrow(Supplier<? extends X>
exceptionSupplier);
T orElseThrow(); [JAVA10]
and the program needs to pick the first string that is non-null. That is, it needs to perform
the following logic:
String s = t;
if (t == null)
s = u;
The Optional class’s orElse method provides a value to use if the Optional contains
a null.
246
Chapter 11 Use in Optionals
try {
String s = null;
String opt = Optional.ofNullable(s)
.orElseThrow(() ->
new Exception("Null Optional"));
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
OUTPUT:
Null Optional
In Java 10, an overloaded version of the orElseThrow method was provided. This
version has no arguments and throws a NoSuchElementException. In the following
example, the orElseThrow method throws a NoSuchElementException. The exception is
caught and “No value present” is displayed.
try {
String s = null;
String opt = Optional.ofNullable(s)
.orElseThrow();
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
247
Chapter 11 Use in Optionals
OUTPUT:
No value present
The orElse, orElseGet, and orElseThrow methods return a value of type X. The or
method returns an Optional<X> that can be followed by additional links in an Optional
chain.
The or method in the following example accepts a Supplier<Optional<String> >,
which prompts the user to enter a string if the value contained in the Optional is null.
String s = null;
Optional<String> os = Optional.ofNullable(s)
.or(supplier);
if (os.isPresent())
System.out.println(os.get());
Enter a string:RED
RED
Since ifPresent returns void, it must be the last method called if used in an
Optional chain. The ifPresent method can be added as the final link in the Optional
chain of the previous example.
String s = null;
Optional.ofNullable(s)
248
Chapter 11 Use in Optionals
.or(supplier)
.ifPresent(x -> System.out.println(x));
Enter a string:Hello
Hello
The filter method returns the Optional if the predicate is true and returns an
empty Optional if the predicate is false.
The filter method is only called on non-null Optionals. This prevents
NullPointerExceptions inside their predicates. If allowed to execute on null Optionals,
the filter method in the following example would throw the exception when its
predicate attempted to take the length of the string.
String t = null;
Optional.ofNullable(t)
.filter(x -> x.length() > 2);
The filter method can be used to build chains of Optionals which result in the
logical AND of their predicates. The chain stops executing after the first false predicate,
since it returns a null Optional. In the following example, all predicates are true, so the
ifPresent executes and “Hello” is displayed.
Optional.of("Hello") // Optional(Hello)
.filter(x -> x.charAt(0) == 'H') // Optional(Hello)
.filter(x -> x.length() > 2) // Optional(Hello)
.filter(x -> x.charAt(1) == 'e') // Optional(Hello)
.ifPresent(x -> System.out.println(x)); // Prints "Hello"
OUTPUT:
Hello
249
Chapter 11 Use in Optionals
In the following example, the first predicate is false, so the second and third
predicates do not execute and no output is produced.
Optional.of("Hello") // Optional(Hello)
.filter(x -> x.charAt(0) == 'i') // Optional(null)
.filter(x -> x.length() > 2) // doesn't execute
.filter(x -> x.charAt(1) == 'e') // doesn't execute
.ifPresent(x -> System.out.println(x)); // doesn't execute
Filter conditions can be logically OR’ed using the Predicate class’s or method along
a predicate chain. The following example filters the Optional using the logical OR of
predicates which check if the first character is either “i” or “H.” Since the first character
is “H,” “Hello” is printed.
OUTPUT:
Hello
Optional.of("4") // Optional("4")
.map(x -> Integer.parseInt(x)) // Optional(4)
.filter(x -> x > 2) // Optional(4)
.filter(x -> (x%2) == 0) // Optional(4)
.ifPresent(x -> System.out.println(x)); // Prints 4
250
Chapter 11 Use in Optionals
Since the ifPresent method returns void, any modifications to the underlying
object will not persist.
Optional<Integer> o1 = Optional.of(2);
o1.ifPresent(x -> ++x);
o1.ifPresent(x -> System.out.println(x)); // Prints 2
The map method can be used to modify the underlying object, since it returns a new
Optional. Simply use the same input and output type in the function (by providing an
implementation of UnaryOperator). The following example changes the underlying
value in an Optional<Integer> from 2 to 3. Note the use of prefix notation in the map’s
function.
Optional.of(2) // Optional(2)
.map(x -> ++x) // Optional(3)
.ifPresent(x -> System.out.println(x)); // Prints 3
OUTPUT:
The flatMap method is similar to the map method, except that the result of its
function is already wrapped in an Optional.
In the following example, the flatMap method maps the String “4” to the Integer
value 4 using a function that returns an Optional<Integer>.
Optional.of("4") // Optional("4")
.flatMap(x ->
Optional.of(Integer.parseInt(x))) // Optional(4)
.ifPresent(x -> System.out.println(x)); // Prints 4
OUTPUT:
251
Chapter 11 Use in Optionals
class Resource
{
static int count = 3;
public Resource()
{
count--;
An Optional chain can be used to consume the resource by calling the Resource
constructor in both the of and map methods. Then, the filter method is used to check
if there are still resources remaining. Note that the last call to map does not execute, since
Resource's count field equals 0, and the previous predicate contains a null Optional.
The result of this logic is that the resource will be completely consumed provided that
the Optional chain is long enough to handle the number of items in the resource.
OUTPUT:
252
Chapter 11 Use in Optionals
Solution
First, a Guess class should be written with fields for the number and the user’s guess.
The Guess(int) constructor is called by the Optional's of method to assign a random
number between 0 and 9 to the number field. The Guess(int, int) constructor is called
by Optional's map method to populate the user’s guess and propagate the current
number to the number field.
class Guess
{
int number;
int guess;
public Guess(int n) { number = n; }
public Guess(int g, int n) { guess = g; number = n; }
}
The function called by the Optional's map method will be a UnaryOperator that
prompts the user to guess a number and passes the value input from a Scanner object
along with the number to the Guess(int, int) constructor. The new Guess object is
returned.
Since the predicate called by Optional's filter method is used five times, it should
be stored in a Predicate object.
253
Chapter 11 Use in Optionals
The Optional's of method calls the Guess(int) constructor which initializes the
number. Then, the map method executes with the UnaryOperator to make a guess. Next,
the filter method’s predicate checks if the guess is correct. If the user guesses correctly,
the filter method returns a null Optional, and processing stops. If the user guesses
incorrectly, processing continues and another guess is made. After the fifth attempt,
if the user has not guessed correctly, the ifPresent method executes, and message
showing the number is displayed.
import java.util.*;
import java.util.function.*;
class Guess
{
254
Chapter 11 Use in Optionals
int number;
int guess;
public Guess(int n) { number = n; }
public Guess(int g, int n) { guess = g; number = n; }
}
class GuessANumber
{
public static void main(String[] args)
{
UnaryOperator<Guess> guessIt = x -> {
System.out.print("Guess a number between 0 and 9:");
return new Guess(new Scanner(System.in).nextInt(),
x.number);
};
In the program’s first execution, the Optional's of method creates a Guess whose
number is 3. The user makes five incorrect guesses for the number. Each call to the map
method along the Optional chain prompts the user for a guess and stores it in a new
Guess object. Each call to the filter method tests if the guess is not equal to 3. Since
each predicate is true, the ifPresent method prints a message displaying the number 3.
PROGRAM OUTPUT 1 (user unable to guess number in five attempts) (input in BOLD):
Short Problems
1) Write a supplier that returns an Optional object. The supplier
prompts the user to select a number between 1 and 5 or enter
“Q” to quit. The Optional should contain an integer value when
a number is selected and contain null when “Q” was entered.
Call the supplier in a loop that prints the contents of the Optional
if populated. The loop terminates when the Optional is null.
Demonstrate in a main program.
256
Chapter 11 Use in Optionals
2) Write a program that uses the supplier you wrote in problem 1 in
a chain of Optionals that throws an exception which prints the
message “User has quit the program.” if the user enters “Q”.
Long Problems
1) The Internal Revenue Service allows you to specify your income
as either a W-2 employee or a self-employed individual. Using the
techniques described in this chapter, write a program that allows
the user to input his or her income as either a W-2 employee or a
self-employed person and then prints the resulting income.
x3 + 4x2 -7x -8
3) The words “of”, “in”, “on”, “beside”, “under”, and “above” are
prepositions. Using the techniques described in this chapter, find
the first preposition in a sentence and replace it with the next
preposition in the list of prepositions specified in the preceding
text. Print the resulting sentence. If there are no prepositions in
the sentence, throw an exception that states that the sentence
contains no prepositions.
257
CHAPTER 12
Use in Streams
The Stream interface provides chainable operations that can be performed on a series of
values. A Stream is generic for type parameter X which is the type of its values.
An object that implements the Stream interface is like an Optional that can contain
several values instead of just one. Stream has many of the same methods defined in the
Optional class including of, filter, and map.
Since many of the methods in the Stream interface return streams, very powerful
chains of streams can be created.
259
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0_12
Chapter 12 Use in Streams
The static generate method can also be used to generate a stream. It accepts a
Supplier that provides an infinite series of elements which are placed in the stream.
The limit method can then be called in the stream chain to stop the series after a
certain number of elements.
The following example creates a stream of 10 random numbers between 0 and 99.
Stream<Integer> tenRandomNumbers =
Stream.generate( () -> (new Random()).nextInt(100))
.limit(10);
OUTPUT:
17
50
97
8
22
76
31
24
78
92
260
Chapter 12 Use in Streams
Filtering a stream may remove all its elements (recall that filtering an Optional will result
in an Optional that either contains a null or a non-null object). In the following example, the
filter method removes all elements from the stream, so nothing is displayed.
In the following example, the two “RED” elements are matched, so “GREEN” and
“BLUE” are removed.
OUTPUT:
RED
RED
Chained filter methods perform the logical AND operation on the results of their
predicates. If the logical OR operation is desired, a composed predicate can be provided
to the filter method. In the following example, elements that are either equal to “RED”
or contain an “R” pass through the filtering.
OUTPUT:
RED
GREEN
RED
261
Chapter 12 Use in Streams
list.stream()
.filter(x -> x.equals("GREEN"))
.forEach(x -> System.out.println(x));
OUTPUT:
GREEN
Optional.of("RED")
.stream()
.forEach(x -> System.out.println(x));
OUTPUT:
RED
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
The no-arg version of the sorted method will arrange the elements in a stream by
their natural order.
262
Chapter 12 Use in Streams
OUTPUT:
Jaquiline
Jimmy
Kyle
OUTPUT:
Jimmy
Jaquiline
Kyle
263
Chapter 12 Use in Streams
OUTPUT:
Jimmy
OUTPUT:
Kyle
S
ection 12.7: flatMap vs. map
The flatMap method flattens a stream during the transformation of elements to a
target type. For a transformation of a Stream of type T to a Stream of type R, the flatMap
method accepts a function whose second type parameter is Stream<? extends R>.
Suppose a Class was defined as a subject and a collection of students (see Chapter 10
for the Student class definition).
class Class
{
String subject;
Collection<Student> students;
public Class(String su, Student... st)
{
subject = su;
students = Arrays.asList(st);
}
}
In the following example, a Stream of Class objects is created with elements for
biology and physics. Each Class contains two students. The Function converts the
Class's student collection to a stream which is flattened by flatMap to create a stream
whose elements are the students in both classes.
264
Chapter 12 Use in Streams
Stream.of(new Class("Biology",
new Student("Joe" ,1001,3.81),
new Student("Mary",1002,3.91)),
new Class("Physics",
new Student("Kalpana",1003,3.61),
new Student("Javier" ,1004,3.71))) // Stream<Class>
.flatMap(x -> x.students.stream()) // Stream<Student>
.forEach(x -> System.out.println(x));
OUTPUT:
The map method does not perform flattening. For a transformation of a Stream of
type T to a Stream of type R, the map method accepts a function whose second type
parameter is R.
In the following example, the List<Student> resulting from the function is not
flattened by the map method, which instead creates a Stream of List<Student> whose
first element contains biology students and whose second element contains physics
students.
Stream.of(
new Class("Biology",
new Student("Joe" ,1001,3.81),
new Student("Mary",1002,3.91)),
new Class("Physics",
new Student("Kalpana",1003,3.61),
new Student("Javier" ,1004,3.71))) // Stream<Class>
.map(x -> x.students) // Stream<List<Student>>
.forEach(x -> System.out.println(x));
OUTPUT:
The following example uses the reduce method to compute the factorial of the
elements of a stream of consecutive integers:
OUTPUT:
120
The reduce method has an overloaded version which requires an identity, which
is the initial value for the running computation. It returns a T, which is the type of the
stream elements.
The following example computes the same factorial providing an identity of 1. Since
the result is a T, it is wrapped in a println statement for display purposes.
System.out.println(
Stream.of(1, 2, 3, 4, 5) // Stream(Integer)
.reduce(1, (x,y) -> x * y)); // Integer
OUTPUT:
120
The reduce method has another overloaded version which can transform complex
objects into simpler reductions.
Suppose a program defines the TwoInts class that contains two integers
class TwoInts
{
266
Chapter 12 Use in Streams
Integer i1;
Integer i2;
public TwoInts(int z1, int z2) { i1=z1; i2=z2;}
}
and the program needs to sum all second integers in a Stream of TwoInts.
OUTPUT:
11
267
Chapter 12 Use in Streams
BiConsumer<R,T> accumulator,
BinaryOperator<R> combiner,
Collector.Characteristics... characteristics);
268
Chapter 12 Use in Streams
The combiner combines containers into the final reduction. The combiner being
used is of type BinaryOperator<R>. An overloaded version of the Collector interface’s
of method uses a BinaryOperator<A>, but the reduction needs to be transformed to type
R using a finisher function (see the last example in this section).
The collect method of the Stream interface uses a Collector to produce a mutable
reduction of the elements in the stream.
OUTPUT:
Accumulator x is initially empty. Then, stream element ‘1’ is placed at the end
forming list [1]. Next, stream element ‘a’ is placed at the beginning forming list [a1].
Then, stream element ‘b’ is placed at the beginning forming list [ba1]. Finally, stream
element ‘2’ is placed at the end forming list [ba12].
An overloaded version of the collect method could also have been used which
specifies the supplier, accumulator, and combiner without creating a Collector object.
In this case, the combiner is of type BiConsumer<R, R> instead of BinaryOperator<R>.
269
Chapter 12 Use in Streams
Stream.of('1','a','b','2')
.collect(supp, acc, comb2)
.forEach(x -> System.out.println(x));
OUTPUT:
A mutual reduction can also be generated using the StringBuilder class. In the
following example, the accumulator removes the vowels from a string before using the
StringBuilder class’s append(String) method to place at the end of the reduction.
String s = Stream.of("Joe","Kalpana","Christopher")
.collect(Collector.of(supps, accs, combs, fins));
System.out.println(s);
270
Chapter 12 Use in Streams
OUTPUT:
JKlpnChrstphr
OUTPUT:
1
a
b
2
The following example transforms a list of characters into a string using the
collectingAndThen method and Function fins2:
271
Chapter 12 Use in Streams
for (Character c : x)
t += c;
return t;
};
String t =
Stream.of('1','a','b','2') // Stream<Character>
.collect(Collectors.collectingAndThen(
Collector.of(supp, accc2, comb1), // List<Character>
fins2)); // String
System.out.println(t); // Prints ba12
OUTPUT:
ba12
The reducing method collects the elements of a stream into an Optional object. It
accepts a BinaryOperator.
The following example uses the reduce method to create an Optional<Integer> that
contains the factorial of 5.
Stream.of(1,2,3,4,5) // Stream<Integer>
.collect(Collectors.reducing((x,y) ->
x *= y)) // Optional<Integer>
.ifPresent(x -> System.out.println(x)); // Prints 120
OUTPUT:
120
The joining method joins a stream of strings into a single String object.
System.out.println(s);
272
Chapter 12 Use in Streams
OUTPUT:
REDGREENBLUE
OUTPUT:
RED,GREEN,BLUE
The groupingBy method organizes the result by a key value. The resulting container
is a map whose values are a list of stream elements. A function which transforms a
stream element to a key value must be provided.
class Car
{
String manu;
String model;
int mpg;
public Car(String ma, String mo, int mp)
{
manu = ma;
model = mo;
mpg = mp;
}
public String toString()
{
return manu + " " + model + " gets " + mpg + " mpg";
}
}
273
Chapter 12 Use in Streams
a result can be created that organizes cars by manufacturer. The resulting container is a
map whose key value is the manu field of type String. The value in each map element is
the list of cars built by the manufacturer. A function which transforms a Car object to the
map key (by obtaining the manufacturer) is provided to the groupingBy method.
OUTPUT:
The following example groups cars by manufacturer while applying the mapping
method to each Car object’s mpg field, and then uses the Collectors class’s toList
method to collect them into a list of integers:
274
Chapter 12 Use in Streams
OUTPUT:
The partitioningBy method splits the reduction into two components based on
a pass/fail criterion. The map’s key is of type Boolean, and a predicate is provided to
specify the pass/fail criterion.
The following example partitions the elements in a stream of cars using a predicate
that specifies the pass/fail criterion of 30 or more miles per gallon.
OUTPUT:
false: [Buick Regal gets 25 mpg, Hyundai Elantra gets 27 mpg, Buick Skylark
gets 26 mpg]
true: [Hyundai Accent gets 30 mpg]
The Collectors class has methods which produce a sum as the mutable reduction.
These methods accept a function which transforms the stream element into the int,
long, or double to be summed.
275
Chapter 12 Use in Streams
Integer sum =
Stream.of(new Car("Buick" ,"Regal" ,25),
new Car("Hyundai","Elantra",27),
new Car("Buick" ,"Skylark",26),
new Car("Hyundai","Accent" ,30))
.collect(Collectors.summingInt(x -> x.mpg));
OUTPUT:
bld.accept("RED");
bld.accept("GREEN");
bld.accept("BLUE");
The build method is used to create a stream from the builder. Once the stream
has been generated, additional elements cannot be added to the builder. Note how
attempting to add the element “YELLOW” to the builder after the stream has been
generated causes an IllegalStateException and that yellow is not added to the stream.
Stream<String> st = bld.build();
try {
bld.accept("YELLOW");
}
catch (IllegalStateException e) {
System.out.println("IllegalStateException");
}
st.forEach(x -> System.out.println(x));
276
Chapter 12 Use in Streams
OUTPUT:
IllegalStateException
RED
GREEN
BLUE
Since the add method returns a Stream.Builder object, it can be used in a stream
chain (the accept method returns void so it must be used as a separate statement).
Stream.builder()
.add("RED")
.add("GREEN")
.add("BLUE")
.build()
.forEach(x -> System.out.println(x));
OUTPUT:
RED
GREEN
BLUE
OUTPUT:
1
2
3
4
10
277
Chapter 12 Use in Streams
IntStream ints =
Stream.of(new Car("Buick" ,"Regal" ,25),
new Car("Hyundai","Elantra",27),
new Car("Buick" ,"Skylark",26),
new Car("Hyundai","Accent" ,30)) // Stream<Car>
.mapToInt(x -> x.mpg); // IntStream
The specializations have several methods that do not exist in the generic Stream
interface. The max and min methods compute the largest and smallest elements in
the stream, respectively. The methods return OptionalInt, OptionalLong, and
OptionalDouble objects, respectively.
ints.max()
.ifPresent(x -> System.out.println(x));
LongStream.of(1,2,3,4)
.min()
.ifPresent(x -> System.out.println(x));
OUTPUT:
30
1
The average method computes the average of the stream elements and returns an
OptionalDouble.
DoubleStream.of(1.1,2.2,3.3,4.4)
.average()
.ifPresent(x -> System.out.println(x));
OUTPUT:
2.75
278
Chapter 12 Use in Streams
The sum method computes the sum of the stream elements and returns an int.
OUTPUT:
10
Solution
An Item class can be defined that contains the item’s model, quantity, and price.
class Item
{
String model;
int quantity;
double price;
279
Chapter 12 Use in Streams
A Part class can be defined that contains the part’s name, manufacturer, installation
time, and a list of items available in the inventory for the part made by the manufacturer.
class Part
{
String name;
String manu;
int hoursToInstall;
List<Item> models;
public Part(String n, String m, int h, Item... it)
{
name = n;
manu = m;
hoursToInstall = h;
models = new ArrayList<>();
for (Item i : it)
models.add(i);
}
@Override
public String toString()
{
return manu + " " + hoursToInstall + "hrs " + models;
}
}
280
Chapter 12 Use in Streams
parts.stream()
.collect(Collectors.groupingBy(x -> x.name))
.forEach( (x,y) -> System.out.println(x + ": " + y));
A particular part can be searched by filtering the stream of parts by name. For
reporting purposes, it is useful to call the peek method at this link in the chain to display
the part’s name and manufacturer. The list of items then needs to be flattened in order to
test each item. Once flattened, we can filter each item based on quantity and price.
parts.stream() // Stream<Part>
.filter(x -> x.name.equals(partName))
.peek(x -> System.out.println(
x.name + " by " + x.manu + ":"))
281
Chapter 12 Use in Streams
To create a list of items for the part sorted by price, the sorted method can be called
after flattening. The Comparator.comparing method can be used to perform the sort if a
Function<Item, Double> is provided to extract the price from each item.
parts.stream() // Stream<Part>
.filter(x -> x.name.equals(partName))
.flatMap(x -> x.models.stream()) // Stream<Item>
.sorted( Comparator.comparing(x -> x.price))
.forEach(x -> System.out.println(x));
To count the total quantity available for a part, the stream of items needs to be
reduced to an Integer using the reduce method. The identity for the sum is zero.
A BiFunction<Double, Item, Double> is used to accumulate each quantity. A
BinaryOperator<Double> is used to combine the quantities.
Integer i =
parts.stream()
.filter(x -> x.name.equals(partName))
.flatMap(x -> x.models.stream())
.reduce(0,
(x,z) -> x + z.quantity,
(x,y) -> x += y);
System.out.println(i);
To find all models for a part that can be installed within a time limit, the stream of
parts needs to be filtered by part name and installation time.
parts.stream()
.filter(x -> x.name.equals(partName))
.filter(x -> x.hoursToInstall < timeLimit)
.forEach(x -> System.out.println(x));
282
Chapter 12 Use in Streams
import java.util.stream.*;
import java.util.*;
import java.util.function.*;
class Item
{
String model;
int quantity;
double price;
public Item(String m, int q, double p)
{
model = m;
quantity = q;
price = p;
}
@Override
public String toString()
{
return model + ":" + quantity + " $" + price;
}
}
class Part
{
String name;
String manu;
283
Chapter 12 Use in Streams
int hoursToInstall;
List<Item> models;
public Part(String n, String m, int h, Item... it)
{
name = n;
manu = m;
hoursToInstall = h;
models = new ArrayList<>();
for (Item i : it)
models.add(i);
}
@Override
public String toString()
{
return manu + " " + hoursToInstall + "hrs " + models;
}
}
class Inventory
{
static String partName = null;
static int quantity=0;
static double price = 0.0;
static int timeLimit = 0;
284
Chapter 12 Use in Streams
new Item("AC25",1,90.74),
new Item("AC26",4,130.22))
);
Supplier<Integer> selectOperation = () -> {
Scanner userInput = new Scanner(System.in);
int operation = -1;
while (operation < 0 || operation > 5)
{
System.out.println("Please select an operation:");
System.out.println(
" 0 - List inventory");
System.out.println(
" 1 - Find item by quantity and price");
System.out.println(
" 2 - List items by price");
System.out.println(
" 3 - Count items by part name");
System.out.println(
" 4 - Find items by installation time");
System.out.print (
" 5 - End program:");
operation = Integer.parseInt(userInput.nextLine());
if (operation < 0 || operation > 5)
System.out.println("Invalid operation");
}
return operation;
};
Supplier<String> selectPartName = () ->
(new Scanner(System.in)).nextLine();
Supplier<Integer> selectQuantity = () ->
Integer.parseInt((new Scanner(System.in)).nextLine());
Supplier<Double> selectPrice = () ->
Double.parseDouble((new Scanner(System.in)).nextLine());
Supplier<Integer> selectTimeLimit = () ->
Integer.parseInt((new Scanner(System.in)).nextLine());
285
Chapter 12 Use in Streams
System.out.println(
"Welcome to Discount Dave's Inventory Program.");
boolean done = false;
while (!done)
{
switch (selectOperation.get())
{
case 0:
parts.stream()
.collect(Collectors.groupingBy(x -> x.name))
.forEach(
(x,y) -> System.out.println(x + ": " + y));
break;
case 1:
System.out.print("Select a part name:");
partName = selectPartName.get();
System.out.print("Select a minimum quantity:");
quantity = selectQuantity.get();
System.out.print("Select a price limit:");
price = selectPrice.get();
parts.stream()
.filter(x -> x.name.equals(partName))
.peek(x -> System.out.println(
x.name + " by " + x.manu + ":"))
.flatMap(x -> x.models.stream())
.filter(x -> x.price <= price)
.filter(x -> x.quantity >= quantity)
.forEach(x -> System.out.println(x));
break;
case 2:
System.out.print("Select a part name:");
partName = selectPartName.get();
parts.stream()
286
Chapter 12 Use in Streams
case 3:
System.out.print("Select a part name:");
partName = selectPartName.get();
Integer i =
parts.stream()
.filter(x -> x.name.equals(partName))
.flatMap(x -> x.models.stream())
.reduce(0,
(x,z) -> x + z.quantity,
(x,y) -> x += y);
System.out.println(i);
break;
case 4:
System.out.print("Select a part name:");
partName = selectPartName.get();
System.out.print(
"Select installation time limit:");
timeLimit = selectTimeLimit.get();
parts.stream()
.filter(x -> x.name.equals(partName))
.filter(x -> x.hoursToInstall < timeLimit)
.forEach(x -> System.out.println(x));
break;
default: // 5
System.out.println("Bye Bye.");
done = true;
}
}
}
}
287
Chapter 12 Use in Streams
288
Chapter 12 Use in Streams
AC25:1 $90.74
EX8426:2 $125.35
AC26:4 $130.22
Please select an operation:
0 - List inventory
1 - Find item by quantity and price
2 - List items by price
3 - Count items by part name
4 - Find items by installation time
5 - End program: 3
Select a part name: tires
14
Please select an operation:
0 - List inventory
1 - Find item by quantity and price
2 - List items by price
3 - Count items by part name
4 - Find items by installation time
5 - End program: 4
Select a part name: tires
Select installation time limit: 2
Firestone 1hrs [FS2112:3 $60.47, FS2479:5 $85.2]
Please select an operation:
0 - List inventory
1 - Find item by quantity and price
2 - List items by price
3 - Count items by part name
4 - Find items by installation time
5 - End program: 5
Bye Bye.
289
Chapter 12 Use in Streams
S
hort Problems
1) Create a stream containing the names “Robert”, “Cheryl”,
“Rachel”, “Jose”, and “Rita”. Remove all names that do not begin
with the letter R. Sort the remaining elements by reverse ordering,
and then print the results.
L ong Problems
1) Using the techniques described in this chapter, produce an
alphabetically sorted, comma-separated list of car models based
on the Car class defined in Section 9a of this chapter. The cars are
defined as follows:
Buick Regal 25
Hyundai Elantra 27
Buick Skylark 26
Hyundai Accent 30
290
Chapter 12 Use in Streams
291
CHAPTER 13
Use in Multithreaded
Programs
ection 13.1: Performing Computations Using
S
Runnable and Callable
Runnable is an interface whose implementations contain computations meant to be
performed in a thread separate from the calling thread. It is a functional interface
containing functional method run which takes no arguments and returns no value.
@FunctionalInterface
public interface Runnable {
void run();
}
OUTPUT:
RED
(new Thread(r)).start();
293
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0_13
Chapter 13 Use in Multithreaded Programs
OUTPUT:
RED
Callable differs from Runnable in that it returns the result of its computation and
can throw a checked exception. It is a functional interface that is generic for result type V,
and with functional method call that returns an object of type V.
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
Since the Callable computation may throw a checked exception, its call method
must be enclosed in a try block. The following example executes the call method of
Callable c1 inside a try block. Since no exception occurs during the computation,
“GREEN” is displayed.
try {
String s1 = c1.call();
System.out.println(s1);
} catch (Exception e) {}
OUTPUT:
GREEN
The following Callable implementation opens and reads a line from file my.txt.
Callable<String> c2 = () -> {
BufferedReader br = new BufferedReader(
new FileReader("my.txt"));
return br.readLine();
};
Assuming that file my.txt doesn’t exist, the following example throws a
FileNotFoundException when the call method executes. The exception is caught and
displayed in the catch block.
294
Chapter 13 Use in Multithreaded Programs
try {
String s1 = c2.call();
System.out.println(s1);
} catch (Exception e) { System.out.println(e.getClass());}
OUTPUT:
class java.io.FileNotFoundException
The following Runnable creates an infinite stream of random integers between 0 and
99. It is run as a daemon thread which is terminated by the JVM when the main thread
finishes. The main thread synchronizes with the Runnable using a semaphore which is
acquired inside the peek method. The number is stored in static field number which is
populated after the semaphore is acquired.
Runnable r = () -> {
Stream.generate( () -> (new Random()).nextInt(100))
.peek(acquire)
.forEach(x -> number = x);
};
The main thread releases the semaphore in a loop and then goes to sleep for
1 second to allow the Runnable to process. The main thread throttles execution by
prompting the user to request another number or end the program. The complete
program is shown as follows.
import java.util.stream.*;
import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.function.*;
class InfiniteStream
295
Chapter 13 Use in Multithreaded Programs
{
private static Semaphore sem = new Semaphore(0);
private static int number;
public static void main(String[] args) throws Exception
{
Consumer<Integer> acquire = x -> {
try {
sem.acquire();
} catch (InterruptedException e) {}
};
Runnable r = () -> {
Stream.generate( () -> (new Random()).nextInt(100))
.peek(acquire)
.forEach(x -> number = x);
};
do
{
sem.release();
Thread.sleep(1000);
System.out.println("The number is " + number);
} while (!finished.get());
}
}
296
Chapter 13 Use in Multithreaded Programs
The number is 5
Do you want another number(Y for yes): Y
The number is 61
Do you want another number(Y for yes): N
String s = null;
Optional.ofNullable(s)
.ifPresentOrElse(x -> System.out.println(x),
() -> System.out.println("empty"));
OUTPUT:
empty
FutureTask(Callable<V> callable);
FutureTask(Runnable runnable, V result);
297
Chapter 13 Use in Multithreaded Programs
The following example creates two FutureTasks. The first executes a Callable that
return a random number between 0 and 99. The second FutureTask results in -1 and
executes a Runnable that displays “Runnable”.
fc.run();
fr.run();
The Future interface specifies a get method which waits for the computation to
complete, if necessary, and then returns the result. The get method may throw an
InterruptedException or an ExecutionException.
try {
int i = fc.get();
int j = fr.get();
System.out.println(i + " " + j);
} catch(InterruptedException|ExecutionException e) {}
OUTPUT:
Runnable
9 -1
298
Chapter 13 Use in Multithreaded Programs
The CompletableFuture resulting from this method can be used as part of a chain.
The following example creates a CompletableFuture<String> object that immediately
completes with the result “RED”. The get method is then called along the future chain
to obtain the result of the computation which is passed to the System.out.println
method.
try {
System.out.println(
CompletableFuture
.completedFuture("RED") // CompletableFuture<String>
.get()); // String
} catch (InterruptedException
|ExecutionException e) {}
OUTPUT:
RED
299
Chapter 13 Use in Multithreaded Programs
The join method is similar to get except that it may only throw an unchecked
exception. Use the get method if a checked exception can be thrown.
System.out.println(
CompletableFuture
.completedFuture("BLUE") // CompletableFuture<String>
.join()); // String
OUTPUT:
BLUE
Neither the get method nor the join method blocks when the future is already
completed. The static supplyAsync method accepts a supplier that returns an object of
the result type.
The future does not complete immediately and does not run in the calling thread. It
executes asynchronously on a task in the ForkJoinPool common pool. In the following
example, “NOT EXECUTED YET” is the first line in the displayed output, since the future
has not executed before the println statement. The future executes when the join
method is called, and therefore “EXECUTING THE FUTURE” and 4 are then displayed.
OUTPUT:
300
Chapter 13 Use in Multithreaded Programs
System.out.println(cf1.join());
OUTPUT:
In the following example, a Consumer object prints “GREEN” which is the result of
the current computation. Note that the join method is not required, since the consumer
argument to thenAccept has already displayed the contents of the future.
CompletableFuture
.supplyAsync( () -> "RED") // CompletableFuture<String>
.thenApply(x ->
x.equals("RED")? "GREEN":"BLUE") // CompletableFuture<String>
.thenAccept(x -> System.out.println(x));// CompletableFuture<Void>
301
Chapter 13 Use in Multithreaded Programs
OUTPUT:
GREEN
CompletionStage<Void> acceptEitherAsync(
CompletionStage<? extends T> other,
Consumer<? super T> action,
Executor executor);
Supplier<Integer> s1 = () -> {
try { Thread.sleep(5000);
} catch (InterruptedException e) {}
return 4;
};
Supplier<Integer> s2 = () -> {
try { Thread.sleep(2000);
} catch (InterruptedException e) {}
return 5;
};
302
Chapter 13 Use in Multithreaded Programs
The first future to complete can be selected by passing the second future as an
argument to the acceptEitherAsync method of the first. Since the second future
completes first, its result is passed to a consumer that prints it.
CompletableFuture<Void> cf3
= cf1.acceptEitherAsync(cf2,
x ->System.out.println(x),exec);
OUTPUT:
Supplier<Integer> s1 = () ->
{
try { Thread.sleep(3000);
} catch (InterruptedException e) {}
return 4;
};
CompletableFuture<Integer> cf1
= CompletableFuture.supplyAsync(s1);
A future that waits for the completion of another future can be created by calling the
first future’s thenCompose method. The function argument to the thenCompose method
transforms the Integer result to a CompletableFuture<String> by creating a local
CompletableFuture<String> object that uses the result and completing it immediately.
303
Chapter 13 Use in Multithreaded Programs
System.out.println(cf2.join());
OUTPUT:
HI4
Supplier<Integer> s = () -> {
try { Thread.sleep(10000);
} catch (InterruptedException e) {}
return 5;
};
CompletableFuture<Integer> cf1
= CompletableFuture.supplyAsync(s);
Since the computation has not run yet, the future can be cancelled. The cancel
method is called with argument true to allow interruption of the task in which the future
runs.
if (cf1.cancel(true))
System.out.println("Future cf1 cancelled.");
OUTPUT:
304
Chapter 13 Use in Multithreaded Programs
try {
System.out.println(cf1.join());
} catch (CancellationException e) {
System.out.println("Cannot join cancelled future.");
}
OUTPUT:
CompletableFuture<Integer> cf2
= CompletableFuture.completedFuture(6);
if (cf2.cancel(true))
System.out.println("Future cf2 cancelled.");
try {
System.out.println(cf2.join());
} catch (CancellationException e) {
System.out.println("Cannot join cancelled future.");
}
OUTPUT:
FutureTask provides the isDone and isCancelled methods that can be used to
check if a future has completed or has been cancelled.
305
Chapter 13 Use in Multithreaded Programs
if (cf2.isDone())
System.out.println("Future cf2 has completed");
if (!cf2.isCancelled())
System.out.println("Future cf2 has not been cancelled");
OUTPUT:
The future in the following example throws an unchecked exception. Therefore, the
function passed to the exceptionally method replaces it with zero. The value zero is
wrapped in a new CompleteableFuture and printed by the thenAccept method.
CompletableFuture
.supplyAsync(y) // CompletableFuture<Integer>
.exceptionally(x -> 0) // CompletableFuture<Integer>
.thenAccept(x ->
System.out.println(x)); // CompletableFuture<Void>
OUTPUT:
306
Chapter 13 Use in Multithreaded Programs
If the previous example was repeated with a computation that does not throw
an unchecked exception, the exceptionally method would do nothing and the
supplyAsync result would be passed to the thenAccept method.
CompletableFuture
.supplyAsync(z) // CompletableFuture<Integer>
.exceptionally(x -> 0) // CompletableFuture<Integer>
.thenAccept(x ->
System.out.println(x)); // CompletableFuture<Void>
OUTPUT:
This method is used to run several futures in parallel. It can be used when the
order of computation is not important provided the futures do not share data (or are
synchronized in some way). Since it creates a CompletableFuture<Void>, the results of
each future are not available on the method chain. The following program runs three
futures in parallel. It stores the result of each computation in a separate field.
import java.util.concurrent.CompletableFuture;
class AllOf
{
static String color1=null;
static String color2=null;
static String color3=null;
public static void main(String[] args)
307
Chapter 13 Use in Multithreaded Programs
{
CompletableFuture<String> cf1
= CompletableFuture.supplyAsync( () -> {
color1 = "RED"; return null;});
CompletableFuture<String> cf2
= CompletableFuture.supplyAsync( () -> {
color2 = "GREEN"; return null;});
CompletableFuture<String> cf3
= CompletableFuture.supplyAsync( () -> {
color3 = "BLUE"; return null;});
CompletableFuture
.allOf(cf1,cf2,cf3) // CompletableFuture<Void>
.join(); // Void
OUTPUT:
RED,GREEN,BLUE
The same results could have been obtained using the thenCombine method, which
combines the results of two CompletionStages using a BiFunction.
The following example defines a BiFunction that adds a comma between the results
of the two futures. This function combines the result of a computation with the previous
computation in comma-delimited string. Note that fields color1, color2, and color3 are
not necessary to store the separate calculations, since the results are being propagated
by the method chain.
CompletableFuture<String> cf4
= CompletableFuture.supplyAsync( () -> "RED");
CompletableFuture<String> cf5
308
Chapter 13 Use in Multithreaded Programs
System.out.println(cf4.thenCombine(cf5, bi)
.thenCombine(cf6, bi)
.join());
OUTPUT:
RED,GREEN,BLUE
CompletableFuture<String> cf1
= CompletableFuture.supplyAsync( () -> "RED");
CompletableFuture<String> cf2
= CompletableFuture.supplyAsync( () -> "GREEN");
CompletableFuture<String> cf3
= CompletableFuture.supplyAsync( () -> "BLUE");
CompletableFuture<Object> result =
CompletableFuture.anyOf(cf1, cf2, cf3);
System.out.println(result.join());
OUTPUT:
RED
Suppose a program needs to concatenate the words entered by a user into a sentence
using two parallel, independent threads. The allOf method could be used to run the
threads in parallel, but then separate fields are required to store the results since allOf
returns a CompletableFuture<Void>. If the thenCombine method were used instead, the
words could be concatenated internally and returned by the second thread.
309
Chapter 13 Use in Multithreaded Programs
The scanning thread prompts the users to enter a word. A semaphore could be used
to inform the other thread that a new word is available. The thread finishes when the
user enters a period.
The concatenating thread wakes up when a word is available and then adds the word
to the sentence.
The threads can be hosted by futures and run in parallel using the thenCombine
method. A BiFunction can select the result of the second thread (the sentence).
CompletableFuture<String> keyboardFuture
= CompletableFuture.supplyAsync(keyboard);
CompletableFuture<String> concatFuture
310
Chapter 13 Use in Multithreaded Programs
= CompletableFuture.supplyAsync(concatenator);
System.out.println(keyboardFuture
.thenCombine(concatFuture, bi)
.join());
The complete program is shown as follows. After the user enters the word
“Functional”, keyboardFuture releases the semaphore causing concatFuture to
concatenate “Functional” to the sentences. This process happens again for the words
“interfaces” and “rock”.
When the user enters a period, keyboardFuture returns a null result and
concatFuture returns the sentence. The BiFunction selects the sentence, and the result
of the combined future is obtained using the join method.
import java.util.concurrent.*;
import java.util.function.*;
import java.util.Scanner;
class MySentence
{
private static Semaphore sem = new Semaphore(0);
private static String word = "";
public static void main(String[] args)
{
Supplier<String> keyboard = () -> {
Scanner scan = new Scanner(System.in);
while (!word.equals("."))
{
System.out.print(
"Enter a word (or . when finished):");
word = scan.nextLine();
sem.release();
}
return null;
};
311
Chapter 13 Use in Multithreaded Programs
CompletableFuture<String> keyboardFuture
= CompletableFuture.supplyAsync(keyboard);
CompletableFuture<String> concatFuture
= CompletableFuture.supplyAsync(concatenator);
System.out.println(keyboardFuture
.thenCombine(concatFuture, bi)
.join());
}
}
312
Chapter 13 Use in Multithreaded Programs
Solution
The rules of grammar defined in the preceding list compare as many as three words
based on their parts of speech. Therefore, a class which stores the last three words
should be written. Each word should also contain its part of speech.
313
Chapter 13 Use in Multithreaded Programs
class SentenceComponent
{
Word current;
Word previous;
Word second;
public SentenceComponent(Word c, Word p, Word s)
{
current = c;
previous = p;
second = s;
}
public SentenceComponent()
{
current = new Word("" ,null);
previous = null;
second = null;
}
@Override
public String toString() { return current.toString(); };
}
314
Chapter 13 Use in Multithreaded Programs
Each grammar rule is applied in subsequent links of the future chain, with the result
of each rule passed to the next. The CompletionStage output from the thenCompose
method can be used to support the chain. A function needs to be defined which takes
the SentenceComponent result from one rule and maps it to a CompletionStage<Sentenc
eComponent> for the next. If a rule fails, it creates a CompletionStage that contains a null
SentenceComponent that is ignored by the next future in the chain. If the component is a
period, it is passed through the rules so that it may be written at the end of the sentence.
The following function implements grammar rule 1. If the current word is a
verb and the previous word is not a noun, the rule has been violated and a null
SentenceComponent is returned. All other combinations pass the SentenceComponent
through to the next rule.
Function<SentenceComponent,
CompletionStage<SentenceComponent>> rule1 = x -> {
CompletableFuture<SentenceComponent> fut
= new CompletableFuture();
SentenceComponent comp = null;
if (x!=null)
{
315
Chapter 13 Use in Multithreaded Programs
Function<SentenceComponent,
CompletionStage<SentenceComponent>> rule2 = x -> {
CompletableFuture<SentenceComponent> fut
= new CompletableFuture();
SentenceComponent comp = null;
if (x!=null)
{
if (x.current.part == null)
comp = x;
else if ((x.previous == null)
|| !x.previous.part.equals(SPEECH.ADJECTIVE)
|| x.current.part.equals(SPEECH.NOUN))
comp = x;
if (comp == null)
System.out.println(
"GRAMMAR RULE 2 ERROR: ADJECTIVES CAN ONLY PRECEDE NOUNS.");
}
316
Chapter 13 Use in Multithreaded Programs
fut.complete(comp);
return fut;
};
Function<SentenceComponent,
CompletionStage<SentenceComponent>> rule3 = x -> {
CompletableFuture<SentenceComponent> fut
= new CompletableFuture();
SentenceComponent comp = null;
if (x!=null)
{
boolean result = true;
if ((x.current.part != null)
&& (x.previous != null) && (x.second != null))
{
if (x.second.part.equals(SPEECH.ARTICLE))
{
if (!x.previous.part.equals(SPEECH.ADJECTIVE)
|| !x.current.part.equals(SPEECH.NOUN))
result = false;
}
else if ( x.previous.part.equals(SPEECH.ARTICLE)
&& !x.current.part.equals(SPEECH.NOUN)
&& !x.current.part.equals(SPEECH.ADJECTIVE))
result = false;
}
if (result == false)
{
System.out.println(
"GRAMMAR RULE 3 ERROR: ARTICLES CAN ONLY PRECEDE NOUNS,");
System.out.println(
317
Chapter 13 Use in Multithreaded Programs
Function<SentenceComponent,
CompletionStage<SentenceComponent>> rule4 = x -> {
CompletableFuture<SentenceComponent> fut
= new CompletableFuture();
SentenceComponent comp = null;
if (x!=null)
{
boolean result = true;
if ((x.current.part != null)
&& (x.previous != null) && (x.second != null))
{
if (x.second.part.equals(SPEECH.PREPOSITION))
{
if (!x.previous.part.equals(SPEECH.ARTICLE)
|| !x.current.part.equals(SPEECH.NOUN))
result = false;
}
else if ( x.previous.part.equals(SPEECH.PREPOSITION)
&& !x.current.part.equals(SPEECH.NOUN)
&& !x.current.part.equals(SPEECH.ARTICLE))
result = false;
}
318
Chapter 13 Use in Multithreaded Programs
The keyboard thread provides the SentenceComponent that is supplied to each rule
thread in turn. The resultant SentenceComponent is returned by the join method. This
component will be null if any of the grammar rules have been violated.
comp = CompletableFuture.supplyAsync(keyboard)
.thenCompose(rule1)
.thenCompose(rule2)
.thenCompose(rule3)
.thenCompose(rule4)
.join();
In the complete program, the chain is placed inside a while loop that runs until
the user enters a period. Local variable sentence concatenates the words with a space
between them. If the user violates a rule of grammar, the empty SentenceComponent is
ignored, and the program attempts to replace the offending word on the next pass of the
while loop.
import java.util.concurrent.*;
import java.util.function.*;
import java.util.Scanner;
enum SPEECH {NOUN,VERB,PREPOSITION,ADJECTIVE,ARTICLE}
319
Chapter 13 Use in Multithreaded Programs
class Word
{
String w;
SPEECH part;
public Word(String x, SPEECH p) { w=x; part=p;}
@Override
public String toString() { return w; };
}
class SentenceComponent
{
Word current;
Word previous;
Word second;
public SentenceComponent(Word c, Word p, Word s)
{
current = c;
previous = p;
second = s;
}
public SentenceComponent()
{
current = new Word("" ,null);
previous = null;
second = null;
}
@Override
public String toString() { return current.toString(); };
}
class SentenceBuilder
{
static Word previous = null;
static Word second = null;
320
Chapter 13 Use in Multithreaded Programs
{
Supplier<SentenceComponent> keyboard = () -> {
Scanner scan = new Scanner(System.in);
SPEECH part = null;
System.out.print("Enter a word (or enter .):");
String w = scan.nextLine();
if (!w.equals("."))
{
System.out.println("Enter its part of speech");
System.out.print("0 - NOUN, ");
System.out.print("1 - VERB, ");
System.out.print("2 - PREPOSITION, ");
System.out.print("3 - ADJECTIVE, ");
System.out.print("4 - ARTICLE:");
int p = Integer.parseInt(scan.nextLine());
part = SPEECH.values()[p];
}
Word word = new Word(w,part);
SentenceComponent comp
= new SentenceComponent(word,previous,second);
second = previous;
previous = word;
return comp;
};
Function<SentenceComponent,
CompletionStage<SentenceComponent>> rule1 = x -> {
CompletableFuture<SentenceComponent> fut
= new CompletableFuture();
SentenceComponent comp = null;
if (x!=null)
{
if (x.current.part == null)
comp = x;
else if (!x.current.part.equals(SPEECH.VERB)
|| (x.previous == null)
|| x.previous.part.equals(SPEECH.NOUN))
321
Chapter 13 Use in Multithreaded Programs
comp = x;
if (comp == null)
System.out.println(
"GRAMMAR RULE 1 ERROR: VERBS CAN ONLY FOLLOW NOUNS.");
}
fut.complete(comp);
return fut;
};
Function<SentenceComponent,
CompletionStage<SentenceComponent>> rule2 = x -> {
CompletableFuture<SentenceComponent> fut
= new CompletableFuture();
SentenceComponent comp = null;
if (x!=null)
{
if (x.current.part == null)
comp = x;
else if ((x.previous == null)
|| !x.previous.part.equals(SPEECH.ADJECTIVE)
|| x.current.part.equals(SPEECH.NOUN))
comp = x;
if (comp == null)
System.out.println(
"GRAMMAR RULE 2 ERROR: ADJECTIVES CAN ONLY PRECEDE NOUNS.");
}
fut.complete(comp);
return fut;
};
Function<SentenceComponent,
CompletionStage<SentenceComponent>> rule3 = x -> {
CompletableFuture<SentenceComponent> fut
= new CompletableFuture();
SentenceComponent comp = null;
322
Chapter 13 Use in Multithreaded Programs
if (x!=null)
{
boolean result = true;
if ((x.current.part != null)
&& (x.previous != null) && (x.second != null))
{
if (x.second.part.equals(SPEECH.ARTICLE))
{
if (!x.previous.part.equals(SPEECH.ADJECTIVE)
|| !x.current.part.equals(SPEECH.NOUN))
result = false;
}
else if ( x.previous.part.equals(SPEECH.ARTICLE)
&& !x.current.part.equals(SPEECH.NOUN)
&& !x.current.part.equals(SPEECH.ADJECTIVE))
result = false;
}
if (result == false)
{
System.out.println(
"GRAMMAR RULE 3 ERROR: ARTICLES CAN ONLY PRECEDE NOUNS,");
System.out.println(
"HOWEVER, AN ADJECTIVE MAY OCCUR BETWEEN THEM.");
}
else
comp = x;
}
fut.complete(comp);
return fut;
};
Function<SentenceComponent,
CompletionStage<SentenceComponent>> rule4 = x -> {
CompletableFuture<SentenceComponent> fut
= new CompletableFuture();
323
Chapter 13 Use in Multithreaded Programs
324
Chapter 13 Use in Multithreaded Programs
{
comp = CompletableFuture.supplyAsync(keyboard)
.thenCompose(rule1)
.thenCompose(rule2)
.thenCompose(rule3)
.thenCompose(rule4)
.join();
if (comp == null)
{
comp = new SentenceComponent();
}
else
{
if (comp.current.w.equals(".")
|| (sentence.length() == 0))
sentence += comp.current;
else
sentence += " " + comp.current;
}
}
System.out.println(sentence);
}
}
If the program is executed with the sentence “The quick fox jumped over the dog”
but the user forgets to enter “fox”, the program reports on word “jumped” that grammar
rule 1 has been violated. The user can then replace “jumped” with “fox” and finish
the sentence. After entering the period, the program prints the grammatically correct
sentence “The quick fox jumped over the dog.”
325
Chapter 13 Use in Multithreaded Programs
0 - NOUN, 1 - VERB, 2 - PREPOSITION, 3 - ADJECTIVE, 4 - ARTICLE: 3
Enter a word (or enter .): jumped
Enter its part of speech
0 - NOUN, 1 - VERB, 2 - PREPOSITION, 3 - ADJECTIVE, 4 - ARTICLE: 1
GRAMMAR RULE 1 ERROR: VERBS CAN ONLY FOLLOW NOUNS.
Enter a word (or enter .): fox
Enter its part of speech
0 - NOUN, 1 - VERB, 2 - PREPOSITION, 3 - ADJECTIVE, 4 - ARTICLE: 0
Enter a word (or enter .): jumped
Enter its part of speech
0 - NOUN, 1 - VERB, 2 - PREPOSITION, 3 - ADJECTIVE, 4 - ARTICLE: 1
Enter a word (or enter .): over
Enter its part of speech
0 - NOUN, 1 - VERB, 2 - PREPOSITION, 3 - ADJECTIVE, 4 - ARTICLE: 2
Enter a word (or enter .): the
Enter its part of speech
0 - NOUN, 1 - VERB, 2 - PREPOSITION, 3 - ADJECTIVE, 4 - ARTICLE: 4
Enter a word (or enter .): dog
Enter its part of speech
0 - NOUN, 1 - VERB, 2 - PREPOSITION, 3 - ADJECTIVE, 4 - ARTICLE: 0
Enter a word (or enter .): .
The quick fox jumped over the dog.
Short Problems
1) Write a completable future that creates a list of strings, adds
“Hello” and then “Futures” to the list in separate stages, and then
prints the list.
2) Write a future chain that generates a null string and then attempts
to replace it with the first three characters in the string. If an
exception occurs, the chain replaces the string with “Error”.
Finally, the chain prints the string.
326
Chapter 13 Use in Multithreaded Programs
Long Problems
1) Blackjack is a card game that adds the face values of a player’s
cards. The player attempts to get as close to 21 as possible without
going over.
–– Prompt for state income tax and real estate taxes. Deduction is
100% of total costs.
–– Add the result of each component and print your total deduction.
327
CHAPTER 14
Use in Atomic
Calculations
It is unsafe for execution threads to share data without protecting them with locks or
some other form of synchronization. In the following program, two parallel threads share
an unprotected int variable called num. During an iteration of thread cf2's for loop, the
value -1490 was read from num and stored in local variable x. The JVM and the operating
system then allow thread cf1 to run for a while, changing the value of num to 8290. When
thread cf2 regains control and reads back num, it has the value 8290 instead of -1490
since the value of num has been corrupted by thread cf1.
import java.util.concurrent.*;
class Unsafe
{
static int num = 0;
public static void main(String[] args)
{
CompletableFuture<Integer> cf1 =
CompletableFuture.supplyAsync( () -> {
for (int i=0; i < 10000; ++i)
{
int x = num;
num += 10;
if (num != (x+10))
System.out.println("cf1 THREAD READ ERROR: x= "
+ x +" num= " + num);
329
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0_14
Chapter 14 Use in Atomic Calculations
}
return num;
});
CompletableFuture<Integer> cf2 =
CompletableFuture.supplyAsync( () -> {
for (int i=0; i < 10000; ++i)
{
int x = num;
num -= 10;
if (num != (x-10))
System.out.println("cf2 THREAD READ ERROR: x= "
+ x +" num= " + num);
}
return num;
});
CompletableFuture.allOf(cf1, cf2)
.join();
}
}
PROGRAM OUTPUT:
330
Chapter 14 Use in Atomic Calculations
The previous program can be made thread-safe by replacing the unprotected int
variable with an AtomicInteger. The addAndGet operation cannot be interrupted by
another thread, so the value assigned to x will not be corrupted. The get method returns
the value of the underlying value.
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
class Safe
{
static AtomicInteger num = new AtomicInteger(0);
CompletableFuture<Integer> cf2 =
CompletableFuture.supplyAsync( () -> {
for (int i=0; i < 10000; ++i)
{
int x = num.addAndGet(-10);
}
return num.get();
});
CompletableFuture.allOf(cf1, cf2)
.join();
}
}
331
Chapter 14 Use in Atomic Calculations
OUTPUT:
8
11
332
Chapter 14 Use in Atomic Calculations
OUTPUT:
4
8
11
OUTPUT:
20
15
The getAndUpdate method returns the underlying value before the calculation is
performed.
OUTPUT:
10
20
15
333
Chapter 14 Use in Atomic Calculations
The compareAndExchange method replaces the underlying value with newValue if the
underlying value is equal to expectedValue and returns the result.
The following example adds 10 to an atomic integer and then resets it to 0 when it
reaches 50. Due to memory semantics, the get method needs to be called to display 0 on
the iteration which resets the value (if the compareAndExchange method were wrapped in
a println statement and the get method were not called, 50 would be displayed during
the fifth iteration of the loop instead of 0).
OUTPUT:
10
20
30
40
0
10
20
30
40
The compareAndSet method replaces the underlying value with newValue if the
underlying value is equal to expectedValue. The compareAndSet method returns true if the
exchange was made as opposed to compareAndExchange which returns the witness value.
334
Chapter 14 Use in Atomic Calculations
In the following program, two parallel threads share an atomic integer. Each thread
adds 10 to the integer inside a loop. Thread cf1 uses the compareAndSet method to reset
the integer back to 0 after comparing it with 100. Thread cf2 resets the integer after
comparing it with 80.
import java.util.concurrent.atomic.*;
import java.util.concurrent.*;
class ShareAnInt
{
public static void main(String[] args)
{
AtomicInteger num = new AtomicInteger(0);
CompletableFuture<Integer> cf1 =
CompletableFuture.supplyAsync( () -> {
int x = num.get();
for (int i=0; i<20; ++i)
{
x = num.updateAndGet(z -> z + 10);
if (num.compareAndSet(100, 0))
{
System.out.println("cf1 resets atomic variable");
x = num.get();
}
System.out.println("cf1 a = " + x);
}
return x;
});
CompletableFuture<Integer> cf2 =
CompletableFuture.supplyAsync( () -> {
int x = num.get();
for (int i=0; i<20; ++i)
{
x = num.updateAndGet(z -> z + 10);
if (num.compareAndSet(80, 0))
335
Chapter 14 Use in Atomic Calculations
{
System.out.println("cf2 resets atomic variable");
x = num.get();
}
System.out.println("cf2 a = " + x);
}
return x;
});
CompletableFuture.allOf(cf1,cf2)
.join();
}
}
PROGRAM OUTPUT:
cf2 a = 10
cf1 a = 20
cf2 a = 30
cf1 a = 40
cf2 a = 50
cf1 a = 60
cf2 a = 70
cf1 a = 80
cf2 a = 90
cf1 resets atomic variable
cf2 a = 10
cf1 a = 10
cf2 a = 20
cf1 a = 30
cf2 a = 40
cf1 a = 50
cf2 a = 60
cf1 a = 70
cf2 resets atomic variable
cf1 a = 10
cf2 a = 10
336
Chapter 14 Use in Atomic Calculations
cf1 a = 20
cf2 a = 30
cf1 a = 40
cf2 a = 50
cf1 a = 60
cf1 a = 80
cf1 a = 90
cf1 resets atomic variable
cf1 a = 0
cf1 a = 10
cf1 a = 20
cf1 a = 30
cf1 a = 40
cf1 a = 50
cf2 a = 70
cf2 a = 60
cf2 a = 70
cf2 resets atomic variable
cf2 a = 0
cf2 a = 10
cf2 a = 20
cf2 a = 30
cf2 a = 40
337
Chapter 14 Use in Atomic Calculations
OUTPUT:
5
3
OUTPUT:
true
false
true
338
Chapter 14 Use in Atomic Calculations
OUTPUT:
0: 0
1: 2
2: 4
3: 6
OUTPUT:
R0
R01
R012
R0123
R01234
339
Chapter 14 Use in Atomic Calculations
class IntAndString
{
int i;
String s;
public IntAndString(int x, String y) { i=x; s=y; }
@Override
public String toString() { return i + " " + s; }
}
AtomicReference<IntAndString> refis =
new AtomicReference<>(new IntAndString(2,"HI"));
The index of a loop can be atomically added to the i field in an IntAndString using
the accumulateAndGet method. The first argument to the accumulateAndGet method is
a reference to a new IntAndString object whose i field contains the value of the loop
counter. The binary operator then creates a new IntAndString object which adds this
value to the i field of the existing object and returns a reference to the new object.
OUTPUT:
2 null
3 null
5 null
8 null
12 null
340
Chapter 14 Use in Atomic Calculations
The first person makes deposits of $50 and withdrawals of $70.50. The second person
makes deposits of $20 and withdrawals of $120.50.
Write a program where the transactions made by both persons on the account during the
first week are performed in parallel. Stop executing transactions if the balance is zero or less.
Solution
An atomic variable that contains the account balance can be created. The program can
then perform deposits or withdrawals that update the balance concurrently from two
different threads. A class which wraps a double balance can be written.
class AccountBalance
{
double balance;
public AccountBalance(double d) { balance = d; }
@Override
public String toString() { return Double.toString(balance); }
}
Binary operators for deposits and withdrawals can be defined. These operators will
return a reference to a new AccountBalance object that adds or subtracts an amount to
the existing balance.
341
Chapter 14 Use in Atomic Calculations
class BankAccount
{
static AtomicReference<AccountBalance> b =
new AtomicReference(new AccountBalance(500.0));
The TRANS enumeration can be provided for the transaction type. A Java array of
TRANS can be used by each thread to model the week of activity for each person.
CompletableFuture<AccountBalance> p1 =
CompletableFuture.supplyAsync( () -> {
for (int i=0; i < p1Trans.length && b.get().balance > 0; ++i)
{
if (p1Trans[i] == TRANS.DEP)
accumulate(50.0, deposit);
else
342
Chapter 14 Use in Atomic Calculations
accumulate(70.50, withdrawal);
System.out.println("p1: " + b.get().balance);
}
return null;
});
CompletableFuture<AccountBalance> p2 =
CompletableFuture.supplyAsync( () -> {
for (int i=0; i < p2Trans.length && b.get().balance > 0; ++i)
{
if (p2Trans[i] == TRANS.DEP)
accumulate(20.0, deposit);
else
accumulate(120.50, withdrawal);
System.out.println("p2: " + b.get().balance);
}
return null;
});
CompletableFuture.allOf(p1,p2)
.join();
The complete program is shown in Listing 14-4. In the execution shown as follows,
the fifth transaction of thread p2 sets the balance to a negative number, so the fifth
transaction of thread p1 does not execute.
343
Chapter 14 Use in Atomic Calculations
@Override
public String toString() { return Double.toString(balance); }
}
class BankAccount
{
static AtomicReference<AccountBalance> b =
new AtomicReference(new AccountBalance(500.0));
static void accumulate(double value, BinaryOperator op)
{
b.accumulateAndGet(new AccountBalance(value), op);
}
public static void main(String[] args)
{
TRANS[] p1Trans =
{TRANS.DEP, TRANS.WITH, TRANS.DEP, TRANS.WITH, TRANS.WITH};
TRANS[] p2Trans =
{TRANS.WITH, TRANS.WITH, TRANS.WITH, TRANS.DEP, TRANS.WITH};
CompletableFuture<AccountBalance> p1 =
CompletableFuture.supplyAsync( () -> {
for (int i=0; i < p1Trans.length && b.get().balance > 0;
++i)
{
if (p1Trans[i] == TRANS.DEP)
accumulate(50.0, deposit);
else
accumulate(70.50, withdrawal);
System.out.println("p1: " + b.get().balance);
}
return null;
});
344
Chapter 14 Use in Atomic Calculations
CompletableFuture<AccountBalance> p2 =
CompletableFuture.supplyAsync( () -> {
for (int i=0; i < p2Trans.length && b.get().balance > 0;
++i)
{
if (p2Trans[i] == TRANS.DEP)
accumulate(20.0, deposit);
else
accumulate(120.50, withdrawal);
System.out.println("p2: " + b.get().balance);
}
return null;
});
CompletableFuture.allOf(p1,p2)
.join();
}
}
PROGRAM OUTPUT:
p2: 429.5
p1: 550.0
p2: 309.0
p1: 238.5
p2: 118.0
p1: 168.0
p2: 188.0
p1: 117.5
p2: -3.0
S
hort Problems
1) Using two atomic integers, generate and print the first 10 entries
in the Fibonacci series (see problem 1 from Chapter 7).
345
Chapter 14 Use in Atomic Calculations
Long Problems
1) Write a password generator that prompts the user for his or her
last name. The program creates a password consisting of the first
three letters of the user’s last name concatenated with a random
number between 0 and 1000. For security reasons, a new random
number is atomically generated (on a separate thread) every .5
seconds. Print the resulting password.
3) A teacher and her grader both work on the grades to her class
of 30 students. The grader populates the grades, but the teacher
can at any time select and print a grade or apply a curve to all the
grades. The curve is a constant multiplier applied to each grade.
346
CHAPTER 15
Use in JavaFX
Applications
In Java 11, JavaFX was removed from the JDK and has been made available as a separate
module. In order to run these examples in Java 11, the JavaFX SDK must be installed and
added to the module path. The classes and methods have been available since JavaFX 2.0.
@FunctionalInterface
public interface EventHandler<T extends Event> extends EventListener
{
void handle(T event);
}
The following example implements a handler for an ActionEvent that occurs when a
button is pressed. It increments the count field and then prints a message to the display.
The handler is registered with the button using the setOnAction method.
347
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0_15
Chapter 15 Use in JavaFX Applications
import javafx.application.Application;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
348
Chapter 15 Use in JavaFX Applications
@FunctionalInterface
public interface Builder<T>
{
T build();
}
349
Chapter 15 Use in JavaFX Applications
A label builder can be written that generates a Label component whose label is
provided by the user.
A button builder can be written that generates a Button component whose name is
provided by the user.
The build method of these builders can then be called to add components to the layout.
The complete program is shown as follows. If the user enters “label1” and “button1”
when prompted, then a label entitled “label1” appears above a button entitled “button1”
on the GUI.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import javafx.util.Builder;
import java.util.Scanner;
350
Chapter 15 Use in JavaFX Applications
primaryStage.setTitle("Test Builder");
primaryStage.setScene(scene);
primaryStage.show();
}
351
Chapter 15 Use in JavaFX Applications
@FunctionalInterface
public interface BuilderFactory
{
Builder<?> getBuilder(Class<?> type);
}
352
Chapter 15 Use in JavaFX Applications
The BuilderFactory then returns the Builder corresponding to the class of the
component, or null if the component is not supported.
return factory.getBuilder(c);
}
353
Chapter 15 Use in JavaFX Applications
Class c = null;
if (className.equals("Label"))
c = new Label().getClass();
if (className.equals("Button"))
c = new Button().getClass();
return factory.getBuilder(c);
}
@Override
public void start(Stage primaryStage)
{
list = new ArrayList<>();
VBox box = new VBox();
354
Chapter 15 Use in JavaFX Applications
primaryStage.setTitle("Test Builder");
primaryStage.setScene(scene);
primaryStage.show();
}
355
Chapter 15 Use in JavaFX Applications
356
Chapter 15 Use in JavaFX Applications
Nested class ListChangeListener.Change has many useful methods. Those that are
demonstrated in this section are listed as follows.
357
Chapter 15 Use in JavaFX Applications
System.out.println("\nNew list:");
x.getList().forEach( y -> System.out.println(y));
};
list.addListener(listener);
The list is used to back TextFields value and index and Buttons add and rmv.
When the user clicks the add button, value is added to the end of the list if index is equal
to the size of the list. If index is less than the size of the list, value replaces the element
at index. If index is greater than the size of the list, the request is ignored. When the user
clicks the rmv button, the element at index is removed from the list if index is less than
the size of the list. If index is greater than or equal to the size of the list, the request is
ignored.
This logic is performed with two implementations of EventHander<ActionEvent>,
which are registered using the Button class’s setOnAction method. Whenever an event
handler changed the list, the ListChangeListener is triggered.
add.setOnAction(x -> {
int i = Integer.parseInt(index.getText());
if (list.size() == i)
358
Chapter 15 Use in JavaFX Applications
list.add(value.getText());
else if (list.size() > i)
list.set(i,value.getText());
});
rmv.setOnAction(x -> {
int i = Integer.parseInt(index.getText());
if (list.size() > i)
list.remove(i);
});
import javafx.application.Application;
import javafx.collections.*;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
@Override
public void start(Stage primaryStage)
{
ObservableList<String> list
= FXCollections.observableArrayList();
ListChangeListener listener = x -> {
while (x.next())
{
System.out.print("list event from " + x.getFrom()
+ " TO " + x.getTo() + ":");
if (x.wasAdded())
{
System.out.print(" ADD " + x.getAddedSubList());
359
Chapter 15 Use in JavaFX Applications
}
if (x.wasRemoved())
{
System.out.print(" RMV " + x.getRemoved());
}
if (x.wasReplaced())
{
System.out.print(" RPL from " + x.getFrom()
+ " TO " + x.getTo());
}
}
System.out.println("\nNew list:");
x.getList().forEach( y -> System.out.println(y));
};
list.addListener(listener);
rmv.setOnAction(x -> {
int i = Integer.parseInt(index.getText;
if (list.size() > i)
list.remove(i);
});
360
Chapter 15 Use in JavaFX Applications
361
Chapter 15 Use in JavaFX Applications
362
Chapter 15 Use in JavaFX Applications
set = FXCollections.observableSet();
The set backs TextField element and Buttons add and rmv. Since sets are
unordered, TextField index is not provided. When the user clicks the add button,
element is added to the set if it doesn’t already exist. If element already exists, the
request is ignored. When the user clicks the rmv button, the element is removed if it
exists. If index doesn't exist, the request is ignored.
This logic is performed with event handlers which are registered using the
Button class’s setOnAction method. Whenever an event handler changed the set, the
SetChangeListener is triggered.
363
Chapter 15 Use in JavaFX Applications
import javafx.application.Application;
import javafx.collections.*;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
364
Chapter 15 Use in JavaFX Applications
365
Chapter 15 Use in JavaFX Applications
366
Chapter 15 Use in JavaFX Applications
map = FXCollections.observableHashMap();
map.addListener(listener);
The map is used to back TextFields key and value. Instead of buttons, the text in
key and value is processed when the user hits Enter while the cursor is positioned in
value. This results in a call to the map’s put method with value and key. Whenever an
event handler changed the map, the MapChangeListener is triggered.
value.setOnAction(x ->
map.put(key.getText(),Integer.parseInt(value.getText())));
367
Chapter 15 Use in JavaFX Applications
import javafx.application.Application;
import javafx.collections.*;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
@Override
public void start(Stage primaryStage)
{
map = FXCollections.observableHashMap();
MapChangeListener listener = x -> {
System.out.print("map event:");
if (x.wasAdded())
{
System.out.print(" ADD " + x.getKey() + ","
+ x.getValueAdded());
}
if (x.wasRemoved())
{
System.out.print(" RMV " + x.getKey() + ","
+ x.getValueRemoved());
}
System.out.println("\nNew map:");
x.getMap().forEach( (y,z) -> System.out.println(y + ","
+ z));
};
map.addListener(listener);
368
Chapter 15 Use in JavaFX Applications
value.setOnAction(x ->
map.put(key.getText(),Integer.parseInt(value.getText())));
369
Chapter 15 Use in JavaFX Applications
map = FXCollections.observableHashMap();
TextField key = new TextField();
TextField value = new TextField();
370
Chapter 15 Use in JavaFX Applications
key.textProperty().addListener(inv);
value.setOnAction(x ->
map.put(key.getText(),Integer.parseInt(value.getText()))
);
import javafx.application.Application;
import javafx.collections.*;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
371
Chapter 15 Use in JavaFX Applications
import javafx.stage.Stage;
import javafx.beans.*;
public class TestInvalidation extends Application {
ObservableMap<String,Integer> map;
@Override
public void start(Stage primaryStage)
{
map = FXCollections.observableHashMap();
TextField key = new TextField();
TextField value = new TextField();
MapChangeListener listener = x -> {
x.getMap().forEach( (y,z) -> System.out.println(y + ","
+ z));
};
map.addListener(listener);
key.textProperty().addListener(inv);
value.setOnAction(x ->
map.put(key.getText(),Integer.parseInt(value.getText()))
);
372
Chapter 15 Use in JavaFX Applications
root.getChildren().add(hBox);
primaryStage.setTitle("Invalidation");
primaryStage.setScene(scene);
primaryStage.show();
}
373
Chapter 15 Use in JavaFX Applications
Solution
Four combinations of text fields and labels can be used to prompt the user for input.
Since program should initially prompt the user to add a driver, the labels should initially
display license id, year, month, and day.
A choice box can be used to display the operations that can be performed on a
license. The first choice, “Add Driver”, should be displayed initially.
Label/TextField combination 1 will always be used to input license id. The data
input using the other three combinations will vary based on the selected operation. An
event handler can be registered with the choice box that generates the correct text for
374
Chapter 15 Use in JavaFX Applications
labels 2 through 4 for each selection. The handler should also clear text fields 2 through 4
and shuts down the application when the user selects “Quit”.
box.setOnAction( x -> {
text2.setText("");
text3.setText("");
text4.setText("");
switch (box.getSelectionModel().getSelectedIndex())
{
case 0:
case 4: label2.setText("Year: ");
label3.setText("Month: ");
label4.setText("Day: ");
break;
case 1:
case 3: label2.setText(" ");
label3.setText(" ");
label4.setText(" ");
break;
case 2: label2.setText("Insurance:");
label3.setText(" ");
label4.setText(" ");
break;
case 5:
case 6: label2.setText("Make: ");
label3.setText("Model: ");
label4.setText("Year: ");
break;
default: Platform.exit();
}
});
The expDate and vehicle suppliers from Chapter 9 can be rewritten to retrieve
input values from the text fields.
An ObservableMap can be used to store the data input in the text fields. The
MapChangeListener from the example in Section 4.c can be used to show the user that
the operation was processed successfully.
d = FXCollections.observableHashMap();
Since the HashMap which backs the ObservableMap implements the merge and
computeIfPresent methods, the switch statement from the Chapter 9 solution can be
modified to work with the GUI. The switch statement should be placed inside the event
handler of a button. When the handler modifies the map, its change listener is triggered
and output is displayed.
376
Chapter 15 Use in JavaFX Applications
377
Chapter 15 Use in JavaFX Applications
378
Chapter 15 Use in JavaFX Applications
import javafx.application.*;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import java.util.function.*;
import java.util.*;
import java.time.*;
import javafx.collections.*;
enum STATUS {ACTIVE, SUSPENDED}
class Vehicle
{
String make;
String model;
int year;
public Vehicle(String ma, String mo, int y)
{
make = ma;
model = mo;
year = y;
}
public String toString()
{
return year + " " + make + " " + model;
}
}
class License
{
LocalDate expDate;
String insurance;
STATUS status;
ArrayList<Vehicle> vehicles;
379
Chapter 15 Use in JavaFX Applications
@Override
public void start(Stage primaryStage) {
380
Chapter 15 Use in JavaFX Applications
d = FXCollections.observableHashMap();
MapChangeListener listener = x -> {
System.out.print("map event:");
if (x.wasAdded())
{
System.out.print(" ADD " + x.getKey() + ","
+ x.getValueAdded());
}
if (x.wasRemoved())
{
System.out.print(" RMV " + x.getKey() + ","
+ x.getValueRemoved());
}
System.out.println("\nNew map:");
x.getMap().forEach( (y,z) -> System.out.println(y + ","
+ z));
};
d.addListener(listener);
381
Chapter 15 Use in JavaFX Applications
"Add Vehicle",
"Remove Vehicle",
"Quit");
box.getSelectionModel().selectFirst();
box.setOnAction( x -> {
text2.setText("");
text3.setText("");
text4.setText("");
switch (box.getSelectionModel().getSelectedIndex())
{
case 0:
case 4: label2.setText("Year: ");
label3.setText("Month: ");
label4.setText("Day: ");
break;
case 1:
case 3: label2.setText(" ");
label3.setText(" ");
label4.setText(" ");
break;
case 2: label2.setText("Insurance:");
label3.setText(" ");
label4.setText(" ");
break;
case 5:
case 6: label2.setText("Make: ");
label3.setText("Model: ");
label4.setText("Year: ");
break;
default: Platform.exit();
}
});
382
Chapter 15 Use in JavaFX Applications
383
Chapter 15 Use in JavaFX Applications
384
Chapter 15 Use in JavaFX Applications
temp.vehicles.removeIf(z ->
z.make.equals(r.make)
&& z.model.equals(r.model)
&& z.year == r.year);
return temp;
};
d.merge(text1.getText(),
new License(null,
null,vehicle.get()),
removeVehicle);
}
});
gridPane.add(label1,0,0);
gridPane.add(text1 ,1,0);
gridPane.add(label2,0,1);
gridPane.add(text2 ,1,1);
gridPane.add(label3,0,2);
gridPane.add(text3 ,1,2);
gridPane.add(label4,0,3);
gridPane.add(text4 ,1,3);
gridPane.add(box ,1,4);
gridPane.add(submit,1,5);
primaryStage.setTitle("DMV");
primaryStage.setScene(scene);
primaryStage.show();
}
385
Chapter 15 Use in JavaFX Applications
The program is demonstrated as follows. When driver 123 with expiration date May
7, 2022, is added, an add to map event is triggered and the new entry is displayed. When
driver 123’s insurance policy is changed to INS22, add to map and remove from map
events are triggered and the new map entry containing the insurance policy is displayed.
When the driver’s privilege is suspended, add to map and remove from map events are
triggered and the new map entry showing the SUSPENDED status is displayed. When
driver 123’s license is renewed, add to map and remove from map events are triggered
and the new map entry showing the ACTIVE status and expiration date June 11, 2022, is
displayed. When a 2017 Honda Accord is added to the driver’s license, add to map and
remove from map events are triggered and the new map entry showing the Accord is
displayed. When the Accord is removed from the license, add to map and remove from
map events are triggered and the new map entry with no vehicles is displayed. When
driver 123 is removed from the database, a remove from map event is triggered and the
map is now empty. The user then quits, and the program terminates.
386
Chapter 15 Use in JavaFX Applications
387
Chapter 15 Use in JavaFX Applications
map event: ADD 123,2022-06-11 INS22 ACTIVE [2017 Honda Accord] RMV
123,2022-06-11 INS22
ACTIVE []
New map:
123,2022-06-11 INS22 ACTIVE [2017 Honda Accord]
(select Remove Vehicle)
(type "Honda" in textfield labeled "Make:")
(type "Accord" in textfield labeled "Model:")
(type "2017" in textfield labeled "Year:")
(press submit button)
map event: ADD 123,2022-06-11 INS22 ACTIVE [] RMV 123,2022-06-11 INS22
ACTIVE [2017 Honda Accord]
New map:
123,2022-06-11 INS22 ACTIVE []
(select Remove Driver)
(press submit button)
map event: RMV 123,2022-06-11 INS22 ACTIVE []
New map:
(select Quit)
Short Problems
1) Write a JavaFX builder factory that prompts the user to enter the
name of a text field. The factory will generate the text field and a
label displaying the name of the text field. The label and the text
field should be displayed on the same row with the label on the
left and the text field on the right. Allow the user to enter names
for various text fields until he or she enters “Quit”.
388
Chapter 15 Use in JavaFX Applications
2) Back the text fields in the program you wrote for problem 1
with an observable list of String objects. The program should
write a message to the display whenever an element in the list is
added, removed, or replaced. The message should describe the
contents of the modification and on which element in the list the
modification was performed.
Long Problems
1) It’s tax time, again! Write a program with a JavaFX GUI with the
following text fields and a label for each field:
–– Wages
–– Taxable refunds
–– Adjusted gross income (wages + taxable refunds)
–– Deductions
–– Tax withheld
Back the text fields with an observable list so that all calculations update when the
user enters data in the wages, taxable refunds, deductions, or tax withheld text fields.
389
Chapter 15 Use in JavaFX Applications
2) Write a JavaFX GUI for the poker game from homework problem
1 in Chapter 9. The player is initially dealt five random cards,
whose face values occupy the first column in five rows of text
fields and whose suites occupy the second column. Include a
remove button to the right of each suite. If the player clicks a
remove button, the corresponding card is replaced with another
card drawn at random and the row of text fields is updated. The
player can replace any three cards. After the third card has been
replaced, the hand is evaluated, and if it contains a straight, a
flush, or a full house, the player wins. Otherwise, the dealer wins.
The winner is displayed in a text field beneath the other five rows.
390
APPENDIX
Method References
In Java 8, method references were introduced. They are an alternative to lambda
expressions as a means to represent functional interfaces. Method references offer a
simple syntax, but usually at the cost of defining an additional method.
ClassName::methodName
OUTPUT:
Recall that the Predicate<T> functional interface has functional method test which
evaluates a condition using argument T and returns true if the condition is true. A static
method that takes an argument of type T and returns a boolean can be written. Method
myTest in class PredicateMR as follows performs the greater than 7 test.
391
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0
Appendix Method References
Predicate<Integer> p2 = PredicateMR::myTest;
A call to Predicate p2's test method will then invoke the myTest method.
if (p2.test(9))
System.out.println("Predicate x > 7 is true for x==9.");
import java.util.function.Predicate;
public class PredicateMR
{
public static boolean myTest(Integer x)
{ return (x > 7)? true : false; }
Predicate<Integer> p2 = PredicateMR::myTest;
if (p2.test(9))
System.out.println("Predicate x > 7 is true " +
"for x==9.") ;
}
}
392
Appendix Method References
PROGRAM OUTPUT:
InstanceReference::methodName
The program in Listing A-2 defines classes P1 and P2, both of which provide
instance methods with the same signature as Predicate.test. The main method
creates instances of P1 and P2. Predicate pred1 is represented by method reference
p1::greaterThan7. Predicate pred2 is represented by method reference p2::isOdd.
When the test method of Predicate p1 is invoked, the greaterThan7 method of the P1
object is executed, resulting in true. When the test method of Predicate p2 is invoked,
the isOdd method of the P2 object is executed, resulting in false.
393
Appendix Method References
class TestInstanceMethodReference
{
public static void main(String[] args)
{
P1 p1 = new P1();
P2 p2 = new P2();
Predicate<Integer> pred1 = p1::greaterThan7;
Predicate<Integer> pred2 = p2::isOdd;
System.out.println(pred1.test(8));
System.out.println(pred2.test(8));
}
}
PROGRAM OUTPUT:
true
false
PROGRAM OUTPUT:
10
import java.util.function.*;
class PredicateMethodsMR
{
public static boolean myTest(Integer x)
{ return (x > 7)? true : false; }
395
Appendix Method References
PROGRAM OUTPUT:
The PredicateHelper class in Listing 3-3 defined the static result method that used
a generic predicate to test a value. The TestPredicateHelper program from Listing 3-4
which demonstrates it can be rewritten using method references. Overloaded versions of
method myTest are defined to host the logic previously contained in lambda expressions.
The compiler is able to invoke the correct version of myTest based on the type of the
second argument to the result method. In the first call, the integer literal 6 causes
myTest(Integer) to be invoked. In the second call, the string literal “Hello” causes
myTest(String) to be invoked.
class TestPredicateHelperMR
{
public static boolean myTest(Integer x)
{
return (x > 2)? true : false;
}
public static boolean myTest(String s)
{
return (s.charAt(0) == 'H')? true : false;
}
396
Appendix Method References
PROGRAM OUTPUT:
import java.util.function.Function;
import java.util.ArrayList;
class NumberParserMR
397
Appendix Method References
{
private static <R extends Number> R parse(String x,
Function<String, R> f)
{
return f.apply(x);
}
public static void main(String[] args)
{
ArrayList< Function<String,? extends Number> > list
= new ArrayList<>();
list.add(Byte::parseByte);
list.add(Short::parseShort);
list.add(Integer::parseInt);
list.add(Long::parseLong);
list.add(Float::parseFloat);
list.add(Double::parseDouble);
PROGRAM OUTPUT:
10
20
30
40
50.0
60.0
398
Appendix Method References
class Student
{
String name;
Integer id;
Double gpa;
public Student(String n, int i, double g)
{
name = n;
id = i;
gpa = g;
}
@Override
public String toString() { return name + " " + id + " " + gpa; }
399
Appendix Method References
Students can then be compared by name, id, or GPA. Note that the by GPA
comparison outputs 1 since 3.82 is greater than 3.76 as specified by the compareTo
method of the Double class. If 0.06 is the desired result, then a lambda expression which
performs the subtraction must be used.
System.out.println(byName.compare(s1,s2));
System.out.println(byId.compare(s1,s2));
System.out.println(byGpa.compare(s1,s2));
OUTPUT:
-8
-1
1
class Class
{
String subject;
Collection<Student> students;
public Class(String su, Student... st)
{
subject = su;
students = Arrays.asList(st);
}
Collection<Student> getStudents() { return students; }
}
400
Appendix Method References
Stream.of(
new Class("Biology",
new Student("Joe" ,1001,3.81),
new Student("Mary",1002,3.91)),
new Class("Physics",
new Student("Kalpana",1003,3.61),
new Student("Javier" ,1004,3.71))) // Stream<Class>
.map(Class::getStudents) // Stream<List<Student>>
.forEach(x -> System.out.println(x));
}
}
OUTPUT:
The Stream class’s collect method accepts methods to create and accumulate
stream elements. These methods are often already available in the element, so method
references are common.
The StringBuilder example from Section 9 in Chapter 12 can be rewritten to
utilize method references. The supplier, combiner, and finishers are simply references
to the StringBuilder class’s new, append, and toString methods, respectively. A
reference to the StringBuilder class’s constructor (StringBuilder::new) is used as the
supplier, since the supplier is responsible for generating a StringBuilder object. Since
the accumulator does more than simply call the StringBuilder's append method, a
lambda expression must be used.
String s = Stream.of("Joe","Kalpana","Christopher")
.collect(Collector.of(
StringBuilder::new,
401
Appendix Method References
accs,
StringBuilder::append,
StringBuilder::toString));
System.out.println(s);
OUTPUT:
JKlpnChrstphr
Since the Collectors class’s groupingBy and mapping methods accept functions,
these arguments are frequently represented by method references. Suppose accessors
were added to the Car class from Section 9a in Chapter 12.
class Car
{
String manu;
String model;
int mpg;
public Car(String ma, String mo, int mp)
{
manu = ma;
model = mo;
mpg = mp;
}
public String toString()
{
return manu + " " + model + " gets " + mpg + " mpg";
}
public String getManu() { return manu; }
public String getModel() { return model; }
public int getMpg() { return mpg; }
}
402
Appendix Method References
The following example uses a reference to the Car class’s getManu method to group
cars by manufacturer. A reference to the Car class’s getMpg method is then used to extract
a car’s mpg, which is then collected into a list of integers.
OUTPUT:
403
Index
A BinaryOperator interface, 98–99
BiPredicate interface, 59
ActionListener interface, 40
BuilderFactory functional interface, 352
AllOf.java, 307–308
andThen method, 73, 97, 111, 118, 121
applyAsDouble method, 97, 101 C
ArrayIndexOutOfBounds exception, 37
Calculator.java, 103–106
Array populate, 182–183
Car.java, 151–152
ArraySpliterator .java, 162–164
Chained filter methods, 261
AtomicBoolean class, 338
collectingAndThen method, 271
Atomic calculations
Collection object
accumulating value, 332
motor vehicle department, 191–197
atomicInteger class, 330, 331
remove elements, 181
bank account, project, 341–345
Comparator chain components,
comparing value, 334–335
specializations, 221–222
problems, 345–346
Comparator comparing methods, 212–216
Unsafe.java program, 329–330
Comparator comparing methods,
update variable, 333
specializations, 217–218
AtomicIntegerArray class, 338
Comparator interface, 209–210
AtomicInteger class, comparing value,
Comparator methods, 211–212
334–336
Comparators
AtomicLong class, 337
BinaryOperators, 228–229
AtomicLongArray class, 338
building chains, 218–221
AtomicReference class, 339–340
map interface, 227–228
sort Java arrays, 224–226
sort lists, 222–224
B compareTo method, 213
BankTransactions.java, 122–127 comparingInt method, 217
BiConsumer functional interface, 114–115 Comparing objects, real estate broker,
BiFunction, 78, 84, 184 229–239
405
© Ralph Lecessi 2019
R. Lecessi, Functional Interfaces in Java, https://doi.org/10.1007/978-1-4842-4278-0
Index
E
Element replacement, list/map, 184
G, H
EventHandler functional interface, 347 Generic functional interfaces, 17–19
integer, 18
Receipt class, 14–17
F TwoIntsProcessorAbstract, 18–19
Filter method, stream elements, 261 getBuilder method, 352
FlatMap vs. map, 264, 265 getOp method, 101
406
Index
L
I Lambda expressions, 217
ifPresent method, 248, 249 block form, 35–37
ifPresentOrElse method, 297 ComputeSquare.java, 40–41
IntBinaryOperator, 185 definition, 29, 31–32
IntBinaryOperator ibo, 99 exception handling, 37, 41
IntConsumer’s accept method, 153 functional interfaces, 29–30, 39–40
IntIterator.java, 157 if statements and loops, 36
IntUnaryOperator, 96–97 lambda_arg_list, 32–34
IntUnaryOperator iuo, 97 limitations, 37
Inventory.java, 283–287 TestLambdaScope.java, 31–32
isEqual method, 54 LongIterator.java, 157–158
Iterator objects traverse, 164 LongUnaryOperator, 96
java array, 165–168
M
J, K Map computations, 186–189
Java array traverse, 153–156 Map merging, 189–190
JavaFX mapToInt method, 278
DMV GUI, 374 Map traverse, 171
EventHandler functional Monitoring changes, JavaFX
interface, 347, 349 ListChangeListener, 356, 359
monitoring changes (see Monitoring MapChangeListener, 366
changes, JavaFX) SetChangeListener, 362
Observable object, 370 Multithreaded Programs
TestBuilderFactory.java, 353–355 acceptEitherAsync method, 302–303
TestEventHandler.java, 348–349 AllOf.java, 307–308
UI components, 349 allOf method, 309
add, 350 ArrayIndexOutOfBounds
button, 350 Exception, 306
label, 350 BiFunction, 308–311
TestBuilder.java, 350, 352 Callable implementation, 294
JavaFX builder factory, 388 CancellationException, 305
407
Index
408
Index
409
Index
410