Java 8: Game Changer

presented by Jack Frosch

About Me

1st Career

About Me

1st Career

About Me

2nd Career

About Me

3rd Career?

Before we begin...

  • It's Java 8 not Java 1.8
  • Java 8 has many useful, but boring, enhancements
  • We're just going to talk about the game changers

Java 8 Overview

The Game Changers

  • Interface enhancements: So simple, yet so helpful
  • Lambda expressions: Gateway to functional programming
  • Streams: Freeing developers to think about what not how

Java <8

  • Abstract methods 
  • No state; only static final constants
  • Interfaces can inherit from multiple interfaces
  • Classes can implement multiple interfaces

Java 8+

  • Abstract methods
  • No state; only static final constants
  • Interfaces can inherit from multiple interfaces
  • Classes can implement multiple interfaces
  • Default methods
  • Static methods
  • @FunctionalInterface

Interface Comparison

Demo classic interface

(if desired)

Interfaces since Java 1.6

// starting in 1.5 interfaces could be declared with generics
public class Dog implements Comparable<Dog> {
    final String ownerLastName;
    final String name;

    public Dog(String name, String ownerLastName) {
        this.name = name;
        this.ownerLastName = ownerLastName;
    }

    public String toString() {
        return ownerLastName + ", " + name;
    }

    @Override // starting in 1.6 @Override applied to interfaces
    public int compareTo(Dog other) {
        int result = ownerLastName.compareTo(other.ownerLastName);
        if (result == 0) {
            result = name.compareTo(other.name);
        }
        return result;
    }

    ...

Interfaces since Java 1.6

    ...

    public static void main(String[] args) {
        Dog[] dogs = { new Dog("Fido", "Smith"), 
                       new Dog("Fluffy", "Jones"), 
                       new Dog("Fido", "Jones") };

        System.out.println("Unsorted:");
        for(Dog dog: dogs) {
            System.out.println(dog);
        }

        List<Dog> dawgs = Arrays.asList(dogs);  // Varargs came along in 1.5
        Collections.sort(dawgs);                // Collections as of 1.5
        System.out.println("\nSorted:");
        for(Dog dog: dawgs) {
            System.out.println(dog);
        }
    }
}
Unsorted:
Smith, Fido
Jones, Fluffy
Jones, Fido

Sorted:
Jones, Fido
Jones, Fluffy
Smith, Fido

What if Oracle did this in Java?

// For illustration only. Java 8 doesn't do this!
public interface Comparable<T> {

    int compareTo(T o);

    // Let's add a new method
    boolean isComparativelyEqual(T o);
}

Chaos!

https://marketingmuster.files.wordpress.com/2011/06/homer.png

Anger!

http://www.memes.com/meme/498049

Confusion!

http://i.giphy.com/mvoxdYnpyk23u.gif

Prior to Java 8

  • Interface method(s) were effectively frozen
  • Any changes would break published API
  • Java could not evolve its APIs

Something had to change or Java might be doomed by its inability to adapt to developer expectations

Java 8 interfaces get default methods

// For illustration only. Java 8 doesn't do this!
public interface Comparable<T> {

    int compareTo(T o);

    // Let's add a new method
    default boolean isComparativelyEqual(T o) {
        return compareTo(o) == 0;    
    }
}

Now Java API designers can add default behaviors to interfaces!

Default method demo

Default method demo

public interface CustomComparable<T> extends Comparable<T> {

    // This is contrived!!! From JavaDoc for Comparable:
    //   "It is strongly recommended (though not required) that
    //    natural orderings be consistent with equals."
    default boolean isComparativelyEqual(T o) {
        return compareTo(o) == 0;
    }

}

Default method demo

public class Dog2 implements CustomComparable<Dog2> {
    final String ownerLastName;
    final String name;
    final LocalDate lastVaccination;    // LocalDate new to Java 8

    public Dog2(String name, String ownerLastName, LocalDate lastVaccination) {
        this.name = name;
        this.ownerLastName = ownerLastName;
        this.lastVaccination = lastVaccination;
    }

