You may be hearing a lot of buzz around functional programming. For example, Java 8 recently introduced new features (lambda expressions and method references) and APIs (Streams, Optional and CompletableFutures) inspired from functional ideas such as first-class functions, composition and immutability.
However, what does this mean for my existing codebase?
In this talk we show how you can refactor your traditional object-oriented Java to using FP features and APIs from Java 8 in a beneficial manner.
We will discuss:
- How to adapt to requirement changes using first-class functions
- How you can enhance code reusability using currying
- How you can make your code more robust by favouring immutability over mutability
- How you can design better APIs and reduce unintended null pointer exceptions using an optional data type"
14. First-class functions
● Scary functional-programming terminology for a common object-oriented
pattern (strategy, function object…)
● All it means is the ability to use a function (method reference, lambda,
object representing a function) just like a regular value
○ Pass it as argument to a method
○ Store it in a variable
● Lets you cope with requirement changes by representing and passing
the new requirement as a function
16. Composing functions: example
import java.util.function.Predicate;
Predicate<Invoice> isFacebookInvoice = this::isFacebookInvoice;
List<Invoice> facebookAndTraining =
invoices.stream()
.filter( isFacebookInvoice.and(this::isTrainingInvoice))
.collect(toList());
List<Invoice> facebookOrGoogle =
invoices.stream()
creating more complex
functions from building blocks
.filter( isFacebookInvoice.or(this::isGoogleInvoice))
.collect(toList());
17. Composing functions: why does it work?
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
}
returns a new
function that is
the result of
composing two
functions
18. Creating function pipelines (1)
public class Letter {
private final String message;
public Letter(String message) {
this.message = message;
}
public Letter addDefaultHeader(){
return new Letter("From her majesty:n" + message);
}
public Letter checkSpelling(){
return new Letter(message.replaceAll("FTW", "for the win"));
}
public Letter addDefaultFooter(){
return new Letter(message + "nKind regards");
}
}
19. Creating function pipelines (2)
import java.util.function.Function;
Function<Letter, Letter> addHeader = Letter::addDefaultHeader;
Function<Letter, Letter> processingPipeline =
addHeader.andThen(Letter::checkSpelling)
.andThen(Letter::addDefaultFooter);
andThen andThen
composing
functions
addHeader checkSpelling addFooter
Letter
{message="Java
8 FTW!"}
Letter{message='From
her majesty:
Java 8 for the win!
Kind regards'}
27. A curry function
// int -> (int -> int)
IntFunction<IntUnaryOperator>
curry(IntBinaryOperator biFunction) {
return f -> s -> biFunction.applyAsInt(f, s);
}
// Usage:
IntFunction<IntUnaryOperator> add = curry((f, s) -> f +
s);
int result = add.apply(1)
.applyAsInt(2);
assertEquals(3, result);
28. Generalised curry function
// F -> (S -> R)
<F, S, R> Function<F, Function<S, R>>
curry(BiFunction<F, S, R> biFunction) {
return f -> s -> biFunction.apply(f, s);
}
29. Summary
● Currying is about splitting up the arguments of a function.
● Partial Application is a function “eating” some of its arguments and
returning a new function
● You can write your functions in curried form from the beginning or use
a function to curry them.
31. Mutable objects
public class TrainJourney {
private int price;
private TrainJourney onward;
public TrainJourney(int p, TrainJourney t) {
price = p;
onward = t;
}
public int getPrice() {
return price;
}
public TrainJourney getOnward() {
return onward;
}
public void setPrice(int price) {
this.price = price;
}
public void setOnward(TrainJourney onward) {
this.onward = onward;
}
}
32. Immutable objects
public class TrainJourneyImmutable {
private final int price;
private final TrainJourneyImmutable onward;
public TrainJourneyImmutable(int p, TrainJourneyImmutable t) {
price = p;
onward = t;
}
public int getPrice() {
return price;
}
public TrainJourneyImmutable getOnward() {
return onward;
}
public TrainJourneyImmutable setPrice(int price) {
return new TrainJourneyImmutable(price, getOnward());
}
public TrainJourneyImmutable setOnward(TrainJourneyImmutable onward)
{
return new TrainJourneyImmutable(getPrice(), onward);
}
}
33. Mutable approach
public TrainJourney link(TrainJourney tj1, TrainJourney tj2) {
if (tj1 == null) {
return tj2;
}
TrainJourney t = tj1;
while (t.getOnward() != null) {
t = t.getOnward();
}
t.setOnward(tj2);
return tj1;
}
39. Related topics
● Domain Driven Design
○ Value Classes are Immutable
● Core Java Improvements
○ New date & time library in Java 8 has many Immutable Objects
○ Current Value Types proposal is immutable
● Tooling
○ final keyword only bans reassignment
○ JSR 308 - improved annotation opportunities
○ Mutability Detector
○ Findbugs
44. Defensive checking
public String getCarInsuranceName(Person person) {
if (person != null) {
Car car = person.getCar();
if (car != null) {
Insurance insurance = car.getInsurance();
if (insurance != null) {
return insurance.getName();
}
}
}
return "No Insurance";
}
45. Optional
● Java 8 introduces a new class java.util.Optional<T>
○ a single-value container
● Explicit modelling
○ Immediately clear that its an optional value
○ better maintainability
● You need to actively unwrap an Optional
○ force users to think about the absence case
○ fewer errors
46. Updating model
public class Person {
private Optional<Car> car;
public Optional<Car> getCar() { return car; }
}
public class Car {
private Optional<Insurance> insurance;
public Optional<Insurance> getInsurance() { return insurance; }
}
public class Insurance {
private String name;
public String getName() { return name; }
}
47. Refactoring our example
public String getCarInsuranceName(Person person) {
return Optional.ofNullable(person)
.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("No Insurance");
}
50. Summary of benefits
● First-class functions let you cope for requirement changes
● Currying lets you re-use code logic
● Immutability reduces the scope for bugs
● Optional data types lets you reduce null checking boilerplate and
prevent bugs
54. Example currying use cases
● Large factory methods
○ Partially apply some of the arguments and then pass this factory
object around.
● Parser Combinators
○ many(‘a’) - function which parses a, aa, aaa, etc.