View
2.811
Download
1
Category
Preview:
Citation preview
@JosePaumard
RxJavaJ8 Stream
Comparison:
patterns
performances
Why should we beinterested in the
Streams API?
@JosePaumard#Devoxx #J8Stream
It’s all about data processing
Up to 2014: only one tool, the Collection Framework
Also 3rd party API: Common Collections, …
@JosePaumard#Devoxx #J8Stream
It’s all about data processing
Up to 2014: only one tool, the Collection Framework
Also 3rd party API: Common Collections, …
From 2014: so many new APIs
@JosePaumard#Devoxx #J8Stream
It’s all about data processing
Why so?
Because data processing is more and more important
@JosePaumard#Devoxx #J8Stream
It’s all about data processing
Why so?
Because data processing is more and more important
and more and more complex!
@JosePaumard#Devoxx #J8Stream
It’s all about data processing
Why so?
Because data processing is more and more important
and more and more complex!
bigger and bigger amount of data to process
@JosePaumard#Devoxx #J8Stream
It’s all about data processing
Why so?
Because data processing is more and more important
and more and more complex!
bigger and bigger amount of data to process
controlled response time
@JosePaumard#Devoxx #J8Stream
It’s all about data processing
Why so?
Because data processing is more and more important
and more and more complex!
bigger and bigger amount of data to process
controlled response time
complex algorithms
@JosePaumard#Devoxx #J8Stream
It’s all about data processing
So data processing needs high level primitives
@JosePaumard#Devoxx #J8Stream
It’s all about data processing
So data processing needs high level primitives
Able to access data wherever it is
@JosePaumard#Devoxx #J8Stream
It’s all about data processing
So data processing needs high level primitives
Able to access data wherever it is
That provides map / filter / reduce functions
@JosePaumard#Devoxx #J8Stream
It’s all about data processing
So data processing needs high level primitives
Able to access data wherever it is
That provides map / filter / reduce functions
And efficient implementations of those!
@JosePaumard#Devoxx #J8Stream
It’s all about data processing
So data processing needs high level primitives
Able to access data wherever it is
That provides map / filter / reduce functions
And efficient implementations of those!
Most probably implemented in parallel
@JosePaumard#Devoxx #J8Stream
Agenda
1) To present two API: the Java 8 Stream API and RxJava
• Fundamentals
• Implemented functionalities
• Patterns!
@JosePaumard#Devoxx #J8Stream
Agenda
1) To present two API: the Java 8 Stream API and RxJava
• Fundamentals
• Implemented functionalities
• Patterns!
2) And to compare those APIs
• From the developer point of view
• Performances!
@JosePaumard
@JosePaumard
@JosePaumard#Devoxx #J8Stream
Questions?#J8Stream
Java 8 Stream API
@JosePaumard#Devoxx #J8Stream
API Stream
What is a Java 8 Stream?
An object that connects to a source
@JosePaumard#Devoxx #J8Stream
API Stream
What is a Java 8 Stream?
An object that connects to a source
That does not hold any data
@JosePaumard#Devoxx #J8Stream
API Stream
What is a Java 8 Stream?
An object that connects to a source
That does not hold any data
That implements the map / filter / reduce pattern
@JosePaumard#Devoxx #J8Stream
API Stream
What is a Java 8 Stream?
An object that connects to a source
That does not hold any data
That implements the map / filter / reduce pattern
A new concept in JDK 8
@JosePaumard#Devoxx #J8Stream
Definition of a Stream
Two things about streams:
1) A Stream does not hold any data
2) A Stream does not modify the data it gets from the source
@JosePaumard#Devoxx #J8Stream
API Stream
Example
List<String> list = Arrays.asList("one", "two", "three") ;
list.stream() .map(s -> s.toUpperCase()).max(Comparator.comparing(s -> s.length())).ifPresent(s -> System.out.println(s)) ;
@JosePaumard#Devoxx #J8Stream
API Stream
Example
List<String> list = Arrays.asList("one", "two", "three") ;
list.stream() // creation of a new Stream object.map(s -> s.toUpperCase()).max(Comparator.comparing(s -> s.length())).ifPresent(s -> System.out.println(s)) ;
@JosePaumard#Devoxx #J8Stream
API Stream
Example
List<String> list = Arrays.asList("one", "two", "three") ;
list.stream().map(s -> s.toUpperCase()) // to upper case.filter(s -> s.length() < 20).max(Comparator.comparing(s -> s.length())).ifPresent(s -> System.out.println(s)) ;
@JosePaumard#Devoxx #J8Stream
API Stream
Example
List<String> list = Arrays.asList("one", "two", "three") ;
list.stream().map(s -> s.toUpperCase()).filter(s -> s.length() < 20) // remove strings longer than 20.max(Comparator.comparing(s -> s.length())).ifPresent(s -> System.out.println(s)) ;
@JosePaumard#Devoxx #J8Stream
API Stream
Example
List<String> list = Arrays.asList("one", "two", "three") ;
list.stream().map(s -> s.toUpperCase()).filter(s -> s.length() < 20).max(Comparator.comparing(s -> s.length())) // take the longest s.ifPresent(s -> System.out.println(s)) ;
@JosePaumard#Devoxx #J8Stream
API Stream
Example
List<String> list = Arrays.asList("one", "two", "three") ;
list.stream().map(s -> s.toUpperCase()).filter(s -> s.length() < 20).max(Comparator.comparing(s -> s.length())).ifPresent(s -> System.out.println(s)) ; // and print the result
@JosePaumard#Devoxx #J8Stream
API Stream
Example
List<String> list = Arrays.asList("one", "two", "three") ;
list.stream().map(String::toUpperCase).filter(s -> s.length() < 20).max(Comparator.comparing(String::length)).ifPresent(System.out::println) ; // and print the result
@JosePaumard#Devoxx #J8Stream
Collectors
We can use collectors
List<Person> list = ... ;
list.stream().filter(person -> person.getAge() > 30).collect(Collectors.groupingBy(
Person::getAge, // key extractorCollectors.counting() // downstream collector
)) ;
@JosePaumard#Devoxx #J8Stream
Collectors
We can use collectors
List<Person> list = ... ;
Map<list.stream()
.filter(person -> person.getAge() > 30)
.collect(Collectors.groupingBy(
Person::getAge, // key extractorCollectors.counting() // downstream collector
)) ;
@JosePaumard#Devoxx #J8Stream
Collectors
We can use collectors
List<Person> list = ... ;
Map<Integer, list.stream()
.filter(person -> person.getAge() > 30)
.collect(Collectors.groupingBy(
Person::getAge, // key extractorCollectors.counting() // downstream collector
)) ;
@JosePaumard#Devoxx #J8Stream
Collectors
We can use collectors
List<Person> list = ... ;
Map<Integer, Long> map = list.stream()
.filter(person -> person.getAge() > 30)
.collect(Collectors.groupingBy(
Person::getAge, // key extractorCollectors.counting() // downstream collector
)) ;
@JosePaumard#Devoxx #J8Stream
Collectors
And we can go parallel!
List<Person> list = ... ;
Map<Integer, Long> map = list.stream().parallel()
.filter(person -> person.getAge() > 30)
.collect(Collectors.groupingBy(
Person::getAge, // key extractorCollectors.counting() // downstream collector
)) ;
@JosePaumard#Devoxx #J8Stream
Sources of a Stream
Many ways of connecting a Stream to a source of data
@JosePaumard#Devoxx #J8Stream
Sources of a Stream
First patterns to create a Stream
List<Person> people = Arrays.asList(p1, p2, p3);
@JosePaumard#Devoxx #J8Stream
Sources of a Stream
First patterns to create a Stream
List<Person> people = Arrays.asList(p1, p2, p3);
Stream<Person> stream = people.stream();
@JosePaumard#Devoxx #J8Stream
Sources of a Stream
First patterns to create a Stream
List<Person> people = Arrays.asList(p1, p2, p3);
Stream<Person> stream = people.stream();
Stream<Person> stream = Stream.of(p1, p2, p3);
@JosePaumard#Devoxx #J8Stream
More patterns
Stream on String
String crazy = "supercalifragilisticexpialidocious";
IntStream letters = crazy.chars();
long count = letters.distinct().count();
@JosePaumard#Devoxx #J8Stream
More patterns
Stream on String
String crazy = "supercalifragilisticexpialidocious";
IntStream letters = crazy.chars();
long count = letters.distinct().count();
> count = 15
@JosePaumard#Devoxx #J8Stream
More patterns
Stream on String
String crazy = "supercalifragilisticexpialidocious";
IntStream letters = crazy.chars();
letters.boxed().collect(Collectors.groupingBy(
Function.identity(),Collectors.counting()
));
@JosePaumard#Devoxx #J8Stream
More patterns
Stream on String
String crazy = "supercalifragilisticexpialidocious";
IntStream letters = crazy.chars();
Map<letters.boxed().collect(
Collectors.groupingBy(Function.identity(),Collectors.counting()
));
@JosePaumard#Devoxx #J8Stream
More patterns
Stream on String
String crazy = "supercalifragilisticexpialidocious";
IntStream letters = crazy.chars();
Map<Integer, letters.boxed().collect(
Collectors.groupingBy(Function.identity(),Collectors.counting()
));
@JosePaumard#Devoxx #J8Stream
More patterns
Stream on String
String crazy = "supercalifragilisticexpialidocious";
IntStream letters = crazy.chars();
Map<Integer, Long> map = letters.boxed().collect(
Collectors.groupingBy(Function.identity(),Collectors.counting()
));
@JosePaumard#Devoxx #J8Stream
More patterns
Stream on String
String crazy = "supercalifragilisticexpialidocious";
IntStream letters = crazy.chars();
Map<Integer, Long> map = letters.boxed().collect(
Collectors.groupingBy(Function.identity(),Collectors.counting()
));
a -> 3c -> 3d -> 1e -> 2f -> 1g -> 1i -> 7l -> 3o -> 2p -> 2r -> 2s -> 3t -> 1u -> 2x -> 1
@JosePaumard#Devoxx #J8Stream
More patterns
Stream built on the lines of a file
String book = "alice-in-wonderland.txt";
Stream<String> lines = Files.lines(Paths.get(book)); // autocloseable
@JosePaumard#Devoxx #J8Stream
More patterns
Stream built on the lines of a file
Stream built the words of a String
String book = "alice-in-wonderland.txt";
Stream<String> lines = Files.lines(Paths.get(book)); // autocloseable
String line = "Alice was beginning to get very tired of";
Stream<String> words = Pattern.compile(" ").splitAsStream(line);
@JosePaumard#Devoxx #J8Stream
Flatmap
How to mix both to get all the words of a book?
Memory-efficient implementations built on lazy reading of the
source of data
Function<String, Stream<String>> splitToWords = line -> Pattern.compile(" ").splitAsStream(line);
Stream<String> lines = Files.lines(path);
Stream<String> words = lines.flatMap(splitToWords);
@JosePaumard#Devoxx #J8Stream
Flatmap
Analyzing the result
Stream<String> words = lines.flatMap(splitToWords);
long count = words.filter(word -> word.length() > 2)
.map(String::toLowerCase)
.distinct()
.count();
@JosePaumard#Devoxx #J8Stream
Flatmap
Another analysis of the result
Stream<String> words = lines.flatMap(splitToWords);
Map<Integer, Long> map = words.filter(word -> word.length() > 2)
.map(String::toLowerCase)// .distinct().collect(
Collectors.groupingBy(String::length,Collectors.counting()
));
@JosePaumard#Devoxx #J8Stream
Extracting the max from a Map
Yet another analysis of the result
map.entrySet().stream().sorted(
Entry.<Integer, Long>comparingByValue().reversed()).limit(3).forEach(System.out::println);
@JosePaumard#Devoxx #J8Stream
Connecting a Stream on a source
Connecting a Stream on a non-standard source is possible
@JosePaumard#Devoxx #J8Stream
Connecting a Stream on a source
Connecting a Stream on a non-standard source is possible
A Stream is built on 2 things:
- A Spliterator (comes from split and iterator)
- A ReferencePipeline (the implementation)
@JosePaumard#Devoxx #J8Stream
Connecting a Stream on a source
The Spliterator is meant to be overriden
public interface Spliterator<T> {
boolean tryAdvance(Consumer<? super T> action) ;
Spliterator<T> trySplit() ;
long estimateSize();
int characteristics();}
@JosePaumard#Devoxx #J8Stream
Connecting a Stream on a source
The Spliterator is meant to be overriden
public interface Spliterator<T> {
boolean tryAdvance(Consumer<? super T> action) ;
Spliterator<T> trySplit(); // not needed for non-parallel operations
long estimateSize(); // can return 0
int characteristics(); // returns a constant}
@JosePaumard#Devoxx #J8Stream
Examples of custom Spliterators
Suppose we have a Stream:
[1, 2, 3, 4, 5, …]
We want to regroup the elements:
[[1, 2, 3], [4, 5, 6], [7, 8, 9], …]
Let us build a GroupingSpliterator to do that
@JosePaumard#Devoxx #J8Stream
GroupingSpliterator
[1, 2, 3, 4, 5, …] -> [[1, 2, 3], [4, 5, 6], …]
public class GroupingSpliterator<E> implements Spliterator<Stream<E>> {
private final long grouping ;private final Spliterator<E> spliterator ;
// implementation}
@JosePaumard#Devoxx #J8Stream
GroupingSpliterator
[1, 2, 3, 4, 5, …] -> [[1, 2, 3], [4, 5, 6], …]
public class GroupingSpliterator<E> implements Spliterator<Stream<E>> {
private final long grouping ;private final Spliterator<E> spliterator ;
// implementation}
GroupingSpliterator<Integer> gs = new GroupingSpliterator(spliterator, grouping);
@JosePaumard#Devoxx #J8Stream
GroupingSpliterator
Implementing estimateSize() and characteristics()
public long estimateSize() {return spliterator.estimateSize() / grouping ;
}
public int characteristics() {
return this.spliterator.characteristics();}
@JosePaumard#Devoxx #J8Stream
GroupingSpliterator
Implementing trySplit()
public Spliterator<Stream<E>> trySplit() {
Spliterator<E> spliterator = this.spliterator.trySplit() ;return new GroupingSpliterator<E>(spliterator, grouping) ;
}
@JosePaumard#Devoxx #J8Stream
GroupingSpliterator
Implementing tryAdvance()
public boolean tryAdvance(Consumer<? super Stream<E>> action) {
// should call action.accept() with the next element of the Stream
// and return true if more elements are to be consumed
return true ; // false when we are done}
@JosePaumard#Devoxx #J8Stream
GroupingSpliterator
The structure of the resulting Stream is the following:
[[1, 2, 3], [4, 5, 6], [7, 8, 9], …]
Each element is a Stream built on the elements of the
underlying Stream
@JosePaumard#Devoxx #J8Stream
GroupingSpliterator
Build a Stream element by element is done with a
Stream.Builder
Stream.Builder<E> builder = Stream.builder() ;
for (int i = 0 ; i < grouping ; i++) {spliterator.tryAdvance(element -> builder.add(element);
}
Stream<E> subStream = subBuilder.build(); // [1, 2, 3]
@JosePaumard#Devoxx #J8Stream
GroupingSpliterator
Are we done with the underlying Stream?
Stream.Builder<E> builder = Stream.builder() ;
for (int i = 0 ; i < grouping ; i++) {spliterator.tryAdvance(
element -> builder.add(element));
}
Stream<E> subStream = subBuilder.build(); // [1, 2, 3]
@JosePaumard#Devoxx #J8Stream
GroupingSpliterator
Are we done with the underlying Stream?
Stream.Builder<E> builder = Stream.builder() ;
for (int i = 0 ; i < grouping ; i++) {spliterator.tryAdvance( // when this call returns false
element -> builder.add(element));
}
Stream<E> subStream = subBuilder.build(); // [1, 2, 3]
@JosePaumard#Devoxx #J8Stream
GroupingSpliterator
Are we done with the underlying Stream?
Stream.Builder<E> builder = Stream.builder() ;boolean finished = false;for (int i = 0 ; i < grouping ; i++) {
if (spliterator.tryAdvance(element -> builder.add(element)))finished = true;
}
Stream<E> subStream = subBuilder.build(); // [1, 2, 3]
@JosePaumard#Devoxx #J8Stream
GroupingSpliterator
Are we done with the underlying Stream?
public boolean tryAdvance(Consumer<? super Stream<E>> action) {Stream.Builder<E> builder = Stream.builder() ;boolean finished = false;for (int i = 0 ; i < grouping ; i++) {
if (spliterator.tryAdvance(element -> builder.add(element)))finished = true;
}
Stream<E> subStream = subBuilder.build(); // [1, 2, 3]action.accept(subStream) ;return !finished ;
}
@JosePaumard#Devoxx #J8Stream
RollingSpliterator
What about building a Stream like this one:
[[1, 2, 3], [2, 3, 4], [3, 4, 5], …]
This time we need a ring buffer
@JosePaumard#Devoxx #J8Stream
RollingSpliterator
The tryAdvance() call on the underlying Stream becomes this:
bufferWriteIndex is an AtomicLong
private boolean advanceSpliterator() {return spliterator.tryAdvance(
element -> { buffer[bufferWriteIndex.get() % buffer.length] = element ; bufferWriteIndex.incrementAndGet() ;
});}
@JosePaumard#Devoxx #J8Stream
RollingSpliterator
Building the element streams from the ring buffer:
private Stream<E> buildSubstream() {
Stream.Builder<E> subBuilder = Stream.builder() ;for (int i = 0 ; i < grouping ; i++) {
subBuilder.add((E)buffer[(i + bufferReadIndex.get()) % buffer.length]
) ;}bufferReadIndex.incrementAndGet() ;Stream<E> subStream = subBuilder.build() ;return subStream ;
}
@JosePaumard#Devoxx #J8Stream
RollingSpliterator
Putting things together to build the tryAdvance() call
@JosePaumard#Devoxx #J8Stream
public boolean tryAdvance(Consumer<? super Stream<E>> action) {boolean finished = false ;
if (bufferWriteIndex.get() == bufferReadIndex.get()) {for (int i = 0 ; i < grouping ; i++) {
if (!advanceSpliterator()) {finished = true ;
}}
}if (!advanceSpliterator()) {
finished = true ;}
Stream<E> subStream = buildSubstream() ;action.accept(subStream) ;return !finished ;
}
@JosePaumard#Devoxx #J8Stream
Spliterator on Spliterator
We saw how to build a Stream on another Stream by
rearranging its elements
What about building a Stream by merging the elements of
other Streams?
@JosePaumard#Devoxx #J8Stream
Spliterator on Spliterators
Let us take two Streams:
[1, 2, 3, …]
[a, b, c, …]
And build a ZippingSpliterator:
[F[1, a], F[2, b], F[3, c], …]
@JosePaumard#Devoxx #J8Stream
Spliterator on Spliterators
What about
estimateSize()
trySplit()
characteristics()?
They are the same as the underlying streams
@JosePaumard#Devoxx #J8Stream
Spliterator on Spliterators
We then need to implement tryAdvance()
Where transform is a BiFunction
public boolean tryAdvance(Consumer<? super R> action) {return spliterator1.tryAdvance(
e1 -> {spliterator2.tryAdvance(e2 -> {
action.accept(tranform.apply(e1, e2)) ;}) ;
}) ;}
@JosePaumard#Devoxx #J8Stream
ZippingSpliterator
What about creating a Builder for this Spliterator?
ZippingSpliterator.Builder<String, String, String> builder = new ZippingSpliterator.Builder();
ZippingSpliterator<String,String,String> zippingSpliterator = builder.with(spliterator1).and(spliterator2).mergedBy((e1, e2) -> e1 + " - " + e2).build();
@JosePaumard#Devoxx #J8Stream
ZippingSpliterator
The complete pattern:
Stream<String> stream1 = Stream.of("one", "two", "three");Stream<Integer> stream2 = Stream.of(1, 2, 3);
Spliterator<String> spliterator1 = stream1.spliterator();Spliterator<Integer> spliterator2 = stream2.spliterator();
Stream<String> zipped = StreamSupport.stream(zippingSpliterator, false);
@JosePaumard#Devoxx #J8Stream
What did we do with Streams so far?
We took one stream and built a stream by regrouping its
elements in some ways
@JosePaumard#Devoxx #J8Stream
What did we do with Streams so far?
We took one stream and built a stream by regrouping its
elements in some ways
We took to streams and we merged them, element by element,
using a bifunction
@JosePaumard#Devoxx #J8Stream
What did we do with Streams so far?
We took one stream and built a stream by regrouping its
elements in some ways
We took to streams and we merged them, element by element,
using a bifunction
What about taking one element at a time, from different
streams?
@JosePaumard#Devoxx #J8Stream
The WeavingSpliterator
We have N streams, and we want to build a stream that takes:
- its 1st element from the 1st stream
- its 2nd element from the 2nd stream, etc…
@JosePaumard#Devoxx #J8Stream
The WeavingSpliterator
The estimateSize():
public long estimateSize() {int size = 0 ;for (Spliterator<E> spliterator : this.spliterators) {
size += spliterator.estimateSize() ;}return size ;
}
@JosePaumard#Devoxx #J8Stream
The WeavingSpliterator
The tryAdvance():
private AtomicInteger whichOne = new AtomicInteger();
public boolean tryAdvance(Consumer<? super E> action) {
return spliterators[whichOne.getAndIncrement() % spliterators.length]
.tryAdvance(action);}
@JosePaumard#Devoxx #J8Stream
One last thing about Spliterators
The characteristics() method is in fact quite important
What does « characteristics » mean for a Spliterator?
@JosePaumard#Devoxx #J8Stream
The Spliterator
It is in fact a special word: characteristics
public interface Spliterator<T> {
public static final int ORDERED = 0x00000010;public static final int DISTINCT = 0x00000001;public static final int SORTED = 0x00000004;public static final int SIZED = 0x00000040;public static final int NONNULL = 0x00000100;public static final int IMMUTABLE = 0x00000400;public static final int CONCURRENT = 0x00001000;public static final int SUBSIZED = 0x00004000;
}
@JosePaumard#Devoxx #J8Stream
The Spliterator
The Spliterator holds a special word: characteristics
// ArrayListSpliteratorpublic int characteristics() {
return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
}
// HashMap.KeySpliteratorpublic int characteristics() {
return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) |Spliterator.DISTINCT;
}
@JosePaumard#Devoxx #J8Stream
The Spliterator
The Spliterator holds a special word: characteristics
This word is used for optimization
people.stream().sorted() // quicksort?.collect(Collectors.toList());
@JosePaumard#Devoxx #J8Stream
The Spliterator
The Spliterator holds a special word: characteristics
This word is used for optimization
people.stream().sorted() // quicksort? It depends on SORTED == 0.collect(Collectors.toList());
@JosePaumard#Devoxx #J8Stream
The Spliterator
The Spliterator holds a special word: characteristics
This word is used for optimization
SortedSet<Person> people = ...;
people.stream().sorted() // SORTED == 1, no quicksort.collect(Collectors.toList());
@JosePaumard#Devoxx #J8Stream
The Spliterator
The Spliterator holds a special word: characteristics
This word is used for optimization
ArrayList<Person> people = ...;
people.stream().sorted() // SORTED == 0, quicksort.collect(Collectors.toList());
@JosePaumard#Devoxx #J8Stream
The characteristics can change
Each Stream object in a pipeline has its own characteristics
@JosePaumard#Devoxx #J8Stream
The characteristics can change
Each Stream object in a pipeline has its own characteristics
Method Set to 0 Set to 1
filter() SIZED -
map() DISTINCT, SORTED -
flatMap() DISTINCT, SORTED, SIZED -
sorted() - SORTED, ORDERED
distinct() - DISTINCT
limit() SIZED -
peek() - -
unordered() ORDERED -
@JosePaumard#Devoxx #J8Stream
The characteristics can change
Each Stream object in a pipeline has its own characteristics
Method Set to 0 Set to 1
filter() SIZED -
map() DISTINCT, SORTED -
flatMap() DISTINCT, SORTED, SIZED -
sorted() - SORTED, ORDERED
distinct() - DISTINCT
limit() SIZED -
peek() - -
unordered() ORDERED -
@JosePaumard#Devoxx #J8Stream
The characteristics can change
Each Stream object in a pipeline has its own characteristics
Method Set to 0 Set to 1
filter() SIZED -
map() DISTINCT, SORTED -
flatMap() DISTINCT, SORTED, SIZED -
sorted() - SORTED, ORDERED
distinct() - DISTINCT
limit() SIZED -
peek() - -
unordered() ORDERED -
@JosePaumard#Devoxx #J8Stream
Back to the Spliterator itself
What did we do with the spliterator, and with the Stream built
on it?
@JosePaumard#Devoxx #J8Stream
What did we do with Streams so far?
We took one stream and built a stream by regrouping its
elements in some ways
We took to streams and we merged them, element by element,
using a bifunction
We took a collection of streams and built a stream by taking
elements from them, in a given order
@JosePaumard#Devoxx #J8Stream
Wrap-up for the Stream API
The Java 8 Stream API is not just about implementing map /
filter / reduce or building hashmaps
We can use it to manipulate data in advanced ways:
- by deciding on what source we want to connect
- by deciding how we can consume the data from that source
Reactive StreamsRxJava
@JosePaumard#Devoxx #J8Stream
RxJava
Open source API, available on Github
Developed by Netflix
RxJava is the Java version of ReactiveX
.NET
Python, Kotlin, JavaScript, Scala, Ruby, Groovy, Rust
Android
https://github.com/ReactiveX/RxJava
@JosePaumard#Devoxx #J8Stream
RxJava
RxJava is not an alternative implementation of Java 8
Streams, nor the Collection framework
It is an implementation of the Reactor pattern
@JosePaumard#Devoxx #J8Stream
RxJava
The central class is the Observable class
@JosePaumard#Devoxx #J8Stream
RxJava
The central class is the Observable class
It’s big: ~10k lines of code
@JosePaumard#Devoxx #J8Stream
RxJava
The central class is the Observable class
It’s big: ~10k lines of code
It’s complex: ~100 static methods, ~150 non-static methods
@JosePaumard#Devoxx #J8Stream
RxJava
The central class is the Observable class
It’s big: ~10k lines of code
It’s complex: ~100 static methods, ~150 non-static methods
It’s complex: not only because there is a lot of things in it, but
also because the concepts are complex
@JosePaumard#Devoxx #J8Stream
RxJava
Interface Observer
used to « observe » an observable
@JosePaumard#Devoxx #J8Stream
RxJava
Interface Observer
used to « observe » an observable
Interface Subscription
used to model the link between an observer and an
observable
@JosePaumard#Devoxx #J8Stream
Interface Observer
A simple interface
public interface Observer<T> {
public void onNext(T t);
public void onCompleted();
public void onError(Throwable e);}
@JosePaumard#Devoxx #J8Stream
How to subscribe
Subscribing to an observable
Observable<T> observable = ... ;
Subscription subscription = observable.subscribe(observer) ;
@JosePaumard#Devoxx #J8Stream
How to subscribe
Subscribing to an observable
Observable<T> observable = ... ;
Subscription subscription = observable.subscribe(observer) ;
public interface Subscription {
public void unsubscribe();
public void isUnsubscribe();}
@JosePaumard#Devoxx #J8Stream
How to subscribe
We can also just declare a callback
Observable<T> observable = ... ;
Observable<T> next = observable.doOnNext(System.out::println) ;
Observable<T> error = observable.doOnError(System.out::println) ;
Observable<T> each = observable.doOnEach(System.out::println) ;
@JosePaumard#Devoxx #J8Stream
RxJava – agenda
Patterns to create Observables
How to merge observables together
Hot observables / backpressure
@JosePaumard#Devoxx #J8Stream
How to createan Observable
@JosePaumard#Devoxx #J8Stream
The usual ways
Observable from collections and arrays
Observable<String> obs1 = Observable.just("one", "two", "three") ;
List<String> strings = Arrays.asList("one", "two", "three") ;Observable<String> obs2 = Observable.from(strings) ;
@JosePaumard#Devoxx #J8Stream
The usual ways
Observable useful for tests
never(): never emits anything
Observable<String> empty = Observable.empty() ;Observable<String> never = Observable.never() ;Observable<String> error = Observable.<String>error(exception) ;
@JosePaumard#Devoxx #J8Stream
The usual ways
Series and time series
Observable<Long> longs = Observable.range(1L, 100L) ;
// intervalObservable<Long> timeSerie1 =
Observable.interval(1L, TimeUnit.MILLISECONDS) ; // serie of longs
// initial delay, then intervalObservable<Long> timeSerie2 =
Observable.timer(10L, 1L, TimeUnit.MILLISECONDS) ; // one 0
@JosePaumard#Devoxx #J8Stream
The usual ways
The using() method
public final static <T, Resource> Observable<T> using(final Func0<Resource> resourceFactory, // producerfinal Func1<Resource, Observable<T>> observableFactory, // functionfinal Action1<? super Resource> disposeAction // consumer
) { }
@JosePaumard#Devoxx #J8Stream
How using() works
The using() method
1) Creates a resource that will provide the elements
2) Uses this resource to create the elements
3) Applies the 3rd argument to the resource
This 3rd element is in fact there to close the resource
@JosePaumard#Devoxx #J8Stream
How using() works
Suppose we want to create an observable on the lines of a text
file
1) We need a BufferedReader
2) The we need to wrap the lines one by one in Observables
3) Then we need to close the BufferedReader
@JosePaumard#Devoxx #J8Stream
How using() works
Let us do that:
Func0<BufferedReader> resourceFactory = () -> new BufferedReader(new FileReader(new File(fileName)));
Action1<BufferedReader> disposeAction = br -> br.close();
@JosePaumard#Devoxx #J8Stream
How using() works
The problem is that… we need to handle exceptions
Func0<BufferedReader> resourceFactory = () -> {
try {return new BufferedReader(
new FileReader(new File(fileName)));} catch (Exception e) {
e.printStackTrace();} return null;
};
@JosePaumard#Devoxx #J8Stream
How using() works
How can we create one Observable by file line?
@JosePaumard#Devoxx #J8Stream
How using() works
How can we create one Observable by file line?
We could read the file line by line, put the lines in a Collection,
then build an Observable on that collection
@JosePaumard#Devoxx #J8Stream
How using() works
How can we create one Observable by file line?
We could read the file line by line, put the lines in a Collection,
then build an Observable on that collection
@JosePaumard#Devoxx #J8Stream
How using() works
How can we create one Observable by file line?
We could read the file line by line, put the lines in a Iterator,
then build an Observable on that iterator
@JosePaumard#Devoxx #J8Stream
How using() works
How can we create one Observable by file line?
We could read the file line by line, put the lines in a Iterator,
then build an Observable on that iterator
Creating an Observable from an Iterator:
Observable.from(() -> iterator);
@JosePaumard#Devoxx #J8Stream
Func1<BufferedReader, String> observableFactory = bufferedReader -> {Iterator<String> readerIterator = new Iterator<String>() {
private String currentLine = null;private boolean finished;
{currentLine = bufferedReader.readLine();finished = currentLine == null;
}
public boolean hasNext() {return !finished;
}
public String next() {String line = currentLine; currentLine = bufferedReader.readLine();finished = currentLine == null;return line;
}};
return Observable.from(() -> readerIterator);}
@JosePaumard#Devoxx #J8Stream
How using() works
The using() method
Made to build an Observable on a resource that needs to be
« closed » at the end of the generation of the stream
@JosePaumard#Devoxx #J8Stream
How using() works
The using() method
Made to build an Observable on a resource that needs to be
« closed » at the end of the generation of the stream
It is not built on the auto closeable mechanism because
RxJava aims to be Java 6 compatible
@JosePaumard#Devoxx #J8Stream
Schedulers
@JosePaumard#Devoxx #J8Stream
RxJava & executors
Some of those methods take a further argument
This Observable is executed in this scheduler
Scheduler is an interface (in fact an abstract class, to define
« default methods » in Java 7)
Schedulers should be used to create schedulers
Observable<Long> longs = Observable.range(0L, 100L, scheduler) ;
@JosePaumard#Devoxx #J8Stream
RxJava & executors
It allows for the execution of Observable in specialized pools of
Threads
Some Observables are executed in special schedulers (cf
Javadoc)
@JosePaumard#Devoxx #J8Stream
Schedulers
Factory Schedulers
public final class Schedulers {
public static Scheduler immediate() {...} // immediate
public static Scheduler newThread() {...} // new thread
public static Scheduler trampoline() {...} // queued in the current// thread
}
@JosePaumard#Devoxx #J8Stream
Schedulers
Factory Schedulers
public final class Schedulers {
public static Scheduler computation() {...} // computation ES
public static Scheduler io() {...} // IO growing ES
public static Scheduler test() {...}
public static Scheduler from(Executor executor) {...}}
@JosePaumard#Devoxx #J8Stream
Schedulers
Schedulers can be seen as Executors
Some of them are specialized (IO, Computation)
Observers are called in the thread that runs the Observable
@JosePaumard#Devoxx #J8Stream
Schedulers
The Schedulers.from(executor) is useful to call observers in
certain threads
For instance:
Scheduler swingScheduler = Schedulers.from(
SwingUtilities::invokeLater);
@JosePaumard#Devoxx #J8Stream
A 1st example
A simple example
Observable<Integer> range1To100 = Observable.range(1L, 100L) ;range1To100.subscribe(System.out::println) ;
@JosePaumard#Devoxx #J8Stream
A 1st example
A simple example
Observable<Integer> range1To100 = Observable.range(1L, 100L) ;range1To100.subscribe(System.out::println) ;
> 1 2 3 4 ... 100
@JosePaumard#Devoxx #J8Stream
A 2nd example
A not so simple example
Observable<Integer> timer = Observable.timer(1, TimeUnit.SECONDS) ;timer.subscribe(System.out::println) ;
@JosePaumard#Devoxx #J8Stream
A 2nd example
A not so simple example
Nothing is printed
Observable<Integer> timer = Observable.timer(1, TimeUnit.SECONDS) ;timer.subscribe(System.out::println) ;
>
@JosePaumard#Devoxx #J8Stream
A 2nd example
Let us modify this code
Observable<Integer> timer = Observable.timer(1, TimeUnit.SECONDS) ;timer.subscribe(() -> {
System.out.println(Thread.currentThread().getName() + " " + Thread.currentThread().isDaemon()) ;
}) ;Thread.sleep(2) ;
@JosePaumard#Devoxx #J8Stream
A 2nd example
Let us modify this code
Observable<Integer> timer = Observable.timer(1, TimeUnit.SECONDS) ;timer.subscribe(() -> {
System.out.println(Thread.currentThread().getName() + " " + Thread.currentThread().isDaemon()) ;
}) ;Thread.sleep(2) ;
> RxComputationThreadPool-1 - true
@JosePaumard#Devoxx #J8Stream
About the 1st & 2nd examples
The first example ran in the main thread
@JosePaumard#Devoxx #J8Stream
About the 1st & 2nd examples
The first example ran in the main thread
The second example ran in a daemon thread, that does not
prevent the JVM from exiting. Nothing was printed out,
because it did not have the time to be executed.
@JosePaumard#Devoxx #J8Stream
About the 1st & 2nd examples
The first example ran in the main thread
The second example ran in a daemon thread, that does not
prevent the JVM from exiting. Nothing was printed out,
because it did not have the time to be executed.
Observable are ran in their own schedulers (executors)
@JosePaumard#Devoxx #J8Stream
Merging Observables
@JosePaumard#Devoxx #J8Stream
Merging Observable together
RxJava provides a collection of methods to merge observables
together
@JosePaumard#Devoxx #J8Stream
Merging Observable together
RxJava provides a collection of methods to merge observables
together
With many different, very interesting semantics
@JosePaumard#Devoxx #J8Stream
Representing an Observable
RxJava uses « marble diagrams » to represent observables
Observable
@JosePaumard#Devoxx #J8Stream
Representing an Observable
RxJava uses « marble diagrams » to represent observables
On this example, onNext() is called 5 times
Observable
@JosePaumard#Devoxx #J8Stream
Representing an Observable
RxJava uses « marble diagrams » to represent observables
Here an exception has been thrown, no data will be generated
after it, the onError() method is called
Observable
@JosePaumard#Devoxx #J8Stream
Representing an Observable
RxJava uses « marble diagrams » to represent observables
Here the end of the Stream has been reached, the
onComplete() method is called
Observable
@JosePaumard#Devoxx #J8Stream
The ambiguous operator
The first operator is amb(), that stands for « ambiguous »
It takes the first Observable that produced data
©RxJava
O1
O2
result
@JosePaumard#Devoxx #J8Stream
The zip operator
The zip operator takes one element from each Observable and
combine them using a function
©RxJava
O1
O2
F(O1, O2)
@JosePaumard#Devoxx #J8Stream
The combine latest operator
The combineLatest is a non-strict zip operator, emits an item
each time an observable emits one
©RxJava
O1
O2
F(O1, O2)
@JosePaumard#Devoxx #J8Stream
Concat and merge
Concat: emits O1 and then O2, without mixing them,
merge stops on the emission of an error
©RxJava©RxJava
@JosePaumard#Devoxx #J8Stream
Observables of Observables
The methods we saw are defined on Iterables of Observables
public final static <T> Observable<T> merge(Iterable<Observable<T>> listOfSequences) { }
@JosePaumard#Devoxx #J8Stream
Observables of Observables
The methods we saw are defined on Iterables of Observables
They are also defined on Observables of Observables
public final static <T> Observable<T> merge(Iterable<Observable<T>> listOfSequences) { }
public final static <T> Observable<T> merge(Observable<Observable<T>> sequenceOfSequences) { }
@JosePaumard#Devoxx #J8Stream
Observables of Observables
And the marble diagrams are different
public final static <T> Observable<T> merge(Iterable<Observable<T>> listOfSequences) { }
©RxJava
@JosePaumard#Devoxx #J8Stream
Observables of Observables
And the marble diagrams are different
public final static <T> Observable<T> merge(Observable<Observable<T>> sequenceOfSequences) { }
©RxJava
@JosePaumard#Devoxx #J8Stream
Observables of Observables
Then comes the switchOnNext operator
public final static <T> Observable<T> switchOnNext(Observable<Observable<T>> sequenceOfSequences) { }
©RxJava
@JosePaumard#Devoxx #J8Stream
Hot and cold Observables
@JosePaumard#Devoxx #J8Stream
Cold and hot Observable
A cold Observable emits items if it is observered
@JosePaumard#Devoxx #J8Stream
Cold and hot Observable
A cold Observable emits items if it is observered
A hot Observable emits items when it is created
@JosePaumard#Devoxx #J8Stream
A 3rd example
Let us see an example
Observable<Long> timer = Observable.interval(1, TimeUnit.SECONDS);
Observable<String> manyStrings = Observable.combineLatest(
timer, Observable.just("one"),(i, s) -> i + " - " + s).forEach(System.out::println);
@JosePaumard#Devoxx #J8Stream
A 3rd example
Let us see an example
Observable<Long> timer = Observable.interval(1, TimeUnit.SECONDS);
Observable<String> manyStrings = Observable.combineLatest(
timer, Observable.just("one"),(i, s) -> i + " - " + s).forEach(System.out::println);
> 0 - one 1 - one 2 – one ...
@JosePaumard#Devoxx #J8Stream
A 3rd example
Let us see an example
Observable<Long> timer = Observable.interval(1, TimeUnit.SECONDS);
Thread.sleep(5500);
Observable<String> manyStrings = Observable.combineLatest(
timer, Observable.just("one"),(i, s) -> i + " - " + s).forEach(System.out::println);
@JosePaumard#Devoxx #J8Stream
A 3rd example
Let us see an example
Observable<Long> timer = Observable.interval(1, TimeUnit.SECONDS);
Thread.sleep(5500);
Observable<String> manyStrings = Observable.combineLatest(
timer, Observable.just("one"),(i, s) -> i + " - " + s).forEach(System.out::println);
> 0 - one 1 - one 2 – one ...
@JosePaumard#Devoxx #J8Stream
A 3rd example
It means that the timer does not increment itself if not
observed
This Observable is cold
@JosePaumard#Devoxx #J8Stream
A 3rd example
It means that the timer does not increment itself if not
observed
This Observable is cold
How can we make it hot?
@JosePaumard#Devoxx #J8Stream
A 3rd example
Making it hot:
Observable<Long> timer = Observable.interval(1, TimeUnit.SECONDS);
Observable<String> manyStrings = Observable.combineLatest(
timer, Observable.just("one"),(i, s) -> i + " - " + s).forEach(System.out::println);
Observable<String> moreManyStrings = Observable.combineLatest(
timer, Observable.just("two"),(i, s) -> i + " - " + s).forEach(System.out::println);
@JosePaumard#Devoxx #J8Stream
A 3rd example
It prints out the following:
> 0 - one 0 - two1 – one1 - two2 – one2 – two ...
@JosePaumard#Devoxx #J8Stream
A 3rd example
Making it hot:Observable<Long> timer = Observable.interval(1, TimeUnit.SECONDS);
Observable<String> manyStrings = Observable.combineLatest(
timer, Observable.just("one"),(i, s) -> i + " - " + s).forEach(System.out::println);
Thread.sleep(5500);
Observable<String> moreManyStrings = Observable.combineLatest(
timer, Observable.just("two"),(i, s) -> i + " - " + s).forEach(System.out::println);
@JosePaumard#Devoxx #J8Stream
A 3rd example
It then prints out the following!
> 0 - one1 - one2 - one3 - one4 - one5 - one5 - two6 - one6 - two7 - one7 – two ...
@JosePaumard#Devoxx #J8Stream
Cold and hot Observable
So in its first use, timer is a cold Observable
And in the second one, it becomes a hot Observable
If used more than once, a cold Observable will be seen hot by
the 2nd and later observers
@JosePaumard#Devoxx #J8Stream
Cold and hot Observable
Problem: what happens if we have many observers to
subscribe?
@JosePaumard#Devoxx #J8Stream
Cold and hot Observable
Problem: what happens if we have many observers to
subscribe?
Our application could miss the first values emitted by an
observable
How can we fix that?
@JosePaumard#Devoxx #J8Stream
Cold and hot Observable
We can use the cache method
cache(int capacity) { }
O1
O2
@JosePaumard#Devoxx #J8Stream
A cached timer
Creating a cached timer:
Observable<Long> timer = Observable.interval(1, TimeUnit.SECONDS);Observable<Long> cachedTimer = timer.cache(1_000);
...
@JosePaumard#Devoxx #J8Stream
ConnectableObservable
We can do better and use a ConnectableObservable
(which is an Observable)
// does not count as a subscriptionConnectableObservable<Long> publish = observable.publish();
// take the time to connect all the observers
// then call publish.connect();
@JosePaumard#Devoxx #J8Stream
Cold and hot Observable
In fact the Observable returned by the connect() call is itself a
hot Observable
So this is also a way to create a hot Observable
Coffe break?
Coffe break!Map, filter, reduce, collect
Repeating & retrying
Dealing with time
Backpressure
Combining Java 8 Streams
& RxJava
Comparisons
The future: reactive
support in Java 9
@JosePaumard#Devoxx #J8Stream
Map, filter,reduce and collect
@JosePaumard#Devoxx #J8Stream
Map and filter
Map / filter, return an Observable
Emits only the elements that match the predicate
map(Func1<T, R> mapping) { } cast(Class<R> clazz) { } // casts the elements
filter(Func1<T, Boolean> predicate) { }
@JosePaumard#Devoxx #J8Stream
Materializing
Materialize: special type of mapping
Emit a Notification object that wraps the item, with metadata
Useful for logging
Does the reverse:
materialize() { } // Observable<Notification<T>>
dematerialize() { } // Observable<T>
@JosePaumard#Devoxx #J8Stream
Selecting ranges
Selecting ranges of elements
first() { }last() { }skip(int n) { }limit(int n) { }take(int n) { }
@JosePaumard#Devoxx #J8Stream
Special filtering
Special functions
Emits a true then complete on the onComplete
if the Observable emitted at least one item
exists(Func1<T, Boolean> predicate) { }
@JosePaumard#Devoxx #J8Stream
Special filtering
Special functions
Emits the nth item then complete on the onNext
if the Observable emitted at least n items
elementAt(int n) { }
@JosePaumard#Devoxx #J8Stream
Special filtering
Special functions
Emits nothing then complete on the onComplete
ignoreElement() { }
@JosePaumard#Devoxx #J8Stream
Special filtering
Special functions
Emits the first item on the onComplete
or emits an error on the second item emitted
single() { }
@JosePaumard#Devoxx #J8Stream
Special filtering
Special functions
Emits the matching item if it has been seen, on onComplete
or emits an error if not, on onComplete
single(Func1<T, Boolean> predicate) { }
@JosePaumard#Devoxx #J8Stream
Reductions & collections
Classical reductions
all(Func1<T, Boolean> predicate) { } // Observable<Boolean>count() { } // Observable<Long>
forEach(Action1<T> consumer) { } // void
@JosePaumard#Devoxx #J8Stream
Reductions & collections
Accumulations
Reduce: returns the reduction of all the items, on onComplete
Returns an error if the observable is empty
reduce(Func2<T, T, T> accumulator) { } // Observable<T>
@JosePaumard#Devoxx #J8Stream
Reductions & collections
Accumulations
Reduce: returns the reduction of all the items, on onComplete
In case the observable might be empty, use the other version
of reduce, that takes a seed
reduce(Func2<T, T, T> accumulator) { } // Observable<T>
reduce(seed, Func2<T, T, T> accumulator) { } // Observable<T>
@JosePaumard#Devoxx #J8Stream
Reductions & collections
Accumulations
Scan: returns the reduction step by step, on onNext
Can also take a seed
reduce(Func2<T, T, T> accumulator) { } // Observable<T>
scan(Func2<T, T, T> accumulator) { } // Observable<T>
reduce(seed, Func2<T, T, T> accumulator) { } // Observable<T>
@JosePaumard#Devoxx #J8Stream
Reductions & collections
Collecting data in a mutable container
Example: adding the elements to a list
collect(Func0<R> stateFactory, // ProducerAction2<R, T> collector) { } // BiConsumer
collect(ArrayList::new, // ProducerArrayList::add) { } // BiConsumer
@JosePaumard#Devoxx #J8Stream
Reductions & collections
Ready to use collectors for lists:
toList() { } // Observable<List<T>>toSortedList() { } // Observable<List<T>>
@JosePaumard#Devoxx #J8Stream
Reductions & collections
Ready to use collectors for lists:
And for maps (toMap can lose items):
toList() { } // Observable<List<T>>toSortedList() { } // Observable<List<T>>
toMultimap(Func1<T, K> keySelector, // Observable<Func1<T, V> valueSelector) { } // Map<K, Collection<T>>
toMap(Func1<T, K> keySelector) { } // Observable<Map<K, T>>
@JosePaumard#Devoxx #J8Stream
Reductions & collections
A special kind of map:
The returned observable could hold a single map,
but it does not
groupBy(Func1<T, K> keySelector) { } // function
@JosePaumard#Devoxx #J8Stream
Reductions & collections
A special kind of map:
The returned observable could hold a single map,
but it does not
It holds GroupedObservable items, which is an Observable
with a key
groupBy(Func1<T, K> keySelector) { } // function
@JosePaumard#Devoxx #J8Stream
Reductions & collections
A special kind of map:
The returned observable could hold a single map,
but it does not
Instead of returning an Iterable of key / value pairs
It returns an Observable of key / value pairs
groupBy(Func1<T, K> keySelector) { } // function
@JosePaumard#Devoxx #J8Stream
Reductions & collections
Let us see an example
Observable<Person> people = ...;
// Observable<GroupedObservable<Integer, Person>>people.groupBy(Person::getAge)
@JosePaumard#Devoxx #J8Stream
Reductions & collections
Let us see an example
Observable<Person> people = ...;
// Observable<GroupedObservable<Integer, Person>>people.groupBy(Person::getAge)
.filter(groupedObs -> groupedObs.getKey() > 20)
@JosePaumard#Devoxx #J8Stream
Reductions & collections
Let us see an example
Observable<Person> people = ...;
// GroupedObservable<Integer, Person> extends Observable<Person>people.groupBy(Person::getAge)
.filter(groupedObs -> groupedObs.getKey() > 20)
.flatMap(groupedObs -> groupedObs)
@JosePaumard#Devoxx #J8Stream
Reductions & collections
Let us see an example
Observable<Person> people = ...;
// Observable<Integer>people.groupBy(Person::getAge)
.filter(groupedObs -> groupedObs.getKey() > 20)
.flatMap(groupedObs -> groupedObs)
.count();
@JosePaumard#Devoxx #J8Stream
Wrap-up on map / filter / reduce
All those methods return an Observable
The result is thus wrapped in an Observable
Chaining operations is made with flatMap()
Wrapping and unwrapping has a cost…
@JosePaumard#Devoxx #J8Stream
Repeating and Retrying
@JosePaumard#Devoxx #J8Stream
Repeating & retrying
The repeat method repeats the same Observable
repeat() { } // Observable<T>repeat(long times) { } // Observable<T>
@JosePaumard#Devoxx #J8Stream
Repeating & retrying
The repeat method repeats the same Observable
repeat() { } // Observable<T>repeat(long times) { } // Observable<T>
repeatWhen(Func1<Observable<Void>>, Observable<?>> notificationHandler
) { }
@JosePaumard#Devoxx #J8Stream
Repeating & retrying
repeat: will repeat the same Observable again and again
repeatWhen:
- on the onComplete of the source Observable, the
notification handler is passed an Observable
- if the notification handler calls the onError or onComplete of
this passed Observable, then repeatWhen calls the same on
the returned Observable
@JosePaumard#Devoxx #J8Stream
Repeating & retrying
repeat: will repeat the same Observable again and again
On the returned Observable, one can invoke:
onNext(), that triggers the repetition
onComplete() or onError(), which will trigger the same call on
the source Observable
@JosePaumard#Devoxx #J8Stream
Repeating & retrying
This function takes an Observable<Void>
And returns an Observable<?>
@JosePaumard#Devoxx #J8Stream
Repeating & retrying
This function takes an Observable<Void>
And returns an Observable<?>
When the Observable to be repeated calls onComplete
Then the passed Observable calls onNext with null
As a response the returned Observable should call:
- onNext to repeat
- onComplete to stop repeating
@JosePaumard#Devoxx #J8Stream
Repeating & retrying
The first thing we need is an Observable on which we can emit
items on demand
@JosePaumard#Devoxx #J8Stream
Repeating & retrying
The first thing we need is an Observable on which we can emit
items on demand
For that, we need to implement the OnSubscribe interface
final Set<Subscriber<? super Void>> subscribers = new HashSet<>();
OnSubscribe<Void> onSubscribe = new OnSubscribe<Void>() {
public void call(Subscriber<? super Void> subscriber) {subscribers.add(subscriber);
}};
@JosePaumard#Devoxx #J8Stream
Repeating & retrying
Then create an Observable from this onSubscribe object
final Observable<Void> returnedObservable = Observable.create(onSubscribe);
final Set<Subscriber<? super Void>> subscribers = new HashSet<>();
OnSubscribe<Void> onSubscribe = new OnSubscribe<Void>() {
public void call(Subscriber<? super Void> subscriber) {subscribers.add(subscriber);
}};
@JosePaumard#Devoxx #J8Stream
Repeating & retrying
Then create the notification handler
Func1 notificationHandler = new Func1<Observable<Void>, Observable<Void>>() {
final Observable<Void> returnedObservable = ...;
public Observable<Void> call(Observable<Void> observable) {
// implementation}
return returnedObservable;};
@JosePaumard#Devoxx #J8Stream
Repeating & retrying
Then create the notification handler
observable.subscribe(new Observer<Void>() {
private int count = 0;
public void onCompleted() {}
public void onError(Throwable e) {subscribers.forEach(sub -> sub.onError(e));
}
@JosePaumard#Devoxx #J8Stream
Repeating & retrying
Then create the notification handler
int count = 0;
public void onNext(Void t) {if (count == 3) {
subscribers.forEach(sub -> sub.onCompleted()); // stops} else {
count++;Thread.sleep(1_000);subscribers.forEach(sub -> sub.onNext(null)); // repeat
}}
@JosePaumard#Devoxx #J8Stream
Repeating & retrying
The complete pattern
Observable<Long> timer = Observable.interval(1, TimeUnit.MILLISECONDS).take(5);
timer.repeatWhen((Func1<? super Observable<? extends Void>, ? extends Observable<?>>)notificationHandler)
.forEach(System.out::println);
Thread.sleep(5_000);
@JosePaumard#Devoxx #J8Stream
Repeating & retrying
The retry method will reset the Observable on error
The retryThen() function works in the same way as for the
repeat
retry() { } // Observable<T>retry(long times) { } // Observable<T>
retryWhen(Func1<Observable<Throwable>>, Observable<?>> notificationHandler
) { }
@JosePaumard#Devoxx #J8Stream
Repeating & retrying
There is also a retry family of methods, that works on onError
instead of onComplete
@JosePaumard#Devoxx #J8Stream
Joining
Joins to Observable, based on the overlapping of durations
join(Observable<TRight> right, Func1<T, Observable<TLeftDuration>> leftDurationSelector,Func1<TRight, Observable<TRightDuration>> rightDurationSelector,Func2<T, TRight, R> resultSelector) { }
@JosePaumard#Devoxx #J8Stream
Join
Joins to Observable, based on the overlapping of durations
©RxJava
Left obs.
Right obs.
F(O1, O2)
@JosePaumard#Devoxx #J8Stream
GroupJoin
Joins to Observable, based on the overlapping of durations
groupJoin(Observable<T2> right, Func1<T, Observable<D1>> leftDuration,Func1<T2, Observable<D2>> rightDuration,Func2<T, <T2>, R> resultSelector)
{ }
@JosePaumard#Devoxx #J8Stream
GroupJoin
Joins to Observable, based on the overlapping of durations
©RxJava
Left obs.
Right obs.
F(O1, O2)
@JosePaumard#Devoxx #J8Stream
Dealing with time
@JosePaumard#Devoxx #J8Stream
Dealing with the time
RxJava has a set of methods to deal with time
Let us go back on our cold / hot Observables
It is easy to imagine a hot Observable that emits an onNext
event each second, thus playing the role of a clock
@JosePaumard#Devoxx #J8Stream
Sampling
With hot observables, RxJava can synchronize on a clock
sample(long period, TimeUnit timeUnit) { }
©RxJava
@JosePaumard#Devoxx #J8Stream
Sampling
Or synchronize on a reference Observable!
sample(Observable<U> sampler) { } // samples on emission & completion
©RxJava
O1
sampler
@JosePaumard#Devoxx #J8Stream
RxJava and synchronization
A very powerful feature:
RxJava can synchronize on a clock (real-time)
And on another Observable, it can then take its own time scale
@JosePaumard#Devoxx #J8Stream
Measuring time
If we can measure time, then we can emit it
Will emit an the amount of time between the two last onNext
events
timeInterval() { } // Observable<TimeInterval<T>>
@JosePaumard#Devoxx #J8Stream
Measuring time
If we can measure time, then we can delay an emission
Will reemit the items, with a delay
This delay can be computed from the items themselves
delay(long delay, TimeUnit timeUnit) ; // Observable<T>
delay(Func1<T, Observable<U> func1) ; // Observable<T>
@JosePaumard#Devoxx #J8Stream
Measuring time
If we can measure time, then we can timeout
Will emit an error if no item is seen during this time
timeout(long n, TimeUnit timeUnit) { }
@JosePaumard#Devoxx #J8Stream
Backpressure
@JosePaumard#Devoxx #J8Stream
Fast hot observables
What happens if a hot Observable emits too many items?
What happens when an Observer cannot keep up the pace of
an Observable?
@JosePaumard#Devoxx #J8Stream
Fast hot observables
What happens if a hot Observable emits too many items?
What happens when an Observer cannot keep up the pace of
an Observable?
This leads to the notion of backpressure
@JosePaumard#Devoxx #J8Stream
Backpressure methods
Backpressure is a way to slow down the emission of elements
It can act on the observing side
Several strategies:
- Buffering items
- Skipping items (sampling is a way to implement this)
@JosePaumard#Devoxx #J8Stream
Backpressure methods
Use cases can be very different!
1) The temperature sensors example
- Suppose our application handle a group of temperature
sensors
- Each sensor sends one measure every seconds
@JosePaumard#Devoxx #J8Stream
Backpressure methods
Use cases can be very different!
1) The temperature sensors example
- Suppose our application handle a group of temperature
sensors
- Each sensor sends one measure every seconds
Maybe we can just keep one measure every hour!
Loosing items ≠ loosing information!
@JosePaumard#Devoxx #J8Stream
Backpressure methods
Use cases can be very different!
2) The YouTube example
- YouTube sends a HD video to your browser
- But my bandwidth is too low
@JosePaumard#Devoxx #J8Stream
Backpressure methods
Use cases can be very different!
2) The YouTube example
- YouTube sends a HD video to your browser
- But my bandwidth is too low
Instead of sending HD, YouTube will send me 720p
@JosePaumard#Devoxx #J8Stream
Backpressure methods
Use cases can be very different!
2) The YouTube example
- YouTube sends a HD video to your browser
- But my bandwidth is too low
Instead of sending HD, YouTube will send me 720p
Loosing items ≠ loosing information!
@JosePaumard#Devoxx #J8Stream
Backpressure
Here we are looking at the backpressure from the observer
point of view
But it can also clog a network
Acting at the Observable level can be important too!
@JosePaumard#Devoxx #J8Stream
Buffering
Buffering records data in a buffer and emits it
buffer(int size) { } // Observable<List<T>>
buffer(long timeSpan, TimeUnit unit) { } // Observable<List<T>>buffer(long timeSpan, TimeUnit unit, int maxSize) { }
@JosePaumard#Devoxx #J8Stream
Buffering
Buffering records data in a buffer (a list) and emits it
buffer(int size) { } // Observable<List<T>>
buffer(long timeSpan, TimeUnit unit) { } // Observable<List<T>>buffer(long timeSpan, TimeUnit unit, int maxSize) { }
buffer(Observable<O> bufferOpenings, // Openings eventsFunc1<O, Observable<C>> bufferClosings) { } // Closings events
@JosePaumard#Devoxx #J8Stream
Windowing
Windowing acts as a buffer, but emits Observables instead of
lists of buffered items
window(int size) { } // Observable<Observable<T>>
window(long timeSpan, TimeUnit unit) { } // Observable<Observable<T>>window(long timeSpan, TimeUnit unit, int maxSize) { }
window(Observable<O> bufferOpenings, // Openings eventsFunc1<O, Observable<C>> bufferClosings) { } // Closings events
@JosePaumard#Devoxx #J8Stream
Buffering & windowing
©RxJava
©RxJava
@JosePaumard#Devoxx #J8Stream
Throttling
Throttling is a sampler on the beginning or the end of a window
throttleFirst(long windowDuration, TimeUnit unit) { } // Observable<T>
throttleLast(long windowDuration, TimeUnit unit) { }
throttleWithTimeout(long windowDuration, TimeUnit unit) { }
@JosePaumard#Devoxx #J8Stream
Throttling
throttleFirst: emits the first item in a window of time
throttleLast: emits the last item from that window, on the next
tick of the clock
@JosePaumard#Devoxx #J8Stream
Throttling
Throttling is a sampler on the beginning or the end of a window
©RxJava
@JosePaumard#Devoxx #J8Stream
Debouncing
Debounce also limits the rate of the emission of the items, by
adding a delay before reemitting
debounce(long delay, TimeUnit timeUnit) ; // Observable<T>
debounce(Func1<T, Observable<U> func1) ; // Observable<T>
@JosePaumard#Devoxx #J8Stream
Debouncing
It two items are emitted within the same time frame, the first
one is lost
©RxJava
@JosePaumard#Devoxx #J8Stream
Wrap-up on RxJava
Complex API, many different concepts, many methods
Allows to process data in chosen threads, this is useful for IO,
computations, specialized threads (GUI threads)
Allows the synchronization of operations
on clocks
on application events
Works in pull mode, and also in push mode
backpressure
Getting the best of both worlds?
@JosePaumard#Devoxx #J8Stream
Getting the best of both API?
What about connecting a J8 Stream to a Rx Observable?
@JosePaumard#Devoxx #J8Stream
Getting the best of both API?
If we have an Iterator, this is easy:
Iterator<T> iterator = ... ;
Observable<T> observable = Observable.from(() -> iterator) ;
@JosePaumard#Devoxx #J8Stream
Getting the best of both API?
If we have an Iterator, this is easy:
If we have a Spliterator, not much more complex:
Iterator<T> iterator = ... ;
Observable<T> observable = Observable.from(() -> iterator) ;
Spliterator<T> spliterator = ... ;
Observable<T> observable = Observable.from(() -> Spliterators.iterator(spliterator)) ;
@JosePaumard#Devoxx #J8Stream
Getting the best of both API?
So if we have a Stream, we can easily build an Observable
@JosePaumard#Devoxx #J8Stream
Getting the best of both API?
So if we have a Stream, we can easily build an Observable
What about the other way?
@JosePaumard#Devoxx #J8Stream
Getting the best of both API?
So if we have a Stream, we can easily build an Observable
What about the other way?
1) We can build an Iterator on an Observable
2) Then build a Spliterator on an Iterator
@JosePaumard#Devoxx #J8Stream
Getting the best of both API?
So if we have a Stream, we can easily build an Observable
What about the other way?
1) We can build an Iterator on an Observable
2) Then build a Spliterator on an Iterator
But we need to do that ourselves…
@JosePaumard#Devoxx #J8Stream
Iterating on an Observable
To implement an Iterator:
1) We need to implement next() and hasNext()
2) remove() is a default method in Java 8
@JosePaumard#Devoxx #J8Stream
Iterating on an Observable
The trick is that
an iterator pulls the date from a source
an observable pushes the data to callbacks
@JosePaumard#Devoxx #J8Stream
Iterating on an Observable
The trick is that
an iterator pulls the date from a source
an observable pushes the data to callbacks
So we need an adapter…
@JosePaumard#Devoxx #J8Stream
Iterating on an Observable
How has it been done in the JDK?
public static<T> Iterator<T> iterator(Spliterator<? extends T> spliterator) {
class Adapter implements Iterator<T>, Consumer<T> {// implementation
}
return new Adapter() ;}
@JosePaumard#Devoxx #J8Stream
class Adapter implements Iterator<T>, Consumer<T> {boolean valueReady = false ;T nextElement;
public void accept(T t) {valueReady = true ;nextElement = t ;
}
public boolean hasNext() {if (!valueReady)
spliterator.tryAdvance(this) ; // calls accept()return valueReady ;
}
public T next() {if (!valueReady && !hasNext())
throw new NoSuchElementException() ;else {
valueReady = false ;return nextElement ;
}}
}
@JosePaumard#Devoxx #J8Stream
Iterating on an Observable
Let us adapt this pattern!
public static<T> Iterator<T> of(Observable<? extends T> observable) {
class Adapter implements Iterator<T>, Consumer<T> {// implementation
}
return new Adapter() ;}
@JosePaumard#Devoxx #J8Stream
class Adapter implements Iterator<T>, Consumer<T> {boolean valueReady = false ;T nextElement;
public void accept(T t) { // needs to get the data from the ObservablevalueReady = true ;nextElement = t ;
}
public boolean hasNext() {return valueReady ;
}
public T next() {if (!valueReady && !hasNext())
throw new NoSuchElementException() ;else {
valueReady = false ;return nextElement ;
}}
}
@JosePaumard#Devoxx #J8Stream
Iterating on an Observable
The accept method
class Adapter implements Iterator<T>, Consumer<T> {boolean valueReady = false ;T nextElement;
public void accept(T t) {observable.subscribe(
element -> nextElement = element, // onNextexception -> valueReady = false, // onError() -> valueReady = false // onComplete
) ;}
}
@JosePaumard#Devoxx #J8Stream
Iterating on an Observable
The accept method
class Adapter implements Iterator<T>, Consumer<T> {boolean valueReady = false ;T nextElement;
public void accept(T t) {observable.subscribe(
element -> nextElement = element, // onNextexception -> valueReady = false, // onError() -> valueReady = false // onComplete
) ;}
}
final…
@JosePaumard#Devoxx #J8Stream
Iterating on an Observable
We can wrap those value in Atomic variable
class Adapter implements Iterator<T>, Consumer<T> {AtomicBoolean valueReady = new AtomicBoolean(false) ;AtomicReference<T> nextElement = new AtomicReference() ;
public void accept(T t) {observable.subscribe(
element -> nextElement.set(element), // onNextexception -> valueReady.set(false), // onError() -> valueReady.set(false) // onComplete
) ;}
}
@JosePaumard#Devoxx #J8Stream
Iterating on an Observable
Cant we do better?
interface Wrapper<E> {
E get() ;
}
Wrapper<Boolean> wb = () -> true ;
@JosePaumard#Devoxx #J8Stream
Iterating on an Observable
Cant we do better?
interface Wrapper<E> {
E get() ;
}
Wrapper<Boolean> wb = () -> true ;Action1<Boolean> onNext = b -> wb.set(b) ; // should return Wrapper<T>
@JosePaumard#Devoxx #J8Stream
Iterating on an Observable
Cant we do better?
interface Wrapper<E> {
E get() ;
public default Wrapper<E> set(E e) {// should return a wrapper of e
}}
Wrapper<Boolean> wb = () -> true ;Action1<Boolean> onNext = b -> wb.set(b) ; // should return Wrapper<T>
@JosePaumard#Devoxx #J8Stream
Iterating on an Observable
Cant we do better?
interface Wrapper<E> {
E get() ;
public default Wrapper<E> set(E e) {return () -> e ;
}}
Wrapper<Boolean> wb = () -> true ;Action1<Boolean> onNext = b -> wb.set(b) ; // should return Wrapper<T>
@JosePaumard#Devoxx #J8Stream
Iterating on an Observable
We can wrap those value in Atomic variable
class Adapter implements Iterator<T>, Consumer<T> {Wrapper<Boolean> valueReady = () -> false ;Wrapper<T> nextElement ;
public void accept(T t) {observable.subscribe(
element -> nextElement.set(element), // onNextexception -> valueReady.set(false), // onError() -> valueReady.set(false) // onComplete
) ;}
}
@JosePaumard#Devoxx #J8Stream
Iterating on an Observable
So we can build an Iterator on an Observable
And with it, a Spliterator on an Observable
it will work on cold observables
@JosePaumard#Devoxx #J8Stream
Getting the best of both API
The cold observables can be implemeted with Java 8 Streams
The hot observables can be implemented by combining both
API
Comparisons:Patterns &
Performances
@JosePaumard#Devoxx #J8Stream
Let us do some comparisons
Let us take one use case
Implemented in Rx and J8 Streams
See the different ways of writing the same processings
And compare the processing times using JMH
@JosePaumard#Devoxx #J8Stream
Shakespeare plays Scrabble
Published in Java Magazine
Presented in Devoxx 2014
Used during Virtual Technology Summit
https://community.oracle.com/docs/DOC-916777
https://github.com/JosePaumard/jdk8-lambda-tour
@JosePaumard#Devoxx #J8Stream
Shakespeare plays Scrabble
Basically, we have the set of the words used by Shakespeare
The official Scrabble player dictionnary
And the question is: how good at Scrabble Shakespeare would
have been?
@JosePaumard#Devoxx #J8Stream
Shakespeare plays Scrabble
1) Build the histogram of the letters of a word
2) Number of blanks needed for a letter in a word
3) Number of blanks needed to write a word
4) Predicate to check is a word can be written with 2 blanks
5) Bonus for a doubled letter
6) Final score of a word
7) Histogram of the scores
8) Best word
@JosePaumard#Devoxx #J8Stream
1) Histogram of the letters
Java 8 Stream API – Rx Java
// Histogram of the letters in a given wordFunction<String, Map<Integer, Long>> histoOfLetters =
word -> word.chars().boxed().collect(
Collectors.groupingBy(Function.identity(),Collectors.counting()
)) ;
@JosePaumard#Devoxx #J8Stream
1) Histogram of the letters
Java 8 Stream API – Rx Java
// Histogram of the letters in a given wordFunc1<String, Observable<HashMap<Integer, LongWrapper>>> histoOfLetters =
word -> toIntegerObservable.call(word).collect(
() -> new HashMap<Integer, LongWrapper>(), (HashMap<Integer, LongWrapper> map, Integer value) -> {
LongWrapper newValue = map.get(value) ;if (newValue == null) {
newValue = () -> 0L ;}map.put(value, newValue.incAndSet()) ;
}) ;
@JosePaumard#Devoxx #J8Stream
1) Histogram of the letters
Java 8 Stream API – Rx Javainterface LongWrapper {
long get() ;
public default LongWrapper set(long l) {return () -> l ;
}
public default LongWrapper incAndSet() {return () -> get() + 1L ;
}
public default LongWrapper add(LongWrapper other) {return () -> get() + other.get() ;
}}
@JosePaumard#Devoxx #J8Stream
2) # of blanks for a letter
Java 8 Stream API – Rx Java
// number of blanks for a given letterToLongFunction<Map.Entry<Integer, Long>> blank =
entry -> Long.max(
0L, entry.getValue() -
scrabbleAvailableLetters[entry.getKey() - 'a']) ;
@JosePaumard#Devoxx #J8Stream
2) # of blanks for a letter
Java 8 Stream API – Rx Java
// number of blanks for a given letterFunc1<Entry<Integer, LongWrapper>, Observable<Long>> blank =
entry ->Observable.just(
Long.max(0L, entry.getValue().get() -
scrabbleAvailableLetters[entry.getKey() - 'a']
@JosePaumard#Devoxx #J8Stream
3) # of blanks for a word
Java 8 Stream API – Rx Java
// number of blanks for a given wordFunction<String, Long> nBlanks =
word -> histoOfLetters.apply(word).entrySet().stream().mapToLong(blank).sum();
@JosePaumard#Devoxx #J8Stream
3) # of blanks for a word
Java 8 Stream API – Rx Java
// number of blanks for a given wordFunc1<String, Observable<Long>> nBlanks =
word -> histoOfLetters.call(word).flatMap(map -> Observable.from(() ->
map.entrySet().iterator())).flatMap(blank).reduce(Long::sum) ;
@JosePaumard#Devoxx #J8Stream
4) Predicate for 2 blanks
Java 8 Stream API – Rx Java
// can a word be written with 2 blanks?Predicate<String> checkBlanks = word -> nBlanks.apply(word) <= 2 ;
// can a word be written with 2 blanks?Func1<String, Observable<Boolean>> checkBlanks =
word -> nBlanks.call(word).flatMap(l -> Observable.just(l <= 2L)) ;
@JosePaumard#Devoxx #J8Stream
5) Bonus for a doubled letter – 1
Java 8 Stream API – Rx Java
// Placing the word on the board// Building the streams of first and last lettersFunction<String, IntStream> first3 =
word -> word.chars().limit(3);
@JosePaumard#Devoxx #J8Stream
5) Bonus for a doubled letter – 1
Java 8 Stream API – Rx Java
// Placing the word on the board// Building the streams of first and last lettersFunc1<String, Observable<Integer>> first3 =
word -> Observable.from(
IterableSpliterator.of(word.chars().boxed().limit(3).spliterator()
)) ;
@JosePaumard#Devoxx #J8Stream
5) Bonus for a doubled letter – 2
Java 8 Stream API – Rx Java
// Bonus for double letterToIntFunction<String> bonusForDoubleLetter =
word -> Stream.of(first3.apply(word), last3.apply(word)).flatMapToInt(Function.identity()).map(scoreOfALetter).max().orElse(0) ;
@JosePaumard#Devoxx #J8Stream
5) Bonus for a doubled letter – 2
Java 8 Stream API – Rx Java
// Bonus for double letterFunc1<String, Observable<Integer>> bonusForDoubleLetter =
word -> Observable.just(first3.call(word), last3.call(word)).flatMap(observable -> observable).flatMap(scoreOfALetter).reduce(Integer::max) ;
@JosePaumard#Devoxx #J8Stream
6) Final score of a word
Java 8 Stream API – Rx Java
// score of the word put on the boardFunction<String, Integer> score3 =
word -> 2*(score2.apply(word) + bonusForDoubleLetter.applyAsInt(word))+ (word.length() == 7 ? 50 : 0);
@JosePaumard#Devoxx #J8Stream
6) Final score of a word
Java 8 Stream API – Rx Java
// score of the word put on the boardFunc1<String, Observable<Integer>> score3 =
word ->Observable.just(
score2.call(word), score2.call(word), bonusForDoubleLetter.call(word), bonusForDoubleLetter.call(word), Observable.just(word.length() == 7 ? 50 : 0)
).flatMap(observable -> observable).reduce(Integer::sum) ;
@JosePaumard#Devoxx #J8Stream
7) Histogram of the scores
Java 8 Stream API – Rx Java
Function<Function<String, Integer>, Map<Integer, List<String>>>buildHistoOnScore =
score -> shakespeareWords.stream().parallel().filter(scrabbleWords::contains).filter(checkBlanks).collect(
Collectors.groupingBy(score, () -> new TreeMap<>(Comparator.reverseOrder()), Collectors.toList()
)) ;
@JosePaumard#Devoxx #J8Stream
7) Histogram of the scores
Java 8 Stream API – Rx JavaFunc1<Func1<String, Observable<Integer>>, Observable<TreeMap<Integer, List<String>>>> buildHistoOnScore =
score -> Observable.from(() -> shakespeareWords.iterator()).filter(scrabbleWords::contains).filter(word -> checkBlanks.call(word).toBlocking().first()).collect(
() -> new TreeMap<Integer, List<String>>(Comparator.reverseOrder()),
(TreeMap<Integer, List<String>> map, String word) -> {Integer key = score.call(word).toBlocking().first() ;List<String> list = map.get(key) ;if (list == null) {
list = new ArrayList<String>() ;map.put(key, list) ;
}list.add(word) ;
}) ;
@JosePaumard#Devoxx #J8Stream
8) Best word
Java 8 Stream API – Rx Java
// best key / value pairsList<Entry<Integer, List<String>>> finalList =
buildHistoOnScore.apply(score3).entrySet().stream().limit(3).collect(Collectors.toList()) ;
@JosePaumard#Devoxx #J8Stream
8) Best word
Java 8 Stream API – Rx Java
// best key / value pairsList<Entry<Integer, List<String>>> finalList2 =
buildHistoOnScore.call(score3).flatMap(map -> Observable.from(() ->
map.entrySet().iterator())).take(3).collect(
() -> new ArrayList<Entry<Integer, List<String>>>(), (list, entry) -> { list.add(entry) ; }
).toBlocking().first() ;
@JosePaumard#Devoxx #J8Stream
8) Best word
Java 8 Stream API – Rx Java// best key / value pairsCountDownLatch latch = new CountDownLatch(3) ;List<Entry<Integer, List<String>>> finalList2 =
buildHistoOnScore.call(score3).flatMap(map -> Observable.from(() ->
map.entrySet().iterator())).take(3).collect(
() -> new ArrayList<Entry<Integer, List<String>>>(), (list, entry) -> { list.add(entry) ; latch.countDown() ; }
).forEach(...) ;
latch.await() ;
@JosePaumard#Devoxx #J8Stream
Patterns comparison
Java 8 Stream API: clean, simple, factory methods for
Collectors
RxJava: flatMap calls, lack of factory methods
Java 8 can easily go parallel, which is a plus
@JosePaumard#Devoxx #J8Stream
Performances
Let us use JMH
Standard tool for measuring code performance on the JVM
Developed as an Open JDK tool
By Aleksey Shipilev http://shipilev.net/
https://twitter.com/shipilev
http://openjdk.java.net/projects/code-tools/jmh/
http://openjdk.java.net/projects/code-tools/jcstress/
https://www.parleys.com/tutorial/java-microbenchmark-harness-the-lesser-two-evils
@JosePaumard#Devoxx #J8Stream
JMH
Easy to setup
<dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-core</artifactId><version>1.11.1</version>
</dependency>
@JosePaumard#Devoxx #J8Stream
JMH
Easy to use
@Benchmark@BenchmarkMode(Mode.AverageTime)@OutputTimeUnit(TimeUnit.MILLISECONDS)@Warmup(iterations=5)@Measurement(iterations=5)@Fork(3)public List<Entry<Integer, List<String>>> measureAverage() {
// implementation to test}
@JosePaumard#Devoxx #J8Stream
JMH
Launching a benchmark
> mvn clean install> java –jar target/benchmark.jar
@JosePaumard#Devoxx #J8Stream
JMH
3 ways of measuring performances
average execution time
number of executions per second
quantiles diagrams
@JosePaumard#Devoxx #J8Stream
Performances
Average execution time
Benchmark Mode Cnt Score Error UnitsNonParallelStreams avgt 100 29,027 ± 0,279 ms/op
@JosePaumard#Devoxx #J8Stream
Performances
Average execution time
Benchmark Mode Cnt Score Error UnitsNonParallelStreams avgt 100 29,027 ± 0,279 ms/opRxJava avgt 100 253,788 ± 1,421 ms/op
@JosePaumard#Devoxx #J8Stream
Performances
Average execution time
Benchmark Mode Cnt Score Error UnitsNonParallelStreams avgt 100 29,027 ± 0,279 ms/opRxJava avgt 100 253,788 ± 1,421 ms/opParallelStreams avgt 100 7,624 ± 0,055 ms/op
@JosePaumard#Devoxx #J8Stream
Performances
Average execution time
RxJava spends a lot of time openning observables, due to the
all flatMap patterns
Benchmark Mode Cnt Score Error UnitsNonParallelStreams avgt 100 29,027 ± 0,279 ms/opRxJava avgt 100 253,788 ± 1,421 ms/opParallelStreams avgt 100 7,624 ± 0,055 ms/op
@JosePaumard#Devoxx #J8Stream
Performances
The bench is on Github:
https://github.com/JosePaumard/jdk8-stream-rx-comparison
What about Java 9?
@JosePaumard#Devoxx #J8Stream
Java 8 Stream API
Back to the definitions:
1) A Stream does not hold any data
2) A Stream does not modify its data
@JosePaumard#Devoxx #J8Stream
Java 8 Stream API
Back to the definitions:
1) A Stream does not hold any data
2) A Stream does not modify its data
How does a Stream work?
1) It connects to a source of data: one source = one stream
2) It consumes the data from the source: « pull mode »
@JosePaumard#Devoxx #J8Stream
Java 8 Stream API
What about:
- Connecting several streams to a single source?
@JosePaumard#Devoxx #J8Stream
Java 8 Stream API
What about:
- Connecting several streams to a single source?
- Connecting several sources to a single stream?
@JosePaumard#Devoxx #J8Stream
Java 8 Stream API
What about:
- Connecting several streams to a single source?
- Connecting several sources to a single stream?
- Having a source that produces data whether or not a stream
is connected to it
@JosePaumard#Devoxx #J8Stream
Java 8 Stream API
What about:
- Connecting several streams to a single source?
- Connecting several sources to a single stream?
- Having a source that produces data whether or not a stream
is connected to it
Clearly, the Stream API has not been made to handle this
@JosePaumard#Devoxx #J8Stream
Reactive Stream API
This leads to the « reactive stream » API
3rd party API: Rx Java (and several other languages)
Implementations available as a preview of JDK 9
Everything takes place in java.util.concurrent.FlowAvailable in the current JDK 9
@JosePaumard#Devoxx #J8Stream
Push mode stream
Let us write a model for the source of data
public interface Publisher<T> {
public ... subscribe(Subscriber<T> subscriber);}
@JosePaumard#Devoxx #J8Stream
Push mode stream
Let us write a model for the source of data
As a subscriber I will want to unsubscribe
So I need an object from the publisher
on which I can call cancel()
public interface Publisher<T> {
public ... subscribe(Subscriber<T> subscriber);}
@JosePaumard#Devoxx #J8Stream
Push mode stream
Let us write a model for the source of data
The first idea that could come to mind is to return a
Subscription object
public interface Publisher<T> {
public Subscription subscribe(Subscriber<T> subscriber);}
@JosePaumard#Devoxx #J8Stream
Push mode stream
Let us write a model for the source of data
But it will be a callback, to stay in an asynchronous world
public interface Publisher<T> {
public void subscribe(Subscriber<T> subscriber);}
@JosePaumard#Devoxx #J8Stream
Push mode stream
Callback in the subscriber to get a subscription
public interface Subscriber<T> {
public void onSubscribe(Subscription subscription);}
@JosePaumard#Devoxx #J8Stream
Push mode stream
Callback in the subscriber to get a subscription
public interface Subscriber<T> {
public void onSubscribe(Subscription subscription);}
public interface Subscription {
public void cancel();}
@JosePaumard#Devoxx #J8Stream
Push mode stream
The publisher might look like this
public class SimplePublisher<T> implements Publisher<T> {
private Set<Subscriber<T>> subs = ConcurrentHashMap.newKeySet();
public void subscribe(Subscriber<T> subscriber) {
if (subs.add(subscriber)) {Subscription subscription = new SimpleSubscription();subscriber.onSubscribe(subscription);
}}
}
@JosePaumard#Devoxx #J8Stream
Push mode stream
In the subscribing code
public class SimpleSubscriber<T> implements Subscriber<T> {
private Subscription subscription;
@Overridepublic void onSubscribe(Subscription subscription) {
this.subscription = subscription;}
}
@JosePaumard#Devoxx #J8Stream
Push mode stream
In the running code
Publisher<String> publisher = ...;Subscriber<String> subscriber = ...;
publisher.subscribe(subscriber);
// some more code
subscriber.getSubscription().cancel();
@JosePaumard#Devoxx #J8Stream
Push mode stream
Callback in the subscriber to get a subscription
I also need callbacks to get the data itself
public interface Subscriber<T> {
public void onSubscribe(Subscription subscription);}
@JosePaumard#Devoxx #J8Stream
Push mode stream
Callback in the subscriber to get a subscription
public interface Subscriber<T> {
public void onSubscribe(Subscription subscription);
}
@JosePaumard#Devoxx #J8Stream
Push mode stream
Callback in the subscriber to get a subscription
public interface Subscriber<T> {
public void onSubscribe(Subscription subscription);
public void onNext(T item);
}
@JosePaumard#Devoxx #J8Stream
Push mode stream
Callback in the subscriber to get a subscription
public interface Subscriber<T> {
public void onSubscribe(Subscription subscription);
public void onNext(T item);
public void onComplete();
}
@JosePaumard#Devoxx #J8Stream
Push mode stream
Callback in the subscriber to get a subscription
public interface Subscriber<T> {
public void onSubscribe(Subscription subscription);
public void onNext(T item);
public void onComplete();
public void onError(Throwable throwable);}
@JosePaumard#Devoxx #J8Stream
Push mode stream
Having a source that produces data independantly from its
consumers implies to work in an asynchronous mode
The API is built on callbacks
@JosePaumard#Devoxx #J8Stream
Several streams per source
In « pull mode », it would not work, or would require the
streams to be synchronized
map Filter 1 Filter 2 Average
map Filter 1 Histogram
Data
@JosePaumard#Devoxx #J8Stream
Several streams per source
In « pull mode », it would not work, or would require the
streams to be synchronized
In « push mode », it does not raise any problem
map Filter 1 Filter 2 Average
map Filter 1 Histogram
Data
@JosePaumard#Devoxx #J8Stream
Several sources for a stream
In « pull mode », it requires a special Spliterator
map Filter 1 Filter 2 Average
Data 1
Data 2
@JosePaumard#Devoxx #J8Stream
Several sources for a stream
In « pull mode », it requires a special Spliterator
In « push mode », since both sources are not synchronized,
we may have problems
map Filter 1 Filter 2 Average
Data 1
Data 2
@JosePaumard#Devoxx #J8Stream
Push mode with several sources
At some point in our data processing pipeline we want to see
both sources as one, ie merged in some way
@JosePaumard#Devoxx #J8Stream
Push mode with several sources
At some point in our data processing pipeline we want to see
both sources as one, ie merged in some way
How can we merge them if one source is faster than the other?
@JosePaumard#Devoxx #J8Stream
Push mode with several sources
At some point in our data processing pipeline we want to see
both sources as one, ie merged in some way
How can we merge them if one source is faster than the other?
We have all the strategies defined in RxJava
@JosePaumard#Devoxx #J8Stream
Merging sources in push mode
1) Decide to follow one of the streams, the first one
2) Combine the two last seen items,
- everytime a new item is generated
- or synchronized on a clock, or another source
@JosePaumard#Devoxx #J8Stream
Merging sources in push mode
1) Decide to follow one of the streams, the first one
2) Combine the two last seen items,
- everytime a new item is generated
- or synchronized on a clock, or another source
Sampler, Debouncer, Throttler, …
@JosePaumard#Devoxx #J8Stream
The question of backpressure
What will happen if a source is « too fast »?
That is, a consumer cannot process data fast enough
It leads to the same question of « backpressure »
@JosePaumard#Devoxx #J8Stream
Backpressure
Several strategies:
1) Create a buffer
2) Synchronize on a clock, or a gate, that could be generated
by the slow observer and sample, or windows, or
debounce, or…
3) Try to slow down the source (can be done if I have the
hand on both the producer and the consumer)
@JosePaumard#Devoxx #J8Stream
Backpressure
There is code for that in the Subscription object
The request() method is there to give information to the
producer
public interface Subscription {
public void cancel();
public void request(long n);}
@JosePaumard#Devoxx #J8Stream
Backpressure
There is code for that in the Subscription object
The request() method is there to give information to the
producer
public void onNext(String element) {
// process the element
this.subscription.request(1L);}
@JosePaumard#Devoxx #J8Stream
Backpressure
Several strategies:
1) Create a buffer
2) Synchronize on a clock, or a gate, that could be generated
by the slow observer and sample, or windows, or
debounce, or…
3) Try to slow down the source (can be done if I have the
hand on both the producer and the consumer)
4) Have several observers in parallel and then merge the
results
@JosePaumard#Devoxx #J8Stream
Reactive Streams
New concept (at least in the JDK)
New complexity, several use cases are possible
Still under work (in the JDK and in 3rd party)
@JosePaumard#Devoxx #J8Stream
Reactive Streams links
Some references on the reactive streams:
http://www.reactive-streams.org/
http://reactivex.io/
https://github.com/reactive-streams/
http://openjdk.java.net/jeps/266
http://gee.cs.oswego.edu/dl/jsr166/dist/docs/index.html
@JosePaumard#Devoxx #J8Stream
Reactive Streams links
In the classes currently available in the JSR 166 package:
- The class Flow has the Publisher, Subscriber and
Subscription interfaces, and the Processor interface
- The class SubmissionPublisher, that implements Publisher,
meant to be overriden or used as a component of a complete
implementation
Just made it to the OpenJDK 9
Conclusion
@JosePaumard#Devoxx #J8Stream
Conclusion
Functional programming is in the mood
From the pure performance point of view, things are not that
simple
Java 8 Streams have adopted a partial functional approach,
which is probably a good trade off
@JosePaumard#Devoxx #J8Stream
Conclusion
Java 8 Stream API: great API to process data in a « pull »
mode
The introduction of a « push » mode allows for many
improvements (synchronization, backpressure)
The backpressure question is relevant
Loosing items ≠ loosing information!
@JosePaumard#Devoxx #J8Stream
Conclusion
RxJava: rich and complex API
many patterns are available in Java 8 Streams
can run in Java 7 applications
the « push » approach is very interesting
choose your use case carefully, to avoir performance hits
http://www.reactive-streams.org/
http://reactivex.io/
https://github.com/reactive-streams/
@JosePaumard#Devoxx #J8Stream
Conclusion
Java 8 Streams: part of the JDK
good performances, efficient memory footprint
parallelization
extensible through the Spliterator patterns
@JosePaumard#Devoxx #J8Stream
Conclusion
Streams & Reactive Streams are very active topics
Java Stream has been released with Java 8, improvements
will be added in Java 9 and beyond
Reactive Streams has several 3rd party implementations
(RxJava) in several languages
Will be part of Java 9
Thank you
Q/A
Recommended