    public String toString() {
        return String.format("%s, %s [Last Vaccination: %s]",
                        ownerLastName, name,
                        lastVaccination.format(DateTimeFormatter.ISO_LOCAL_DATE));
    }

    @Override
    public int compareTo(Dog2 other) {
        int result = ownerLastName.compareTo(other.ownerLastName);
        if (result == 0) {
            result = name.compareTo(other.name);
        }
        return result;
    }

    ...

Default method demo

    ...

    // Let's include lastVaccination date in equals
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Dog2 dog2 = (Dog2) o;
        return Objects.equals(ownerLastName, dog2.ownerLastName) &&
                Objects.equals(name, dog2.name) &&
                Objects.equals(lastVaccination, dog2.lastVaccination);
    }

    @Override
    public int hashCode() {
        return Objects.hash(ownerLastName, name, lastVaccination);
    }

    ...

Default method demo

    ...

    public static void main(String[] args) {
        Dog2 fido_smith = new Dog2("Fido", "Smith", LocalDate.now());
        Dog2 fido_smith2 = new Dog2("Fido", "Smith", LocalDate.now()
                                                              .minus(3, ChronoUnit.WEEKS));
        Dog2 fluffy_jones = new Dog2("Fluffy", "Jones", LocalDate.of(2015, Month.JULY, 1));
        Dog2 fido_jones = new Dog2("Fido", "Jones", LocalDate.parse("2015-02-28"));

        Dog2[] dogs = { fido_smith, fido_jones, fluffy_jones, fido_smith2 };

        // omitted sorting demo code as it was the same as before

        System.out.println("\nComparatively Equal");
        System.out.println("Are Fido Jones & Fido Smith equal? "
                                + fido_jones.equals(fido_smith));
        System.out.println("Are Fido Jones & Fido Smith comparatively equal? " 
                                + fido_jones.isComparativelyEqual(fido_smith));
        System.out.println("Are Fido Smith & Fido Smith #2 equal? " 
                                + fido_smith.equals(fido_smith2));
        System.out.println("Are Fido Smith & Fido Smith #2 comparatively equal? " 
                                + fido_smith.isComparativelyEqual(fido_smith2));
    }

Default method demo

Unsorted:
Smith, Fido [Last Vaccination: 2015-10-04]
Jones, Fido [Last Vaccination: 2015-02-28]
Jones, Fluffy [Last Vaccination: 2015-07-01]
Smith, Fido [Last Vaccination: 2015-09-13]

Sorted:
Jones, Fido [Last Vaccination: 2015-02-28]
Jones, Fluffy [Last Vaccination: 2015-07-01]
Smith, Fido [Last Vaccination: 2015-10-04]
Smith, Fido [Last Vaccination: 2015-09-13]

Comparatively Equal
Are Fido Jones & Fido Smith equal? false
Are Fido Jones & Fido Smith comparatively equal? false
Are Fido Smith & Fido Smith #2 equal? false
Are Fido Smith & Fido Smith #2 comparatively equal? true

And the diamond problem?

Method resolution

  1. Subtypes carry default methods from super types

  2. A subtype's default method overrides super type's
  3. Class implementations of method override any interface default implementation of the same method
  4. A conflict between two default methods or a default method and an abstract method requires class to implement method. Note: Can't just make the class abstract!

Demo default method resolution

(if desired)

Default method resolution

public interface Greetable {
    void greet();
}

public interface English extends Greetable {
    default void greet() { System.out.println("Hello"); }
}

public interface French extends Greetable {
    default void greet() { System.out.println("Bonjour"); }
}

public interface German extends Greetable {
    default void greet() { System.out.println("Guten Tag"); }
}

public interface AmericanEnglish extends English {
    default void greet() { System.out.println("Hi"); }

    // We need to do this to unhide the English method
    default void greetFormalEnglish() {
        English.super.greet();
    }
}

Default method resolution

public class Greeter implements AmericanEnglish, French, German {

