Date post: | 22-Jan-2018 |
Category: |
Software |
Upload: | sina-madani |
View: | 97 times |
Download: | 0 times |
A brief tour of modern Javaby Sina Madani
A brief history…
• February 1997 – Java 1.1AWT, inner classes, JDBC, reflection (introspection only), RMI, JavaBeans…
• December 1998 – Java 2• JIT compiler, Collections framework, JIT compiler, CORBA IDL…
• May 2000 – Java 3• Hotspot, JNDI, JDPA…
• February 2002 – Java 4• assert keyword, NIO, exception chaining, IPv6, prefs, APIs for security,
logging, image…
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
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
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
Diamond problem
interface I1 {
default void foo() {
System.out.println(“I1::foo”);
}
}
interface I2 {
default void foo() {
System.out.println(“I2::foo”);
}
}
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”
• 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();
}
}
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
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
Before Java 8
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.print(currentThread().getName());
}
});
With lambdas
Thread t = new Thread (() -> {
System.out.print(currentThread().getName());
});
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);
});
ThreadFactory before Java 8
ThreadFactory tf = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r);
}
};
ThreadFactory with method reference
ThreadFactory tf = Thread::new;
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
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);
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
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
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
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”]
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;
}
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;
}
Java 8
Set<String> getShortNames(Set<String> names) {
return names.stream()
.filter(name -> name.length() < 5)
.map(String::toUpperCase)
.collect(Collectors.toSet());
}
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”
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();
}
Declarative style (Java 8)
String getShortNames(Set<String> names) {
return names.stream()
.filter(name -> name.length() < 5)
.map(String::toUpperCase)
.collect(Collectors.joining(“, ”));
}
Extension 2
• The set of names has > 1 million elements
• We need to process them faster
• Create a multi-threaded version to speed things up
Java 1
• “I‘m handing in my resignation.”
Java 5
• “Sorry boss, I’m not feeling well today.”
Java 8
Set<String> getShortNames(Set<String> names) {
return names.parallelStream()
.filter(name -> name.length() < 5)
.map(String::toUpperCase)
.collect(Collectors.toSet());
}
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
Imperative style
double result = 0d;
for (number : numbers) {
if (number > k && number % 2 == 0) {
result = Math.sqrt(number);
break;
}
}
return result;
Stream (functional style)
return numbers.stream()
.filter(number -> number > k)
.filter(number -> number % 2 == 0)
.map(Math::sqrt)
.findFirst();
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…”
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.
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
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
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.
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
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!
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;
};
}
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
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
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)
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);
}
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
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)
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)
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
Questions?
• Thank you for listening!