Steven Levine
Steven Levine
6 min read

Tags

Recently I attended a talk given by Brian Goetz, about evolving Java. It was refreshing to hear that Java is moving forward again from the Java Language architect himself. Like others, I have mixed feeling about how some of the features were added to the language, but overall, after hearing Brian’s talk, Java is getting the attention it needs to “keep up”.

Below you can read some of the main points from the talk. Brian’s deck can be found here.

Full disclaimer, this post is me taking notes while trying to follow along with what Brian was saying. There might be a few errors. Please let me know if you find one.

This really doesn’t do Brian’s presentation justice, but hope you find it informative about what’s coming in Java 8.

Note: I am working on a follow up post with actual running Java 8 code to demonstrate most of the features mentioned here.

A night with Brian Goetz - NY JAVA Meetup, Dec 3, 2013

  • Java 8 - A new beginning
  • Trying to get Java moving again
  • Get things moving forward without breaking backwards compatibility

Modernizing Java

  • Language
    • Lambda Expressions (closures)
    • Interfaces (default methods)
  • Libraries
    • Bulk data on collections
    • Parallelism

Lambda Expressions

  • Argument list, a return type, and a body
    • (Object o) -> o.toString
  • Can refer to enclosing values
    • (Person p) -> p.getName().equals(name)
  • Method references to an existing method
    • Object::toString()
  • Allows you to treat code as data

History

  • In 1995 most main stream languages did not support closures
  • Today, Java is the last holdout
    • C++ added them recently
    • C# added them in 3.0
    • All new languages have them

Long road

  • 1997 Ordersky - Pizza
  • 2006 - 2008 a vigorous debate, BGGA, and CICE
  • Little language evolution from Java SE 5 (2004)
  • Project coin (small language changes) in Java SE 7
  • Dec 2009 OpenJDK Lambda formed
  • Nov 2010 - JSR-335
  • Current Status
    • Lambda’s, Interface, Bulk Ops

Evolving a mature language

  • Those encouraging change
    • Adapting to changing
      • hardware, attitudes, fashions, problems
  • Those discouraging change
    • Maintain compatibility
      • Low tolerance for change that will break anything
    • Preserving the core
      • Cant alienate user base
  • Adapting to change
    • In 1995 everything was sequential, with imposed order
    • Very deterministic
  • We want to introduce things that are more parallel
  • We had the wrong defaults at the start, namely mutability
  • Hard to undo this default behavior technically as well as in people’s mindsets

Typical Iteration Example (External Iteration)

for (Shape s) {
    if (s.getColor() == RED)
        s.setColor(BLUE);
}
  • Foreach loop hides complex interactions
  • External iteration - client has to drive thus the what and how are intermingled

Inversion of Control

  • Allows libraries to be much more expressive

Lambda Example (Internal Iteration)

shapes.forEach(s -> {
    if (s.getColor() == RED)
        s.setColor(BLUE);
})
  • Internal iteration - client in charge of the what, library in charge of how

Functional Interfaces

  • Predicate<T>, Consumer<T>, Supplier<T>
  • Predicate<String> isEmpty = s -> s.isEmpty()
  • Runnable r = () -> {println("hello");

We could have added function types, but it was obvious and WRONG

  • Would have interacted badly with erasure, introducing complexity and corner cases, would have a notion of old, and new libraries
  • Better to preserve the Core
  • Bonus - existing library are now forward compatible to lambdas

Lambdas enable better APis

  • Enable more powerful API’s
  • Client-Library boundary is more permeable
  • Safer, exposes, more opportunities for optimizations

Example Higher Order Function

iterface Comparartor {
    public static<T, U extends Comparable<? super U>>
    Comparator<T> comparing(Function<T, U> f) {
        return (x, y) -> f.apply(x).compareTo(f.apply(y));
    }
}

Problem: Interface evolution

  • If you add a method to Interface, it will break all implementing libraries (obviously)
  • Source incompatible change, but binary will continue to work
  • Libraries will start looking old
  • Need a way to evolve them or replace them
  • Collections.sort() “bags nailed to side, don’t want to continue this” –BG

Interface with Default Method

interface Collection<T> {
    default void forEach(Consumer<T> action) {
        for (T t: this)
            action.apply(t);
    }
}
  • Can override it, like a virtual method
  • Consumer doesn’t know if they are using default or another implementation found in superclass chain

A question was posed asking why is the default keyword necessary? Can’t the compiler infer if there is an implementation in the IF, it is the default? “Of course it can figure it out… but we wanted extra clarity, deal with it. :) “ –BG