    // Comment greet() implementation  out to see the compiler complain
    @Override
    public void greet() {
        System.out.println("Howdy");
    }

    public static void main(String[] args) {
        Greeter greeter = new Greeter();
        greeter.greet();
        greeter.greetInFrench();
        greeter.greetInGerman();
        greeter.greetInAmericanEnglish();
        greeter.greetInEnglish();

    }

    private void greetInFrench() { French.super.greet(); }

    private void greetInGerman() { German.super.greet(); }

    private void greetInAmericanEnglish() { AmericanEnglish.super.greet(); }

    private void greetInEnglish() { AmericanEnglish.super.greetFormalEnglish(); }
}
Howdy
Bonjour
Guten Tag
Hi
Hello

Functional Interfaces

  • Declare a single, abstract method (SAM)
  • May implement static method(s)
  • May implement default method(s)
  • May be tagged with @FunctionalInterface
    • Compiler will complain if you have other than one abstract method

Functional Interfaces

  • Some examples you know:
    • Comparable
    • Runnable
  • Some new to Java 8
    • Consumer
    • Predicate
    • Function
    • ... many more
@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     * ...
     */
    boolean test(T t);

    /**
     * Returns a composed predicate that represents a short-circuiting logical
     * AND of this predicate and another.
     * ...
     */
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    // ...
}

