Talk Goals
- Not an introduction to Java 8 features
- Look at Java 8 features from API designer perspective
- How to add them to existing libraries in a compatible way
- Use when designing new APIs
Why do we define API?
- Provide a well specified and supported building block
- Limit inter-component coupling
- A cover story to save you from having to tell the truth
- Allow implementations to change without ripple effect
- Stability for clients is paramount
Forms of Compatibility
- Binary: clients must continue to work without recompiling
- Contract: can weaken preconditions, strengthen postconditions
- Environment: avoid increasing restrictions on runtime environment
- Source: not required, but make reasonable efforts
Will you please start talking about Java 8 now?
API Designer's Guide to Java Releases
- Ensure you run properly on new version
- Wait 5 years
- Consider adopting new language features
So Why Care About Java 8?
- Your clients may be using Java 8 right away
- Key Java 8 features are forwards-compatible!
- Finally some new tools for API designers
- Java 8 designed with compatibility in mind
New Feature Summary
- Lambda expressions
- Method references
- Streams
- Interfaces with method bodies
- Annotations on type references
Lambda Expressions
- Lambda expressions will appear in client code, not API
- An interface with exactly one abstract method can be represented as lambda
- @FunctionalInterface is optional
- Encourages functional programming style
- Your clients can use this today
Lambda != inner class
- Not just syntactic sugar!!
- Typically no class file created for lambda
- Compiled into invokedynamic byte code that can be optimized further by JVM
Functional Example: Resource Visitor
private int doCount(IResource root) {
int count = 1;
if (root instanceof IContainer) {
IResource[] kids = ((IContainer) root).members();
for (IResource resource : kids) {
count += doCount(resource);
}
}
return count;
}
180 milliseconds to count 28,000 resources
Functional Example: Resource Visitor
private int doCount(IResource root) {
final int[] count = new int[] {0};
root.accept(proxy -> ++count[0] > 0, IResource.NONE);
return count[0];
}
5 milliseconds to count 28,000 resources (36x faster)
Why is it faster?
- Described what we wanted, rather than how
- Implemented with direct walk over data structure
- Vastly less garbage
This is nothing to do with lambda expressions, but demonstrates advantage of functional style
Many Eclipse Examples
- Most listener APIs
- Job / IJobFunction
- IContextFunction
- IAdaptable
- IResourceVisitor
- IWorkspaceRunnable
- IRunnableWithProgress
Once you require Java 8
- Use class library function types where possible (java.util.function)
- Use consistent terms: Function, Consumer, Supplier, Predicate, BinaryOperator
- Use default methods as needed to ensure one abstract method
Gotcha: Ambiguous References
public void accept(IResourceProxyVisitor v, int d, int f)
...
public void accept(IResourceVisitor v, int d, int f)
...
//error: ambiguous reference
resource.accept(res->true, IResource,DEPTH_INFINITE, IResource.NONE);
Binary compatible but not source compatible
Streams
- Represent a pipeline on which a series of functions operate
- Infinite, lazy
- Don't modify underlying source
- Provide filter, collect, map, reduce, sort
- Enables parallel execution via spliterator
- Consider providing stream-oriented view of your data structures
Method References
- Can provide API methods that implement common functions
- Clients can use method references to invoke them
- Reference static method
- Reference instance method on an object
- Reference instance method on objects of a given type
- Constructor references
- Can add to API without Java 8 dependency
Method Reference Example
public class ResourceCounter {
private int count = 0;
public boolean countResources(IResource resource) {
count++;
return true;
}
public boolean countFiles(IResource resource) {
if (proxy.getType() == IResource.FILE)
count++;
return true;
}
public int getCount() {
return count;
}
}
Method Reference Example
- Method reference appears in client code
- None of the API involved has Java 8 dependency
ResourceCounter counter = new ResourceCounter();
root.accept(counter::countResources);
return counter.getCount();
Static methods in interfaces
“Not even the fifth most exciting thing in Java 8”
- Alex Buckley
- Handy for convenience methods
- Binary compatible to add
- Require dependency on Java 8
- Conversion between static and default not compatible
Default Methods
- Interfaces can now provide method bodies
- Ideal for evolving existing interfaces implemented by clients
- Somewhat limited: only public, no access to state
public interface Comparator<T> {
default Comparator<T> thenComparing(Comparator<? super T> other) { ... }
}
Comparator<String> cmp = Comparator.comparingInt(String:length)
.thenComparing(String.CASE_INSENSITIVE_ORDER);
Compatibility of Default Methods
- Binary compatible to convert to default
- Adding new default method: binary compatible, may break contract compatibility
- Introducing ambiguous inheritance creates source incompatibility
Interfaces vs Abstract Classes
- Interfaces:
- Clean separation between API and implementation
- Multiple inheritance, doesn't lock client into your class hierarchy
- Can completely swap implementations
- Abstract classes:
- Can evolve without breaking subclasses
- Provide function that accesses/manipulates state
- Private state and function
Interface Evolution
- Can't change an interface without breaking implementations
- Anti-pattern: IPerspectiveListener4, ITextViewerExtension8
- Solution: specify when interfaces should not be implemented by clients
- Use abstract classes in cases where client extension is needed (IMoveDeleteHook -> TeamHook)
Interfaces With Default Methods
- Abstract classes still much more powerful and flexible
- Use on functional interfaces to maintain SAM restriction
- Not needed for interfaces not implemented by clients
- Main usage for evolving existing interfaces
Type Annotations
public String findOldest(List<@ReadOnly String> list)
throws @Fatal IllegalStateException { ... }
- Can annotate type references in your API signatures
- Can add new annotation types in your API for these contexts
- Watch out for contract compatibility
- Use @Target to indicate where annotation can be used
- Feels incomplete: no built in annotations, no class library integration
Other Annotation Changes
- Repeating annotations @Repeatable
- Converting existing annotation to repeatable is compatible
- Can add "this" as first formal method parameter
- Adding "this" parameter is binary compatible
public class Dragon implements Animal{
public String roar(Dragon this) {
return "Roar!";
}
}
Key Impressions
- Finally some tools to help evolve APIs
- Can start making your APIs Java 8 friendly without Java 8
- Lambdas and streams will be a big mental shift for traditional Java developers
- Complete integration with Java class libraries
- Oracle's Java 8 documentation rocks, always look there first