Look ma!
I am a functional programmer

 

Java 8 in Action

Michał Urbanek

What is it about?

Why should I care?

  • only java version with support
  • java 7 is EOL since April 2014
  • AppEngine will (once) migrate

It's cool!

Back in the day

Main motivations?

Functional Programming

  • immutability
  • pure functions
  • first class and higher order functions 
  • pattern matching
  • tail recursion
  • declarative
  • hipster :)

What's new?

  • Default methods
  • Lambdas
  • Streams
  • Parallel Data Processing
  • Optionals
  • New Date and Time API
  • Completable Future
  • ...

“Java is still not dead - 
and people are starting to figure that out.”

Default methods

Default methods

  • virtual extension methods
  • non-abstract method implementations in interfaces
  • help APIs to evolve in compatible way
  • static methods in interfaces

Optional methods

public interface Sized {
    int size();

    default boolean isEmpty() {
        return size() == 0;
    }
}
interface Iterator<T> {

    boolean hasNext();

    T next();

    default void remove() {
        throw new UnsupportedOperationException();
    }
}

Multiple inheritance
of behaviour

Resolution Rules

  1. Classes always win
  2. Otherwise, sub-interfaces win
  3. Finally, if choice is ambiguous then class has to explicitly select method implementation

Abstract class vs. Interface

Class can extend only from one abstract class, but a class can implement multiple interfaces.

Abstract class can enforce a common state trough instance variables (fields). An interface can't have instance variables.

Lamda Expressions

Behaviour parametrization
oldschool

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});
Thread t = new Thread(new Runnable() {
    public void run() {
        System.out.println("Hello world");
    }
});

Lambda expression

  • Anonymous - doesn't have a name
  • Function - it has list of params, body and return type
  • Passed Around - can be argument
  • Concise - no boiler plate code
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});
Thread t = new Thread(new Runnable() {
    public void run() {
        System.out.println("Hello world");
    }
});
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, (String a, String b) -> b.compareTo(a));
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, (a, b) -> b.compareTo(a));
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

names.sort((a, b) -> b.compareTo(a));
Thread t = new Thread(() -> { System.out.println("Hello world"); });
Thread t = new Thread(() -> System.out.println("Hello world"));

Behaviour parametrization
newschool

(String s) -> s.length

(Apple a) -> a.getWeight() > 150

() -> 42

(int x, int y) -> {
    System.out.println("Result:");
    System.out.println(x + y);
}
(parameters) -> expression

(parameters) -> { statements; }

Syntax and examples

if (logger.isDebugEnabled()) {
    loger.debug("Cause: " + generateDiagnostics());
}

Deferred execution


loger.debug(() -> "Cause: " + generateDiagnostics());

Functional interfaces

  • must contain exactly one abstract method declaration
  • as many default methods as possible
  • can be annotated with @FuncitonalInterface
@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}

Common functional interfaces

Predicate<T> T -> boolean
Consumer<T> T -> void
Function<T, R> T -> R
Supplier<T> () -> T
UnaryOperator<T> T -> T
BinaryOperator<T> (T, T) -> T
BiPredicate<L, R> (L, R) -> boolean

Method references

  • reuse existing method definitions
  • pass them just like lambdas
  • improve readability
  • use :: keyword (nothing to do with C++)
  • can be also used for constructors

Streams

Streams

  • implement java.util.Stream
  • sequence of elements on which you can perform operations
  • manipulate collections  in a declarative way
  • internal vs external iteration
  • intermediate vs. terminal operations

Streams vs. Collections

Collection Stream
Egarly Constructed Computed on demand
Values spread out in space Values spread out in time
Traversable many times Traversable only once
External iteration Internal iteration

Intermediate vs. Terminal

Intermediate Stream
Return Stream Produce result
Lazy Eager
Can be chained Final
e.g. filter, map, limit, sorted, distinct e.g. forEach, count, collect, sum, min

Why bother?

Parallelism just got easy

Simply change stream() to parallelStream()

animals.stream().filter(Animal::isPredator).count()
animals.parallelStream().filter(Animal::isPredator).count()

and let your extra cores do the job!!

