A 1-day course notes on practising functional programming in Java 8. Coding examples can be downloaded from https://sites.google.com/site/omarbashirsite/home/library/functional-programming-in-java-8.
3. Default Methods
● Methods implemented in interfaces that are
available to all their implementations.
– Removes the need to provide a common implementation
either in every class implementing the interface or in a
common base class implementation.
– Makes existing interface implementations future-proof.
● Interface implementations can override default
implementations.
● Non-final methods in the Object base class cannot
be overridden by default methods.
4. Default Methods (Example)
● An instrumentation interface for vehicles.
– Methods
● Return trip distance.
● Return trip time.
● Default method to return average speed by dividing the trip
distance by the trip time.
● Implementations
– Aircraft instrumentation to which leg distances with
corresponding times are added.
– Car instrumentation to which distance travelled in hourly
intervals is added.
– Statue instrumentation that returns 0 for all methods.
6. CarInstrumentation
public class CarInstrumentation implements Instrumentation {
private final List<Double> samples;
public CarInstrumentation() {
samples = new ArrayList<>();
}
public void addSample(double distance) {
samples.add(distance);
}
@Override
public double getTripDistance() {
double sum = 0.0;
for(double value : samples) {
sum += value;
}
return sum;
}
@Override
public double getTripTime() {
return samples.size();
}
}
7. AircraftInstrumentation
public class AircraftInstrumentation implements Instrumentation {
private final List<Double> legTimes;
private final List<Double> legDistances;
public AircraftInstrumentation() {
legTimes = new ArrayList<>();
legDistances = new ArrayList<>();
}
public void addLeg(double legDistance, double legTime) {
legTimes.add(legTime);
legDistances.add(legDistance);
}
private double add(List<Double> data) {
double sum = 0.0;
for(double value : data) {
sum += value;
}
return sum;
}
@Override
public double getTripDistance() {
return add(legDistances);
}
@Override
public double getTripTime() {
return add(legTimes);
}
}
8. Statue Interface
public class StatueInstrumentation implements Instrumentation {
@Override
public double getTripDistance() {
return 0;
}
@Override
public double getTripTime() {
return 0;
}
@Override
public double getAverageSpeed() {
return 0;
}
}
9. Example
public static void main(String[] args) {
System.out.printf("Statue speed = %.2fn", new
StatueInstrumentation().getAverageSpeed());
CarInstrumentation carInst = new CarInstrumentation();
carInst.addSample(50);
carInst.addSample(70);
carInst.addSample(60);
System.out.printf("Car speed = %.2fn", carInst.getAverageSpeed());
AircraftInstrumentation planeInst = new AircraftInstrumentation();
planeInst.addLeg(70, 1.5);
planeInst.addLeg(25, 0.25);
planeInst.addLeg(75, 1.25);
planeInst.addLeg(110, 1);
System.out.printf("Plane speed = %.2fn", planeInst.getAverageSpeed());
}
10. Default Methods
● With great power comes great responsibility.
– Developers may be tempted to extend existing
implementations using default methods.
● This may require overriding default methods in some interface
implementation to accommodate specifics of implementations.
– Ideally, default methods should
● contain common functionality across interface
implementations.
● contain functionality that can be defined in terms of the
abstract methods.
– If overriding default methods becomes common, these
may be converted to abstract methods.
11. Static Methods
● Similar to static methods in classes.
● Allows methods to be called without implementing an
interface.
● Allows grouping of relevant utility methods with the
corresponding interfaces.
– No need to write utility companion classes., e.g.,
● Static methods are not part of the implementing
classes.
● To invoke, prefix the static method name with the
name of the interface.
● Static methods can be invoked from default methods.
12. StaticMethods(Example)
public interface Calculator {
static double add(double a, double b) {
return a + b;
}
static double subtract(double a, double b) {
return a - b;
}
static double multiply(double a, double b) {
return a * b;
}
static double divide(double a, double b) {
return a / b;
}
}
public static void main(String[] args) {
double a = 5.0;
double b = 2.5;
System.out.println(Calculator.add(a, b));
System.out.println(Calculator.subtract(a, b));
System.out.println(Calculator.multiply(a, b));
System.out.println(Calculator.divide(a, b));
}
Usage
14. Anonymous Function Calls
● Syntax
– parameter > expression
– Translates to
● function(parameter) where function implementation
contains the expression
– Parameter syntax,
● Parenthesis optional only for single parameters in function call.
● Parameter types are optional.
– Expression syntax
● Braces and return required for multi-line expressions.
● Expressions can be method references.
15. Functional Interfaces
● Specifying type of lambda expressions.
– To assign lambda expressions to variables and used
repeated.
– To declare lambda expressions as function parameters.
● Standard Java interfaces with following exceptions,
– Only one abstract method in the interface.
● SAM (Single Abstract Method) interfaces.
– Can be decorated with annotation
@FunctionalInterface for compiler to check that
interface is SAM interface.
● Functional interfaces for common use cases already
specified in java.util.function package.
– Custom functional interfaces rarely required.
16. Lambda Expression - Example
// BiFunction takes two arguments and returns the result.
// The following statement effectively creates an anonymous
// class implementing BiFunction where the apply method adds the
// two arguments and returns the result.
BiFunction<Integer, Integer, Integer> intAdder = (a, b) -> a + b;
int op1 = 5;
int op2 = 25;
int sum = intAdder.apply(op1, op2);
System.out.printf("%d + %d = %dn", op1, op2, sum);
17. Lambda Expression - Example
public static void main(String[] args) {
int op1 = 25;
int op2 = 5;
int sum = process(op1, op2, (a, b) -> a + b);
int diff = process(op1, op2, (a, b) -> a - b);
int product = process(op1, op2, (a, b) -> a * b);
int div = process(op1, op2, (a, b) -> a / b);
System.out.printf("%d + %d = %dn", op1, op2, sum);
System.out.printf("%d - %d = %dn", op1, op2, diff);
System.out.printf("%d * %d = %dn", op1, op2, product);
System.out.printf("%d / %d = %dn", op1, op2, div);
}
private static int process(
int x,
int y,
BiFunction<Integer, Integer, Integer> processor) {
return processor.apply(x, y);
}
18. Lambda Expression - Example
public class FunctionalCalculator {
public static void main(String[] args) {
if (args.length == 3) {
Map<String, BiFunction<Double, Double, Double>> calculator = new HashMap<>();
calculator.put("+", (a, b) -> a + b);
calculator.put("-", (a, b) -> a - b);
calculator.put("*", (a, b) -> a * b);
calculator.put("/", (a, b) -> a / b);
String operator = args[1];
if (calculator.containsKey(operator)) {
Double operand1 = Double.parseDouble(args[0]);
Double operand2 = Double.parseDouble(args[2]);
double result = calculator.get(operator).apply(operand1, operand2);
System.out.printf("%.2f %s %.2f = %.2f", operand1, operator, operand2, result);
} else {
System.out.printf("Operator %s not recognisedn", operator);
System.out.println("Operators:: +, -, *, /");
}
} else {
System.out.println("USAGE:: Command line arguments operand1 operator operand2");
System.out.println("Operators:: +, -, *, /");
}
}
}
19. Closure
● Lambda expressions allow capturing variables and
methods defined in their scope.
– Lambda expressions close over the scope in which they
exist.
● The state of the variables captured stay with the
expressions even if they are passed to other
methods and executed there.
● Captured variables are effectively final, i.e.,
– Mutating these values results in a compilation error.
20. Closure-Example
public class WeightCalculator {
final double mass;
public WeightCalculator(double mass) {
this.mass = mass;
}
public Function<Double, Double> getCalculator() {
return g -> this.getWeight(mass, g);
}
private double getWeight(double m, double g) {
return m * g;
}
}
public static void main(String[] args) {
double mass = 10;
Function<Double, Double> calculator =
new WeightCalculator(mass).getCalculator();
double weightOnEarth = calculator.apply(9.8);
double weightOnMars = calculator.apply(3.77);
System.out.printf("%.2f kg on Earth is %.2f N on Earthn",
mass, weightOnEarth);
System.out.printf("%.2f kg on Earth is %.2f N on Marsn",
mass, weightOnMars);
}
21. Method Referencing
● Obtaining the reference of a method so that
– It can be assigned to a matching functional interface
variable,
– And that functional interface can be executed later.
● :: is the referencing operator.
● Applies to,
– Static methods, e.g., ClassName::staticMethodName.
– Instance methods, e.g.,
ObjectRef::instanceMethodName.
– Constructors, e.g., ClassName::new.
24. Optional
● Container class that may or may not contain a non-
null value.
● Contains methods that
– determine presence or absence of value.
– perform operations based on presence or absence of a
value.
– operate on the contained value.
● Protects against NullPointerException.
25. Key Members
● Construction using static factory methods,
– empty()
● returns an empty Optional instance.
– of(T value)
● returns an Optional instance with value.
– ofNullable(T value)
● returns an Optional instance with the supplied value if non-
nullable, an empty instance otherwise.
26. Key Members
● Accessors,
– get()
● returns contained value or throws NoSuchElementException if
empty.
– isPresent()
● returns true if not empty.
– orElse(T other)
● returns enclosed value if not empty otherwise returns other.
– orElseGet(Supplier<? extends T> other)
● returns enclosed value if not empty otherwise calls other to get
a value.
27. Key Members
● Transformation, consumer and filter,
– ifPresent(Consumer<? super T> consumer)
● calls consumer on value if present otherwise does nothing.
– filter(Predicate<? super T> predicate)
● returns this Optional if the value is present and predicate returns
true otherwise returns empty Optional.
– map(Function<? super T, ? extends U> mapper)
● transforms the value using the mapper if present otherwise does
nothing.
– flatMap(Function<? super T, Optional<U>>
mapper)
● applies mapper to value and returns an Optional bearing
transformed value.
28. Optional-Example
/**
* Find at matching name from the list and if present. print the value
* after converting it into uppercase.
* @param args
*/
public static void main(String[] args) {
List<String> names = Arrays.asList("George Patton", "Omar Bradley",
"Bernard Montgomery", "Erwin Romel");
Optional<String> name = find(names, "Brad");
name.map(x -> x.toUpperCase()).ifPresent(x -> System.out.println(x));
System.out.println("DONE");
}
private static Optional<String> find(List<String> names, String value) {
Optional<String> result = Optional.empty();
boolean found = false;
Iterator<String> itr = names.iterator();
while (!found && itr.hasNext()) {
String item = itr.next();
if (item.contains(value)) {
found = true;
result = Optional.of(item);
}
}
return result;
}
30. Streams
● An abstraction allowing generic and declarative
processing of collections.
● Operations on streams are
– Intermediate operations
● Return a stream object representing intermediate results.
● Allow chaining of operations.
– Terminal operations
● Either perform a void operation or return a non-stream result.
● Immutable operations
– Original data source is not modified.
31. Streams
● Obtaining a stream on a collection,
– stream() to return a sequential stream.
– parallelStream() to create a stream capable of
executing in multiple threads.
● Streams evaluated lazily,
– Once a terminal operation is executed.
● Streams cannot be reused,
– Once a terminal operation executed, the stream is closed.
– Call get() on the stream before calling a terminal
operation to get a copy of the stream.
32. Streams - Example
public class Finder {
/**
* Find at matching name from the list and if present. print the
* value after converting it into uppercase.
* @param args
*/
public static void main(String[] args) {
List<String> names = Arrays.asList("George Patton",
"Omar Bradley", "Bernard Montgomery", "Erwin Romel");
Optional<String> name = names.stream().
filter(x -> x.contains("Bernard")).findFirst();
name.map(x -> x.toUpperCase()).
ifPresent(x -> System.out.println(x));
System.out.println("Done");
}
}
33. Collectors
● Implementation of the Collector interface.
● Performs useful reduction operations on stream,
e.g.,
– Accumulation of elements into collections,
– Summarising values,
– Grouping,
– Partitioning
● Typically used in the collect method of the stream to
return the final collection after the stream pipeline
has executed.
34. Collectors - Example
/**
* Return a list of even numbers from a list of string
* representation of integers.
* @param args
*/
List<String> numStr = Arrays.asList("53", "26", "33", "77",
"2", "8", "9", "98");
List<Integer> evenNums = numStr.stream().
map(x -> Integer.parseInt(x)).
filter(x -> x % 2 == 0).
collect(Collectors.toList());
evenNums.forEach(System.out::println);
35. Stream Reduction - Example
List<Double> nums = Arrays.asList(25.5, 23.25, 17.0, 9.25);
/*
* reduce takes an initial accumulator value and a BiFunction
* extension that takes the accumulated value, iteratively adds
* the elements in the stream.
*/
double average = nums.stream().
reduce(0.0, (acc, x) -> acc + x)/nums.size();
System.out.printf("Average = %.3f", average);
37. Creating Streams
● static Stream<T> of(T... values)
– Creates a stream of values specified.
● static IntStream range(int inclusiveStart,
int exclusiveEnd)
– Sequentially ordered stream of integers with increment of
1 excluding the end value.
● static IntStream rangeClosed(int
inclusiveStart, int inclusiveEnd)
– Sequentially ordered stream of integers with increment of
1 including the end value.
38. IntStream - Example
IntStream range = IntStream.rangeClosed(1, num);
int factorial = range.reduce(1, (acc, x) -> acc * x);
System.out.printf("%d! = %dn", num, factorial);
40. flatMap
● Consider a collection of collections of items.
– flatMap used to transform items of inner collections.
– and then flatten the collection of collections of
transformed items to a collection of transformed
items.
● Defined for
– Stream,
– Optional
41. Example – Stream flatMap
List<List<String>> linesOfWords =
Arrays.asList(Arrays.asList("Incy", "wincy", "spider"),
Arrays.asList("Climbing", "up", "the", "spout"));
Stream<String> dataStream = linesOfWords.stream().
flatMap(line -> line.stream().map(word -> word.toUpperCase()));
List<String> data = dataStream.collect(Collectors.toList());
data.forEach(System.out::println);
INCY
WINCY
SPIDER
CLIMBING
UP
THE
SPOUT