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
- Adapting to changing
- Those discouraging change
- Maintain compatibility
- Low tolerance for change that will break anything
- Preserving the core
- Cant alienate user base
- Maintain compatibility
- 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