Some might say: “We now have multiple inheritance in Java???”

  • Java always had multiple inheritance of Types
  • This adds multiple inheritance of Behavior
    • But not of state
    • Java interface are stateless (like Fortress’s Traits)

Resolution Rule 1

  • If class can inherit a method from superclass and a superInterface, prefer the superclass
    • Defaults only considered if no method declared in superclass chain
    • True for both concrete and abstract superclass
  • Ensure compatibility with previous versions of Java

Resolution Rule 2

  • If class can inherit a method from two interfaces, and one more specific than (a subtype of) the other, prefer the more specific
    • An implementation in List would take precedence over once in Collection
  • The shape of the inheritance tree doesn’t matter

Resolution Rule 3

  • There is no rule 3!

Class inheriting behavior from two SuperInterfaces

interface A {
    default void m() {}
}
interface B {
    default void m() {}
}

class C implements A, B {
    void m() {A.super.m();}
}
  • If you inherit two superInterface implementations, you (as developer) need to disambiguate which implementation to call
  • The onus is on the developer to decide, not the compiler

Another SuperInterface Example

interface A {
    default void m() {}
}
interface B extends A {}

interface C extends B {}
// gets impl from A
class D implements B, C {}

How Lambda’s can help

Typical Compartor Example

Comparator<Person> byLastName =
    Comparartor.comparing(p -> p.getLastName());
Collections.sort(people, byLastName);
  • We want code to look exactly at the problem statement

Comparing with Lambda’s

Collections.sort(people, comparing(p -> p.getLastName()));

// Option 1, use simple Lambda
people.sort(comparing(p -> p.getLastName()));

// Option 2, use Class method
people.sort(comparing(Person::getLastName));

// We can also "reverse" the Collection
people.sort(comparing(Person::getLastName).reversed());

// Or add an additional compare to the pipeline
people.sort(comparing(Person::getLastName).reversed()
    .thenComparing(Person::getFirstName));

Important thing to be able to look at code and KNOW what it does!

Example from Above

shapes.forEach(s -> {
    if (s.getColor() == RED)
        s.setColor(BLUE);
})
  • Lets say we want to massage the results of the above collection
  • Another new feature added to Collections is Streams

Manipulate all elements of a Collection after applying a Filter

shapes.stream()
    .filter(s -> s.getColor() == RED)
    .forEach(s -> { s.setColor(BLUE); });

Filter and Collect

List<Shape> blueBlcoks
    = shapes.stream()
        .filter(s -> s.getColor() == BLUE)
        .collect(Collections.toList())

Filter, Transform, and then Collect

List<Shape> blueBlcoks
    = shapes.stream()
        .filter(s -> s.getColor() == BLUE)
        .map(Shape::getContainingBox)
        .collect(Collections.toList())

Filter, Map, and then Aggregate

int sumOfWeights =
    = shapes.stream()
        .filter(s -> s.getColor() == BLUE)
        .mapToInt(Shape::getWeight)
        .sum();
  • Believe it (or not), these examples are not any more expensive (perhaps cheaper) than a typical for loop
  • This is possible because it does a single pass on data
    • Creates a pipeline with Filter, then Map, and then Sum invokes them
    • In other words, Filter and Map are lazy operations

Imperative vs Streams

  • Individual data vs Sets
  • Focused on How, vs What
  • Doesn’t read like problem statement vs Code reads like problem statement
  • Steps mashed together vs Well factored

Parallelism

  • Goal: Easy to use parallel libraries for Java

Parallelizing the getWeight operation is easy

int sumOfWeights =
    = shapes.parallelStream()
        .filter(s -> s.getColor() == BLUE)
        .mapToInt(Shape::getWeight)
        .sum();

Conclusion: So why Lambda’s?

  • Its about time! “All the cool kids are doing it” – BG
  • Provide libraries a path to multi-core (needed internal iteration)
  • Empower library developers

Q/A

  • Features left out, whats coming next?
    • Value Types - A long or a decimal (or other Type), that won’t need to be accessed via a pointer, instead directly from a register
    • Useful for static data