Java 8 introduced cool new features such as Lambdas and Streams. We'll take a look at what they are how to use them effectively. We'll also walkthrough an example of a lightweight Java 8 service running in AWS cloud, which can read and index tweets into an ElasticSearch cluster
14. Default Methods
14
public class Dog implements Bark {
// No overridden method
}
public interface Bark {
default String bark() {
return "Woof Woof!";
}
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.bark(); // Woof Woof!
}
26. Lambdas
26
Comparator<Student> c = new Comparator<Student>()
{
public int compare(Student a1, Student a2) {
return a1.getAge().compareTo(a2.getAge());
}
};
27. Lambdas
27
Comparator<Student> c = new Comparator<Student>()
{
public int compare(Student a1, Student a2) {
return a1.getAge().compareTo(a2.getAge());
}
};
Comparator<Student> c =
(Student a1, Student a2) ->
a1.getGrade().compareTo(a2.getGrade());
41. Streams
41
Stream is a sequence of elements from a
source that supports data processing
operations:
Fancy iterators that let you manipulate
collections of data in a declarative,
composable and transparently parallel way
53. CF
53
Future models an asynchronous computation and
provides a reference to its result that will be available
when the computation itself is completed
59. CF
59
Pattern 1: Async Sequencing
thenAccept*
Run a function when complete
thenApply*
Convert the result using a function when complete
60. CF
60
Pattern 2: Async Join
thenAcceptBoth*
Run a function when both futures are done
thenCombine*
Convert the result of 2 futures into a new thing
when both are done
61. CF
61
CF<String> user =
CF.supplyAsync(() -> "John");
CF<String> id =
CF.supplyAsync( () -> "1");
user
.thenCombineAsync(id, (u, i) -> u + i)
.thenAccept(System.out::println);
62. References
ď§ Java 8 in Action: https://www.manning.com/books/java-8-in-action
ď§ @Winterberg: http://winterbe.com/posts/2014/03/16/java-8-tutorial/
ď§ IntelliJ for Java Projects
62
64. Demo
64
Demo 1:
⢠Run code to periodically fetch tweets from Twitter
⢠Store the fetched tweets somewhere
Tech Stack used:
⢠AWS Lambda to create scheduled function
⢠AWS S3 to store fetched tweets
⢠Twitter Streaming Endpoint to fetch tweets
65. Demo
65
Demo 2:
⢠Index the stored tweets into an ElasticSearch cluster
⢠Explore and visualize patterns using Kibana Dashboards
Tech Stack used:
⢠AWS Lambda to create a function reacting to S3 events
⢠AWS ElasticSearch Service
⢠Kibana 4
http://www.oracle.com/us/technologies/java/duke-424174.html
Creator: Joe Palrang
Duke was designed to represent a "software agent" that performed tasks for the user.
Duke was the interactive host that enabled a new type of user interface that went beyond the buttons, mice, and pop-up menus of the desktop computing world
RedMonk Q3 2014 Programming Language Rankings
X axis: Popularity Rank on GitHub (by # of projects)
Y axis: Popularity Rank on StackOverflow (by # of tags)
GitHub and Stack Overflow are used here first because of their size and second because of their public exposure of the data necessary for the analysis
RedMonk Q3 2014 Programming Language Rankings
X axis: Popularity Rank on GitHub (by # of projects)
Y axis: Popularity Rank on StackOverflow (by # of tags)
GitHub and Stack Overflow are used here first because of their size and second because of their public exposure of the data necessary for the analysis
Language construct that groups related methods together into a contract. (HAS-A vs IS-A signaled by an abstract class)
Java 8 API introduces many new methods on existing interfaces, such as the sort method on the List
Java 8 API introduces many new methods on existing interfaces, such as the sort method on the List
How would maintainers of other libraries like Guava feel if they suddenly had to implement sort?
The main users of default methods are library designers.
default methods were introduced to evolve libraries such as the Java API in a compatible way
But even if you are an application developer this is important:
default methods can help structure your programs by providing a flexible mechanism for multiple inheritance of
behavior: a class can inherit default methods from several interface/classes
There are three kinds of compatibility when introducing a change to a Java program: binary, source, and behavioral compatibilities
Adding a method to an interface is binary compatible and behavioral compatible but NOT source compatible
Overlap: They both can contain abstract methods and methods with a body.
Difference 1: A class can extend only from one abstract class, but a class can implement multiple interfaces.
Difference 2: An abstract class can enforce a common state constants, members, method stubs and defined methods, whereas interfaces can only have constants methods stubs AND (now) default methods
Difference 3: Methods and members of an abstract class can be defined with any visibility, whereas all methods of an interface must be defined as public (they are defined public by default).
Meta Difference: Abstract class is for polling together behavior whereas interfaces act like contracts
What if the class implements two interfaces and both those interfaces define a default method with the same signature
1 Classes always win: A method declaration in the class or a superclass takes priority
over any default method declaration.
2 Otherwise, sub-interfaces win: the method with the same signature in the most
specific default-providing interface is selected. (If B extends A , B is more specific
than A ).
3 Finally, if the choice is still ambiguous, the class inheriting from multiple interfaces
has to explicitly select which default method implementation to use by
overriding it and calling the desired method explicitly:
new syntax X.super.m(âŚ)
Where X is the superinterface whose method m you want to call.
1. Behavior parameterization is a software development pattern that lets you handle
frequent requirement changes.
2. It is the ability for a method to take multiple different behaviors as parameters and use them internally to accomplish different behaviors
3. In this sense its similar to Strategy Design pattern
4. Prior to Java 8, it could be encoded using anonymous classes
The Java 8 feature of passing code to methods (and also being able to return it and
incorporate it into data structures) also provides access to a whole range of additional
techniques that are commonly referred to as functional-style programming
Java 8 decided to allow methods to be valuesâto make it easier for
you to program. Moreover, the Java 8 feature of methods as values forms the basis of
various other Java 8 features (such as Stream s)
You could combine the grade and age into one method called 'filter':
But we'll still need a way to differentiate what attrobute to filter on
We could use flags but thats really ugly
What if the requirements mandate filtering based on not just a single attribute but a combination of attributes?
So far we have parameterized the filter method with values such
as a String, an Integer, or a boolean. But in this case what w eneed is a better way to tell your filter method
the selection criteria for students.
Creating a StudentPredicate interface and having multiple predicate objects implement that and then passing that interface as a second arg to filterStudents method can solve this in theory.
Cons: Unnecessary declaration of multiple objects/interfaces.
Anonymous classes could be used but they are bulky and distracting
We could use built-in method:
students.stream().filter(p -> p.age = 20).forEach(p -> System.out.println(p));
1. An interface is still a functional interface if it has many default methods as long as it specifies only one
abstract method.
2. The signature of the abstract method of the functional interface essentially describes
the signature of the lambda expression. We call this abstract method a function descriptor
Note that none of the new functional interfaces allow for a checked exception to be thrown.
You have two options if you need a lambda expression to throw an exception: define
your own functional interface that declares the checked exception, or wrap the lambda
with a try/catch block
Note that none of the new functional interfaces allow for a checked exception to be thrown.
You have two options if you need a lambda expression to throw an exception: define
your own functional interface that declares the checked exception, or wrap the lambda
with a try/catch block
The type of a lambda is deduced from the context in which the lambda is used.
The type expected for the lambda expression inside the context (for example, a method
parameter that itâs passed to or a local variable that itâs assigned to) is called the target
type
https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
Lambdas and Closures:
A closure is an instance of a function that can reference
nonlocal variables of that function with no restrictions
Now Java 8 lambdas and anonymous classes do
something similar to closures: they can be passed as argument to methods and can
access variables outside their scope. But they have a restriction: they canât modify
the content of local variables of a method in which the lambda is defined
Those variables have to be implicitly final. It helps to think that lambdas close over values rather
than variables
Allows us to reference constructors or methods without executing them.
Method references and Lambda are similar in that they both require a target type that consist of a compatible functional interface.
Java 8 enables you to pass references of methods or constructors via the :: keyword
4 types:
Static MR:
Arrays.sort(items, (a, b) -> Util.compareItems(a, b));
Arrays.sort(items, Util::compareItems);
Instance MR:
items.forEach((x) -> System.out.print(x));
items.forEach(System.out::print);
Reference to a method of arbitrary instance
items.forEach((x) -> { x.publish(); });
items.forEach(Item::publish);
Constructor reference
ConstructorReference cref = Item::new;
Item item = cref.constructor();
Streams are an update to the Java API that lets you manipulate collections of data in a
declarative way (you express a query rather than code an ad hoc implementation for
it)
1. Sequence of elementsâLike a collection, a stream provides an interface to a
sequenced set of values of a specific element type. But Collections are about data; streams are about computations.
2. SourceâStreams consume from a data-providing source such as collections,
arrays, or I/O resources.
3. Data processing operationsâStreams support database-like operations and common
operations from functional programming languages to manipulate data,
such as filter, map, reduce, find, match, sort,
Much business logic entails database-like operations such as grouping, filtering, limiting a list of
entities by some constraint (for example, all old students)
We keep reimplementing these operations using iterators.
In contrast most databases let you specify such operations declaratively:
Express what you want done rather than how you want it done!
To gain performance youâd need to process it in parallel and leverage multicore architectures.
But writing parallel code is complicated in comparison to working with iterators.
In addition, itâs no fun to debug.
Declarative: Write code in a declarative way
Composable: Allows chaining to create data processing pipelines
Paralellizable: parallelXXX
In coarsest terms, the difference between collections and streams has to do with when
things are computed:
1. A collection is an in-memory data structure that holds all the values
the data structure currently has
2. A Stream is a conceptually fixed data structure (you canât add or
remove elements from it) whose elements are computed on demand
3. You can see a stream as a set of values spread out in time.
In contrast, a collection is a set of values spread out in space
(here, computer memory), which all exist at a single point in time
Collections are about data; streams are about computations.
Netflix DVD vs Netflix Stream example
Sorted takes a Comparator:
Stream<T> sorted(Comparator<? super T> comparator)
static <T,U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T,? extends U> keyExtractor)
Stream operations have two important characteristics:
1. Pipelining: Many stream operations return a stream themselves, allowing operations
to be chained and form a larger pipeline allowing for laziness and short-circuiting.
2. Internal iteration: stream operations do the iteration behind the scenes for you.
Stream operations that can be connected are called intermediate operations, and operations
that close a stream are called terminal operations
Intermediate: filter, map, limit, skip, distinct, sorted
Terminal: forEach, collect, count, anyMatch, noneMatch, allMatch, findFirst, findAny, reduce/fold
What exactly happens when you call the method parallelStream() ?
How many threads are being used? What are the performance benefits?
Because operations such as filter (or sorted , map , and collect ) are available as
high-level building blocks that donât depend on a specific threading model, their internal
implementation could be single-threaded or potentially maximize your multicore
architecture transparently
forEach: Consumes each element from a stream and applies a lambda to each of
them. The operation returns void.
Count: Returns the number of elements in a stream. The operation returns a long.
Collect: Reduces the stream to create a collection such as a List, a Map, or even
an Integer
<R> R collect(Supplier<R> supplier,
BiConsumer<R,? super T> accumulator,
BiConsumer<R,R> combiner)
Reduce:
T reduce(T identity,
BinaryOperator<T> accumulator)
// Reduce
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
reduce takes two arguments:
â An initial value, here 0.
â A BinaryOperator<T> to combine two elements and produce a new value; here
you use the lambda (a, b) -> a + b.
Thereâs also an overloaded variant of reduce that doesnât take an initial value, but it
returns an Optional object
Optional<Integer> max = numbers.stream().reduce(Integer::max);
You can also get an empty stream using the empty method as follows:
Stream<String> emptyStream = Stream.empty();
Iterate:
Returns an infinite sequential ordered Stream produced by iterative application of a function f to an initial element seed, producing a Stream consisting of seed, f(seed), f(f(seed)), etc.
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
Generate:
Returns an infinite sequential unordered stream where each element is generated by the provided Supplier. This is suitable for generating constant streams, streams of random elements etc
static <T> Stream<T> generate(Supplier<T> s)
https://jaxenter.com/java-performance-tutorial-how-fast-are-the-java-8-streams-118830.html
Concurrency is related to how an application handles multiple tasks it works on.
An application may process one task at at time (sequentially) or work on multiple tasks at the same time (concurrently).
Parallelism on the other hand, is related to how an application handles each individual task. An application may process the task serially from start to end, or split the task up into subtasks which can be completed in parallel.
[1] Concurrency:
Main goal is to perform several loosely related tasks on the same CPUs, keeping the
core as busy as possible to maximize the throughput of your application, what you
really want to achieve is to avoid blocking a thread and wasting its computational
resources while waiting
[2] Paralellism:
Split an operation into multiple suboperations and perform those
suboperations in parallel on different cores, CPUs, or even machines.
Specifically for Multicore processors we can take advantage via: FJ Framework + parallel streams
In recent years, two trends are obliging us to rethink the way we write software.
The first trend is related to the hardware on which we run our applications, and the second
trend concerns how applications are structured and particularly how they interact with each other
Since Java 1.5
Futures still dont allow you to write concise concurrent code.
CompletableFuture does.
For example, itâs difficult to express dependencies between
results of a Future ; declaratively itâs easy to say, âWhen the result of the long computation
is available, please send its result to another long computation, and when thatâs
done, combine its result with the result from another query.â But implementing this
with the operations available in a Future is a different story
Combining two asynchronous computations in oneâboth when they're independent
and when the second depends on the result of the first
â Waiting for the completion of all tasks performed by a set of Future s
â Waiting for the completion of only the quickest task in a set of Future s (possibly
because theyâre trying to calculate the same value in different ways) and retrieving
its result
â Programmatically completing a Future (that is, by manually providing the
result of the asynchronous operation)
â Reacting to a Future completion (that is, being notified when the completion
happens and then having the ability to perform a further action using the result
of the Future , instead of being blocked waiting for its result
The supplyAsync method accepts a Supplier as argument and returns a Completable-
Future that will be asynchronously completed with the value obtained by invoking
that Supplier .
This Supplier will be run by one of the Executor s in the ForkJoin-
Pool , but you can specify a different Executor by passing it as a second arg
Tenants for Reactive Programming
Avoid Blocking Thread (to optimize usage of all available cores)
Avoid changing threads (to optimize our use of cpu caches)
Avoid crippling failure (failure in a subsystem should be encapsulated and handled)
[1] and [2] are competing concerns
Parallel Streams and CompletableFutures both internally use the same common pool that by default has
a fixed number of threads equal to the one returned by:
Runtime.getRuntime().availableProcessors() .
Nevertheless, CompletableFutures have an advantage because, in contrast to whatâs offered by the parallel Streams API , they allow you to
specify a different Executor to submit their tasks to.
This allows you to configure this Executor , and in particular to size its thread pool, in a way that better fits the
requirements of your application.
â If youâre doing computation-heavy operations with no I/O, then the Stream interface
gives the simplest implementation and one likely to be the most efficient
(if all threads are compute-bound, then thereâs no point in having more threads
than processor cores).
â On the other hand, if your parallel units of work involve waiting for I/O (including
network connections), then CompletableFutures give more flexibility and the
ability to match the number of threads to the wait/computer, or W/C, ratio as
discussed previously. Another reason to avoid using parallel streams when I/O
waits are involved in the stream-processing pipeline is that the laziness of
streams can make it harder to reason about when the waits actually happen.
* = additional behavior happens on current thread (blocking)
*Async = additional behavior happens elsewhere
* = additional behavior happens on current thread (blocking)
*Async = additional behavior happens elsewhere