Always measure!

Optional

  • source of error
  • bloats the code
  • meaningless
  • ​breaks java philosophy
  • creates a hole in the type system

Problems with null?

Optional

  • model the absence of a value
  • it's strongly typed
  • forget about the NullPointerException
  • ​less bloated code

Optional

Optional.empty();

Optional.of("Just a text");

Optional.ofNullable("This can be null");

Creation

Optional<String> optional = Optional.of("String");

optional.isPresent();

optional.ifPresent(s -> System.out.println(s));

optional.map(String::length).get();

optional.filter(s -> s.length() > 3).orElse("Default");

optional.orElseGet(() -> "Default");

optional.orElseThrow(() -> new IllegalArgumentException());

Operations

Optional

private String getInsuranceName(Person person) {
    if (person != null) {
        Car car = person.getCar();
        if (car != null) {
            Insurance insurance = car.getInsurance();
            if (insurance != null) {
                String name = insurance.getName();
                return name == null ? UNKNOWN_NAME : name;
            }
        }
    }
    return UNKNOWN_NAME;
}

Null checks made easy

private String getInsuranceNameOptional(Person person) {
    return Optional.ofNullable(person)
            .flatMap(Person::getCarOptional)
            .flatMap(Car::getInsuranceOptional)
            .map(Insurance::getName)
            .orElse(UNKNOWN_NAME);
}

Map improvements

Map<Integer, String> map = new HashMap<>();

// put value only if not already in map
map.putIfAbsent(5, "val5");

// iterate over entries
map.forEach((key, val) -> System.out.println(val));

// compute new value from old value
map.computeIfPresent(3, (key, val) -> key + num);

// compute new value only if not already in map
map.computeIfAbsent(23, key -> "val" + key);

// remove key only if given value
map.remove(3, "val3");

Array operations

int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9};

// sort array in parallel
Arrays.parallelSort(array);

// fill array with computed values in parallel
Arrays.parallelSetAll(array, i -> array.length - i);

// compute array's prefixes in parallel
Arrays.parallelPrefix(array, Integer::sum);

New Date API

  • representing date and time for both humans and machines
  • dealing with different timezones and calendars
  • separation of chronologies
  • package java.time.*
  • immutable value classes
  • thread-safe
  • heavily inspired by joda-time

New Date API

// access to the current date and time (timezone aware)
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();

// access to timezones
ZoneId.getAvailableZoneIds();
ZoneId.of("Europe/Berlin");

// time representation
LocalTime now = LocalTime.now(zone1);
LocalTime late = LocalTime.of(23, 59, 59);

now.isBefore(late);
long hoursBetween = ChronoUnit.HOURS.between(now, late);

// date representation
LocalDate today = LocalDate.now();
LocalDate yesterday = today.minusDays(1);

// date and time representation
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
DateTimeFormatter.ofPattern("MMM dd, yyyy - HH:mm").format(sylvester);

CompletableFuture

  • create asynchronous computation and retrieve its result
  • merging asynchronous operations
  • reacting to the completion of an asynchronous operation

CompletableFuture

List<String> result = IntStream.range(0, 4)
    .mapToObj(i -> CompletableFuture.supplyAsync(
        () -> Computation.compute(i), executor))
    .map(future -> future.thenApply(String::valueOf))
    .map(future -> future.thenCompose(
        s -> CompletableFuture.supplyAsync(() -> "value" + s, executor)))
    .map(CompletableFuture::join)
    .collect(Collectors.toList());

What's next?

What's next?

Java 9

  • Project Jigsaw aka Modules
  • Light-weight JSON API 
  • HTTP 2 Client 
  • Process API Improvements 
  • Improved contended locking 

References

  • Raoul-Gabriel Urma et al. "Java 8 in Action"
  • Richard Warburton "Java 8 Lambdas"

 

  • https://github.com/winterbe/java8-tutorial
  • https://docs.oracle.com/javase/tutorial/java
    /javaOO/lambdaexpressions.html

Look ma! I am a functional programmer! - Java 8 in Action

By urbmic

Look ma! I am a functional programmer! - Java 8 in Action

  • 1,034