Predicate

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    /**
     * Returns a composed {@code Consumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code Consumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

Consumer

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);

    /**
     * Returns a composed function that first applies the {@code before}
     * function to its input, and then applies this function to the result.
     *
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    // ...
}

Function

What do they give us?

If a method takes a functional interface as a parameter,

then we can pass the following:

  • An anonymous inner class, the old-fashioned way (but why??)
  • A lambda expression; i.e. (arg) -> { statement } 
  • A method or constructor reference

Method takes a Predicate

// a method that accepts a Predicate
public List<Integer> pickSome(List<Integer> values,
                              Predicate<Integer> pickingStrategy) {

    List<Integer> results = new ArrayList<>();
    for(Integer candidate: values) {
        if(pickingStrategy.test(candidate)) {
            results.add(candidate);
        }
    }

    return results;
}

Use Anonymous Inner Class

List<Integer> values = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

List<Integer> evens = pickSome(values, new Predicate<Integer>() {
                                             @Override
                                             public boolean test(Integer value) {
                                                 // pick evens
                                                 return value % 2 == 0;
                                             }
                                         });

// results: 2,4,6,8,10

Use Lambda expression

List<Integer> values = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// Let's pick evens
List<Integer> evens = pickSome(values, value -> value % 2 == 0);

// results: 2,4,6,8,10

Lambda Expressions


// Think of lambda expressions as disembodied methods
// the -> operator separates arguments from implementation

// general form
(TypeA argument1, TypeB argument2) -> {
                                        doSomething... ;
                                        doDomethingElse... ;
                                        return something;
                                      }
// no-args
() -> { statement body }

// one argument
(TypeC argument1) -> { statement body }

// type can often be inferred by compiler (but will be non-final)
(argument1) -> { statement body }

// if statement body is one statement, braces optional
(argument1) -> statement

// argument parentheses optional for one argument
value -> statement

forEach(Consumer interface)

// The Iterable interface now has a default forEach method

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

//.........

// Instead of:
    for(Foo foo : fooList) {
        foo.doSomething);
    }

// We can do this
    fooList.forEach(Foo foo -> foo.doSomething());

// But type can be inferred...
    fooList.forEach(foo -> foo.doSomething());

There's even a simpler way using method references

Method references

Kind Example
Reference to a static method ContainingClass::staticMethodName
Reference to an instance method of a particular object containingObject::
instanceMethodName
Reference to an instance method of an arbitrary object of a particular type ContainingType::methodName
Reference to a constructor ClassName::new

Use Method reference

// Think of method references are shorthand expressions 
// where argument(s) can be inferred

// General syntax is
// instance::methodName or Type::methodName

// When to use? Whenever you might use a lamdba expression 
// just to pass a value through to a method

Instead of:
    list.forEach(Foo foo -> foo.doSomething())
use:
    list.forEach(Foo::doSomething)

If you have a method declared like:
    map(Function<T, R> f) {...}

instead of lambda to map an element from a List<String>:
    map(value -> value.toUpperCase())
use:
    map(String::toUpperCase)

Use method references for cleaner code

// Example of how we can get to point of using method reference

List<Integer> values = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

values.forEach((String value) -> System.out.println(value));

// better
values.forEach((value) -> System.out.println(value));

// better still
values.forEach(value -> System.out.println(value));

// best
values.forEach(System.out::println);

// results: 1,2,3,4,5,6,7,8,9,10

Method / C'tor reference demo

Method reference demo

public class MethodReferenceDemo {

    public static void main(String[] args) {

        List<String> names = Arrays.asList("Barb", "Bob", "Sarah", "Steve");

        System.out.println("Using lambda to output names in list");
        names.forEach(name -> System.out.println(name));


        System.out.println("\nLet's imperatively transform the collection");
        System.out.println("Then use method reference to print them out...");

        List<String> transformedValues = new ArrayList<String>();
        names.forEach(name -> transformedValues.add(name.toUpperCase()));
        transformedValues.forEach(System.out::println);
    }
}
Using lambda
Barb
Bob
Sarah
Steve

Let's imperatively transform the collection
Then use method reference to print them out...
BARB
BOB
SARAH
STEVE

C'tor reference demo

public class CtorReferenceDemo {
    static class Foo {

        static int instanceCount;
        private Foo() {
            instanceCount++;
        }

        static Foo create(Supplier<Foo> supplier) {
            return supplier.get();
        }
    }

    public static void main(String[] args) {
        for(int i = 1; i <= 10; i++) {
            Foo foo = Foo.create(Foo::new);
            System.out.println(foo.instanceCount);
        }
    }
}
1
2
3
4
5
6
7
8
9
10

Streams

  • The imperative approach
  • The same thing done with functional approach
  • Interesting notes about streams

Imperative approach

BigDecimal calculateTaxableTotal() {
    BigDecimal taxableTotal = BigDecimal.ZERO;

    for(LineItem item: lineItems) {
        if(item.isTaxable()) {
            taxableTotal = taxableTotal.add(item.calculateTotal());
        }
    }
    return taxableTotal;
}

Declare mutable

Iterate

Filter

Mutate

What's wrong here?

  • "The love of mutation is the root of all evil"
  • We've buried the filtering logic inside a loop
  • We're telling the program how to iterate, so computers laugh at us after we go home
  • Even though only some of the items are taxable, we loop all the way through the loop to make sure we have all of the taxable items taxed 

We finally yield an answer. Now do it concurrently!

Functional approach

public BigDecimal calculateTaxableTotal() {
    return lineItems.stream().filter(LineItem::isTaxable)
                             .map(LineItem::calculateTotal)
                             .reduce(BigDecimal.ZERO, BigDecimal::add);
}

What's going on here?

  • None of our variables are mutated
  • We've said we want to filter using method reference
  • We transformed our LineItem to a BigDecimal using calculateTotal
  • We're telling the program to iterate, not how to iterate
  • Since only some of the items are taxable, the operations are lazy up to reduce 

Lazy Evaluation

public class LazyEvalExample {
    static int inTheZone;

    static Integer map(final Integer val) {
        System.out.println("Transforming " + val + "...");
        return val * val;
    }

    static boolean filter(final Integer val) {
        System.out.println("Filtering " + val + "...");
        return val >= inTheZone - 5 && val % 7 == 0;
    }

    public static void main(String[] args) {
        inTheZone = new Random().nextInt(100);

        System.out.println("The inTheZone value: " + inTheZone);

        Stream<Integer> values = IntStream.rangeClosed(1, 100).boxed();
        Stream<Integer> partiallyProcessed = values.filter(LazyEvalExample::filter)
                                                    .map(LazyEvalExample::map);

        System.out.println("We've prepared to filter and map transform the values...");
        System.out.println("... now find and transform upon executing the terminal method");
        int firstFoundSquare = partiallyProcessed.findFirst().get();

        System.out.println("The square of the first found value " + firstFoundSquare);
    }
}

Lazy Evaluation

The inTheZone value: 25
We've prepared to filter and map transform the values...
... now find and transform upon executing the terminal method
Filtering 1...
Filtering 2...
Filtering 3...
Filtering 4...
Filtering 5...
Filtering 6...
Filtering 7...
Filtering 8...
Filtering 9...
Filtering 10...
Filtering 11...
Filtering 12...
Filtering 13...
Filtering 14...
Filtering 15...
Filtering 16...
Filtering 17...
Filtering 18...
Filtering 19...
Filtering 20...
Filtering 21...
Transforming 21...
The square of the first found value 441

Full Functional Demo

A LineItem

class LineItem {
    static long TIME_TO_CALCULATE_TOTAL = 50;

    private final Order order;
    private final String product;
    private final int quantity;
    private final BigDecimal salePrice;
    private final boolean taxable;

    LineItem(Order order, String product, int quantity,
                    BigDecimal salePrice, boolean taxable) {
        this.order = order;
        this.product = product;
        this.quantity = quantity;
        this.salePrice = salePrice;
        this.taxable = taxable;
    }

    BigDecimal calculateTotal() {
        sleepAwhile();    // sleep for TIME_TO_CALCULATE_TOTAL ms
        return salePrice.multiply(new BigDecimal(quantity));
    }

    boolean isTaxable() { return taxable; }
    ...

An Order has LineItems

class Order {
    String orderId;
    LocalDate orderDate;
    BigDecimal taxRate;
    final List<LineItem> lineItems = new ArrayList<>();

    Order(String orderId, LocalDate orderDate, BigDecimal taxRate) {
        ...
    }

    BigDecimal calculateTaxableTotal() {
        BigDecimal taxableTotal = BigDecimal.ZERO;
       for(LineItem item: lineItems) {
            if(item.isTaxable()) { taxableTotal = taxableTotal.add(item.calculateTotal()); }
        }
        return taxableTotal;
    }

    BigDecimal calculateNontaxableTotal() {
        BigDecimal nontaxableTotal = BigDecimal.ZERO;
        for(LineItem item: lineItems) {
            if(!item.isTaxable()) { nontaxableTotal = nontaxableTotal.add(item.calculateTotal()); }
        }
        return nontaxableTotal;
    }
    BigDecimal calculateTax() {
        return calculateTaxableTotal().multiply(taxRate).setScale(2, BigDecimal.ROUND_HALF_EVEN);
    }

    BigDecimal calculateTotal() {
        return calculateNontaxableTotal().add(calculateTaxableTotal().add(calculateTax()));
    }
    ...

An OrderFactory

public class OrderFactory {
    static Order createOrder() {
        Order order = new Order("Order 1", LocalDate.now(), new BigDecimal("0.05"));
        addLineItems(order);
        return order;
    }

    static void addLineItems(Order order) {
        order.addLineItem(new LineItem(order, "Radio", 1, new BigDecimal("100.00"), true));
        order.addLineItem(new LineItem(order, "Book", 2, new BigDecimal("15.00"), true));
        order.addLineItem(new LineItem(order, "DVD", 1, new BigDecimal("10.00"), true));
        order.addLineItem(new LineItem(order, "Milk", 1, new BigDecimal("3.50"), false));
        order.addLineItem(new LineItem(order, "Fruit", 5, new BigDecimal("3.00"), false));
        order.addLineItem(new LineItem(order, "Alcohol", 5, new BigDecimal("20.00"), true));
        order.addLineItem(new LineItem(order, "Cereal", 3, new BigDecimal("5.00"), false));
        order.addLineItem(new LineItem(order, "Soup", 10, new BigDecimal("1.00"), false));
        order.addLineItem(new LineItem(order, "Bread", 2, new BigDecimal("3.00"), false));
        order.addLineItem(new LineItem(order, "Eggs", 2, new BigDecimal("2.50"), false));
    }

    ...
}

Let's test it

public class ImperativeExample {

    public static void main(String[] args) {

        long start = System.currentTimeMillis();

        Order order = OrderFactory.createOrder();
        System.out.println("Non-taxable total: " + order.calculateNontaxableTotal());
        System.out.println("Taxable total: " + order.calculateTaxableTotal());
        System.out.println("Tax: " + order.calculateTax());
        System.out.println("Order total: " + order.calculateTotal());

        System.out.println("Total time (secs): " + 
                            (System.currentTimeMillis() - start) / 1000.0);
    }
}
Non-taxable total: 54.50
Taxable total: 240.00
Tax: 12.00
Order total: 306.50
Total time (secs): 1.61

A more functional Order

public class Order2 extends Order {
    Order2(String orderId, LocalDate orderDate, BigDecimal taxRate) {
        super(orderId, orderDate, taxRate);
    }

    @Override
    public BigDecimal calculateTaxableTotal() {
        return getStream().filter(LineItem::isTaxable)
                          .map(LineItem::calculateTotal)
                          .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    @Override
    public BigDecimal calculateNontaxableTotal() {
        return getStream().filter(item -> !item.isTaxable())
                          .map(LineItem::calculateTotal)
                          .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    private Stream<LineItem> getStream() {
        return lineItems.stream();
    }
}

Modify our OrderFactory

public class OrderFactory {
    ...

    static Order2 createOrder2() {
        Order2 order = new Order2("Order 2", LocalDate.now(), new BigDecimal("0.05"));
        addLineItems(order);
        return order;
    }

    ...
}

Let's test it

public class FunctionalExample {

    public static void main(String[] args) {
        Order order = OrderFactory.createOrder2();

        long start = System.currentTimeMillis();

        System.out.println("Non-taxable total: " + order.calculateNontaxableTotal());
        System.out.println("Taxable total: " + order.calculateTaxableTotal());
        System.out.println("Tax: " + order.calculateTax());
        System.out.println("Order total: " + order.calculateTotal());

        System.out.println("Total time (secs): " 
                            + (System.currentTimeMillis() - start) / 1000.0);
    }
}
Non-taxable total: 54.50
Taxable total: 240.00
Tax: 12.00
Order total: 306.50
Total time (secs): 1.489
The imperative way...

Non-taxable total: 54.50
Taxable total: 240.00
Tax: 12.00
Order total: 306.50
Total time (secs): 1.61

What have we learned

  • Functional gives us the same results with less code
  • Functional code is more fluent
  • Performance is about equal

Let's change it up a bit...

  • Slow down the LineItem calculateTotal
  • Switch to parallel stream and see it go!

A LineItem

class LineItem {
    static long TIME_TO_CALCULATE_TOTAL = 500; // slow it down

    private final Order order;
    private final String product;
    private final int quantity;
    private final BigDecimal salePrice;
    private final boolean taxable;

    LineItem(Order order, String product, int quantity,
                    BigDecimal salePrice, boolean taxable) {
        this.order = order;
        this.product = product;
        this.quantity = quantity;
        this.salePrice = salePrice;
        this.taxable = taxable;
    }

    BigDecimal calculateTotal() {
        sleepAwhile();
        return salePrice.multiply(new BigDecimal(quantity));
    }

    boolean isTaxable() { return taxable; }
    ...

Let's test it slowed down

public class FunctionalExample {

    public static void main(String[] args) {
        Order order = OrderFactory.createOrder2();

        long start = System.currentTimeMillis();

        System.out.println("Non-taxable total: " + order.calculateNontaxableTotal());
        System.out.println("Taxable total: " + order.calculateTaxableTotal());
        System.out.println("Tax: " + order.calculateTax());
        System.out.println("Order total: " + order.calculateTotal());

        System.out.println("Total time (secs): " 
                            + (System.currentTimeMillis() - start) / 1000.0);
    }
}
Slowed down...

Non-taxable total: 54.50
Taxable total: 240.00
Tax: 12.00
Order total: 306.50
Total time (secs): 14.083

A faster functional Order

public class Order2 extends Order {
    Order2(String orderId, LocalDate orderDate, BigDecimal taxRate) {
        super(orderId, orderDate, taxRate);
    }

    @Override
    public BigDecimal calculateTaxableTotal() {
        return getStream().filter(LineItem::isTaxable)
                          .map(LineItem::calculateTotal)
                          .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    @Override
    public BigDecimal calculateNontaxableTotal() {
        return getStream().filter(item -> !item.isTaxable())
                          .map(LineItem::calculateTotal)
                          .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    private Stream<LineItem> getStream() {
        return lineItems.parallelStream();    // do it with multiple threads
    }
}

Performance with parallelStream

public class FunctionalExample {

    public static void main(String[] args) {
        Order order = OrderFactory.createOrder2();

        long start = System.currentTimeMillis();

        System.out.println("Non-taxable total: " + order.calculateNontaxableTotal());
        System.out.println("Taxable total: " + order.calculateTaxableTotal());
        System.out.println("Tax: " + order.calculateTax());
        System.out.println("Order total: " + order.calculateTotal());

        System.out.println("Total time (secs): " 
                            + (System.currentTimeMillis() - start) / 1000.0);
    }
}
Non-taxable total: 54.50
Taxable total: 240.00
Tax: 12.00
Order total: 306.50
Total time (secs): 3.031
The single-threaded way...

Non-taxable total: 54.50
Taxable total: 240.00
Tax: 12.00
Order total: 306.50
Total time (secs): 14.083

You've earned a bonus

Bonus: Testing Lambdas

//... package and imports omitted
public class LambdaTestDemo {
    final List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Predicate<Integer> numberPicker;

    @Before
    public void setUp() { numberPicker = value -> value % 2 != 0; }

    @Test
    public void testIsOddNumberLambda() { assertTrue("101 is odd", 
                                             numberPicker.test(101)); }

    @Test
    public void testOddNumberLambda_YieldsFalseForEvenNumbers() {
          assertFalse("100 is not odd", numberPicker.test(100));
    }

    @Test
    public void testOddNumberPicker() {
        List<Integer> odds = numbers.stream()
                                    .filter(numberPicker)
                                    .collect(Collectors.toList());
        assertEquals(Arrays.asList(1, 3, 5, 7, 9), odds);
    }
}

Bonus: Groovy & Java 8

import java.util.function.Predicate
import java.util.stream.Collectors

public List<Integer> pickSome(List<Integer> values,
                              Predicate<Integer> pickingStrategy) {
    values.stream()
          .filter(pickingStrategy)
          .collect(Collectors.toList())
}

List<Integer> values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

List<Integer> evens = pickSome(values, {value -> value % 2 == 0})
println evens

List<Integer> odds = pickSome(values) { value -> value % 2 != 0 }
println odds

// Groovy already had these functional behaviors years ago :-)
def threes = values.findAll { value -> value % 3 == 0 }
println threes

Summary

  • Functional interfaces & lambda expressions are cool
  • However, they're just the tip of the iceberg
  • The real game changer is the new way you'll start thinking about programming functionally
    • Stop telling computers how to do simple things. Otherwise, you just give them something to laugh at us about after we go home!

Resources

http://384uqqh5pka2ma24ild282mv.wpengine.netdna-cdn.com/wp-content/uploads/2014/01/JavaLam-1.jpg

Title image of Duke chiseling Lambda from:

"Computers laugh at us after we go home..." courtesy of Neal Ford, @neal4d

Questions?

Q. How to test lambda expressions?

A. See bonus slide and code

 

Q. What about functional programming with futures?

A. See http://bit.ly/java8-functional-futures-article

 

Recently asked questions...

Thank you!

Java8: GameChanger

By Jack Frosch

Java8: GameChanger

Java 8's functional programming enhancements are a true game changer

  • 511
Loading comments...

More from Jack Frosch