This document provides a summary of modern Java features introduced between Java 1.1 and Java 8. Some key updates include lambda expressions and method references in Java 8 that allow for more concise functional-style programming. Java 8 also introduced default methods in interfaces, streams for functional-style collections operations, and Date-Time API improvements. Other additions were parallel processing support, CompletableFuture for asynchronous non-blocking code, and Nashorn JavaScript integration. Java 9 will focus on a new module system.
3. A brief history…
• September 2004 – Java 5 (Java as we know it!)
• Generics, annotations, enum, varargs, for-each, static imports, Scanner,
auto-boxing/unboxing, concurrent utilities (task scheduling, atomics, locks…)
• December 2006 – Java 6 (one of the most popular versions)
• General back-end improvements, nothing new that’s noteworthy
• July 2011 – Java 7 (Project Coin)
• invokedynamic, Strings in switch, type inference (“diamonds”), binary
literals, try-with-resources, fork/join framework, multi-catch, new file I/O…
• March 2014 – Java 8
• July 2017 (we hope!) – Java 9
4. Why is Java 8 so significant?
• Like Java 5, Java 8 makes fundamental additions to the language
• It (should!) change how programmers think and code
• It embraces functional programming
• It brings Java to the modern era
• Java is “plagued” by its origins
• Lots of “modern” JVM languages are far superior: Groovy, Scala, Kotlin…
• Java 8 makes significant strides to keep up to date
5. default and static methods in interfaces
• Interface methods can contain a default implementation
• Implementing classes do not need to override default methods
• “Poor man’s multiple inheritance”?
• Partially removes the need for X as interface and AbstractX
• A convenient way to make it less laborious to implement interfaces
• Allows interfaces to evolve without breaking compatibility
• Static methods also allow for evolution – no longer need to have a
wrapper class for implementing utility methods
• Partially motivated by other new features in Java 8
7. class BC1 {
public void foo() {
System.out.println(“BC1::foo”);
}
}
class Derived extends BC1 implements I1, I2 {}
• Calling new Derived().foo() results in “BC1::foo”
8. • What if you have:
class Derived implements I1, I2 {}
new Derived().foo();
• This won’t compile – which foo() do we mean?
• Need to implement it to resolve this issue
• Can still call the base implementations if we want
class Derived implements I1, I2 {
public void foo() {
System.out.println(“Derived::foo”);
I1.super.foo();
I2.super.foo();
}
}
9. Functional interfaces
• A functional interface has exactly one non-default method
• Runnable is a functional interface – just one method void run()
• An interface with one (unimplemented) method is really a function
• Think about it: when you pass a Runnable to a Thread, you are really telling
the thread to execute the body of the run() method, and nothing else
• Due to Java’s principle of “everything has to be an object”, functions haven’t
been treated with the dignity they deserve.
• Functional interfaces promote functions to “first-class” citizens
• “Hey daddy, can I go alone?” “No, you need to be wrapped in a class.”
• This is no longer the case with Java 8
10. Lambda expressions
• This is the “headline” feature of Java 8 (deservedly so!)
• A replacement for anonymous functional interface implementations
• You no longer need the “ceremony” for doing this common task
• A lambda expression allows you to define the body of a functional
interface’s method without all the boilerplate around it
11. Before Java 8
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.print(currentThread().getName());
}
});
12. With lambdas
Thread t = new Thread (() -> {
System.out.print(currentThread().getName());
});
13. Method references
• Even more concise than lambdas
• Suppose all you want to do is pass the parameters to another method
• E.g. a Runnable in ThreadFactory
• With lambdas, this is as follows:
ThreadFactory tf = new ThreadFactory(r -> {
return new Thread(r);
});
14. ThreadFactory before Java 8
ThreadFactory tf = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r);
}
};
16. Things to know with lambdas
• Lambdas are not simply syntactic sugar for anonymous inner classes
• No .class files are generated – dynamic dispatch used instead
• Variables of the outer class accessed within the body of lambda
expression should be (effectively) final
• That is, you can’t change the value from within the lambda expression
• Their value must be assigned – need to be statically determinable
• You can’t throw checked exceptions from within a lambda
• Exceptions need to be handled within the lambda
• There are ways to “cheat” this using wrappers
17. java.util.function
• This is what drives lambda expressions and method references
• Function<T,R> R apply(T t);
• Consumer<T> void accept(T t);
• Supplier<T> T get();
• Predicate<T> boolean test(T t);
• UnaryOperator<T> T apply(T t);
• Also has function types for every combination of int, long, double
• E.g. IntConsumer, IntToLongFunction, DoubleToIntConsumer etc.
• Every interface also has a “Bi” version – accepts two parameters
• e.g. BiFunction<T,U,R> R apply(T t, U u);
18. Example: Collections.forEach
• Collections API (java.util.Collection and friends) has been updated to
take advantage of the functional interfaces
• One of the most common tasks is to iterate over a collection
Arrays.asList(5, 7, 9, 16).forEach(System.out::println);
• forEach expects a Consumer<T> where T is the type of the collection
19. Streams API (java.util.stream)
• A Stream is an abstract pipeline of computation
• Allows for performing aggregate operations on data sources
• A data source could be a collection, arrays, iterators, I/O etc.
• java.util.stream only consists of interfaces, not implementation
• Includes Stream<T>, IntStream, DoubleStream, LongStream etc.
• Common operations include:
• map, reduce, filter, findAny, findFirst, forEach, count, distinct, iterate,
generate, limit, min, max, sorted, skip, peek, of, noneMatch, allMatch,
anyMatch, collect, toArray
20. Properties of Streams
• Lazily evaluated
• Most (“intermediate”) operations just build up the pipeline of computation
• Only “terminal operations” will trigger any actual “work”
• Terminal operations include forEach, collect, findAny, sum, reduce etc.
• Streams do not store any data; nor do they modify the source
• Always return a new Stream for intermediate operations
• Streams are consumable – traverse the data source only once
• Single-pass pipeline and laziness make streams very efficient
• Can be unbounded (infinite streams)
• A convenient way to express data-parallel computations
21. Language evolution with example
• Suppose you have a set of names
• Create a capitalised subset of the names shorter than 5 characters
E.g. [“Sarah”, “Beth”, “Hamid”, “Marcus”, “Chris”, “Jim”]
becomes
[“BETH”, “CHRIS”, “JIM”]
22. Java 1
Set getShortNames(Set names) {
Set shortNames = new HashSet();
Iterator iter = names.iterator();
while (iter.hasNext()) {
String name = (String) iter.next();
if (name.length() < 5)
shortNames.add(name.toUpperCase());
}
return shortNames;
}
23. Java 5
Set<String> getShortNames(Set<String> names) {
Set<String> shortNames = new HashSet<String>();
for (String name : names) {
if (name.length() < 5)
shortNames.add(name.toUpperCase());
}
return shortNames;
}
25. Extension 1
• Get the resulting set of names as a single string separated by commas
• For example:
Set [“Chris”, “Charlie”, “Megan”, “Amy”, “Ben”, “Zoe”]
becomes:
String “CHRIS, AMY, BEN, ZOE”
26. Imperative style (Java 7)
String getShortNames(Set<String> names) {
StringBuilder shortNames = new StringBuilder();
Iterator iter = names.iterator();
for (int i = 0; i < iter.hasNext(); i++) {
String name = iter.next();
if (name.length() < 5)
shortNames.append(name.toUpperCase());
if (i != names.size())
shortNames.append(“, ”);
}
return shortNames.toString();
}
32. Efficiency example
“Find the square root of the first even number greater than k.”
Suppose:
List<Integer> numbers = asList(1, 2, 5, 3, 7, 8, 12, 10);
and
k = 4
33. Imperative style
double result = 0d;
for (number : numbers) {
if (number > k && number % 2 == 0) {
result = Math.sqrt(number);
break;
}
}
return result;
34. Stream (functional style)
return numbers.stream()
.filter(number -> number > k)
.filter(number -> number % 2 == 0)
.map(Math::sqrt)
.findFirst();
35. Which does more work?
• Imperative style:
• 1 < 4, 2 < 4, 5 > 4, 5 % 2 > 0, 3 < 4, 7 > 4, 7 % 2 > 0, 8 > 4, 8 % 2 == 0, sqrt(8)
• So that’s a total of 10 evaluations to get the answer
• This is what the Stream implementation DOES NOT do:
• Look for all numbers greater than k in the list
• Look for all even numbers (greater than k) in the list
• Get the square root (of all even numbers greater than k) in the list
• The stream composes all intermediate operations
• It applies as many operations to each element first before moving on
• E.g. “is 2 > 4? Move on. Is 5 > 4? Is it even? Move on…”
36. This is essentially equivalent:
return numbers.stream()
.filter(num -> num > k && num % 2 == 0)
.map(Math::sqrt)
.findFirst();
So a Stream is not only more readable and declarative (what to do, not
how to do it) but it does this as efficiently as the imperative style.
37. Handling the result
• What if the list contains no even numbers?
• What if the list contains no numbers greater than k?
• What if the list is empty?
• Imperative style:
• return some arbitrary double value
• Could be misinterpreted– no way to know that it couldn’t find a value!
• Nobody likes handling exceptions!
• Functional style:
• return an OptionalDouble
38. Optional<T>
• An immutable container which may or may not be null
• Calling get() if no value is present will throw an unchecked exception
• Can get an Optional from static methods:
• Optional.of(T value) throws NullPointerException
• Optional.ofNullable(T value)
• Optional.empty()
• Can apply operations based on whether value is present
• ifPresent(Consumer<? super T> consumer)
• map and filter
• T orElse(T other), T orElseGet(Supplier<> other), orElseThrow
39. Optional chaining example
String customerNameByID(List<Customer> customers, int id) {
return customers.stream()
.filter(c -> c.getID() == id)
.findFirst() //Optional<Customer>
.map(Customer::getName)
.filter(Customer::isValidName)
.orElse(“UNKNOWN”);
}
If customer with id exists, return their name. Otherwise return something else.
40. Infinite Stream example
long totals = LongStream.generate(() ->
currentTimeMillis() % 1000)
.parallel()
.limit(1_000_000)
.sum();
• This will continuously generate numbers according to the logic
provided in the Supplier and add them up
• If you remove the limit, it’ll max out your CPU forever!
• The stream of numbers isn’t stored – it’s computed on-demand and
fed through the processing pipeline
41. Parallelism and Asynchronicity
• Parallel streams use the common ForkJoinPool by default
• Uses (number of logical cores - 1) threads
• Can easily get the computation to be handled by another pool
Future<Long> busyWork = new ForkJoinPool(numThreads)
.submit(getComputation(256));
...//do other busy work in the current thread
long execTime = busyWork.get(); //Blocked!
42. static Callable<Long> getComputation(final int target) {
return () -> {
final long startTime = nanoTime();
IntStream.range(0, target)
.parallel().forEach(loop -> {
int result = ThreadLocalRandom.current()
.ints(0, 2147483647)
.filter(i -> i <= target)
.findAny().getAsInt();
System.out.println(result);
});
return nanoTime()-startTime;
};
}
43. Triggering computations
• So far, we have seen parallel but synchronous (blocking)
• Even though our computation starts on submission to the Executor, we don’t
know when it’s done
• Calling .get() forces us to wait for the computation to finish
• Can use a ScheduledExecutorService to periodically execute (or
delay execution of) a ScheduledFuture
• But we want to have more flexibility and automation, not delay tasks
• Wouldn’t it be great if we could declare our computations and chain
them together to trigger automatically when they’re done?
• This could be used to handle dependencies between tasks, for example
44. CompletionStage<T>
• Complex interface (38 methods) for chaining computations
• Computation type may be Function, Consumer or Runnable
• May be triggered by one or two stages (including both and either)
• Execution can be default, async or async with custom Executor
• Flexible exception handling semantics
• BiConsumer/BiFunction where either result or exception is null
• Specify an exception handling function for this stage
• Exceptional completion is propagated downstream (to dependencies)
• toCompletableFuture() returns an interoperable implementation
45. CompletableFuture<T>
• implements CompletionStage<T>, Future<T> (total 59 methods!)
• Provides a non-blocking API for Future using callbacks
• Cancellation results in exceptional completion
• Can be explicitly completed (or exceptionally completed)
• T getNow(T valueIfAbsent) – non-blocking, returns fallback if absent
• Multiple ways to obtain a CompletableFuture
• supplyAsync, runAsync, completedFuture or no-args constructor
• Can combine any number of CompletableFuture to trigger completion
• static CompletableFuture<Void> allOf(CompletableFuture... cfs)
• static CompletableFuture<Object> anyOf(CompletableFuture... cfs)
46. CompletableFuture<Void> promise = CompletableFuture
.supplyAsync(() -> compute(target), executor)
.thenApply(Duration::ofNanos)
.thenApply(PerformanceTest::formatDuration)
.thenAccept(System.out::println)
.exceptionally(System.err::println);
for (boolean b = true; !promise.isDone(); b = !b) {
out.println(b ? "tick" : "tock");
Thread.sleep(1000);
}
47. Nashorn (JavaScript in Java)
• Java-based JavaScript engine (replaces Rhino)
• jjs (terminal command) gives you a JavaScript REPL (like nodejs)
• Can invoke and evaluate JS directly from Java (file or string)
• You can even get the result of the last expression as a native Java object!
• Everything in Nashorn is based on Java objects, so can be passed around
• Can invoke functions/methods written in JS from Java (and vice-versa)!
• Can bind variables into global JS space from Java
• Essentially allows us to write prettier code using JS which can
seamlessly interoperate with existing Java codebase
48. Other additions/changes
• new Date-Time API (java.time) – completely immutable
• Default methods added to various interfaces (e.g. in Comparator)
• Streams and lambdas used/added to various places in API
• e.g. concurrency utilities, File I/O as well as Collections
• Unsigned arithmetic support
• Parallel array sorting
• Various other back-end improvements (boring stuff)
49. Java 9 overview
• Modules (“Project Jigsaw”)
• Process API updates
• jshell: command-line REPL for Java
• Stack-walking API – standardized way to get info from stack traces
• HTTP 2.0 client, Money and Currency API, Reactive Streams
• private methods in interfaces?!
• Underscore (“_”) no longer a valid identifier
• Collection factory methods (immutable)
• Native desktop integration (java.awt.desktop)
50. Possible features in future versions of Java
• Data classes
• Many POJOs are just “dumb data holders”
• Scala-style class declarations would be better – no boilerplate
• Enhanced switch
• Test for types instead of lots of instanceof checks
• Get results from switch – case statements could return values
• Switch on data type patterns
• Project Valhalla – value types with no identity
• Don’t pay for what you don’t use – objects have unnecessary overhead
• “Codes like a class, works like an int” – exploit caching/locality
“Plague”
Architects admit they’d have done things differently if they could go back in time
Design heavily influenced by what was going on in 1995
Need to “steal” C++ programmers
Familiar syntax and constructs
Promote OO
Worst HelloWorld ever? (too much ceremony)
Mutability is the default
Checked exceptions
Single inheritance
“Generics” are tacked on
No operator overloading
No value types (like structs)
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html
CompletionStage interface has 38 methods determined in a (3x3x3)+(3x3+1) + 1 “toCompleteableFuture()”
The first aspect (what triggers a stage):
Methods with names starting with “then” are for adding another stage to be triggered when a single stage completes.
Methods with names containing “both” are for adding another stage to be triggered when two previous stages both complete.
Methods with names containing “either” are for adding another stage to be triggered when either one of two previous stages completes.
The second aspect (whether the computation takes an argument and returns a result):
Methods with names containing “apply” take a Function, which takes an argument (the result of the previous stage) and return a result (the argument for the next stage).
Methods with names containing “accept” take a Consumer, which takes an argument but does not return a result.
Methods with names containing “run” take a Runnable, which takes no arguments and does not return a result.
The third aspect (how the execution of the computation is arranged):
Methods with names which do not end in “async” execute the computation using the stage’s default execution facility.
Methods with names that end in “async” execute the computation using the stage’s default asynchronous execution facility.
Methods with names that end in “async” and that also take an Executor argument, execute the computation using the specified Executor.
Lastly, there four handler methods for dealing with exceptions and/or more general “meta-level” aspects of computations
http://www.jesperdj.com/2015/09/26/the-future-is-completable-in-java-8/
https://youtu.be/oGll155-vuQ?t=22m – fancy switch
https://youtu.be/oGll155-vuQ?t=32m22s – Value types
Would be nice to have default parameters instead of having to make factory methods and lots of constructors