SlideShare a Scribd company logo
1 of 81
Download to read offline
RxJava Applied:
Concise Examples where It Shines
Igor Lozynskyi
JavaDay Kyiv - Oct 14-15, 2016
Software Engineer at GlobalLogic
Presentation’s home
https://github.com/aigor/rx-presentation
Outline
● RxJava vs Java 8 Streams
● RxJava usage example
Pre Java 8 data processing
interface Iterator<T> {
T next();
boolean hasNext();
void remove();
}
Pre Java 8 data processing
List<Employee> employees = service.getEmployees();
Map<Integer, List<Employee>> ageDistribution = new HashMap<>();
for (Employee employee : employees) {
if (employee.getAge() > 25){
List<Employee> thisAge = ageDistribution.get(employee.getAge());
if (thisAge != null){
thisAge.add(employee);
} else{
List<Employee> createThisAge = new ArrayList<>();
createThisAge.add(employee);
ageDistribution.put(employee.getAge(), createThisAge);
}
}
}
System.out.println(ageDistribution);
Java 8 Stream ...
● Connects data source and client
● Do not hold any data
● Implements map / filter / reduce pattern
● Enforces functional style of data processing
Stream collectors
List<Employee> employees = service.getEmployees();
Map<Integer, List<Employee>> ageDistribution = new HashMap<>();
for (Employee employee : employees) {
if (employee.getAge() > 25) {
List<Employee> thisAge = ageDistribution.get(employee.getAge());
if (thisAge != null){
thisAge.add(employee);
} else {
List<Employee> createThisAge = new ArrayList<>();
createThisAge.add(employee);
ageDistribution.put(employee.getAge(), createThisAge);
}
}
}
System.out.println(ageDistribution);
Stream collectors
List<Employee> employees = service.getEmployees();
Map<Integer, List<Employee>> ageDistribution =
employees.stream()
.filter(e -> e.getAge() > 25)
.collect(Collectors.groupingBy(Employee::getAge));
System.out.println(ageDistribution);
Stream collectors
List<Employee> employees = service.getEmployees();
Map<Integer, Long> ageDistribution =
employees.stream()
.filter(e -> e.getAge() > 25)
.collect(Collectors.groupingBy(
Employee::getAge,
Collectors.counting()
));
Stream sources
Stream<String> stream1 = Arrays.asList("A", "B").stream();
Stream<String> stream2 = Stream.of("Q", "P", "R");
IntStream chars = "some text".chars();
Stream<String> words
= Pattern.compile(" ").splitAsStream("some other text");
Non-standard stream sources
Can we use non-standard stream sources?
?
Stream generator
Stream
.generate(() -> UUID.randomUUID().toString())
.limit(4)
.forEach(System.out::println);
Spliterator interface
public interface Spliterator<T> {
boolean tryAdvance(Consumer<? super T> action);
Spliterator<T> trySplit();
long estimateSize();
int characteristics();
}
class RandomSpliterator implements Spliterator<String> {
public boolean tryAdvance(Consumer<? super String> action) {
action.accept(UUID.randomUUID().toString());
return true;
}
// for parallel streams
public Spliterator<String> trySplit() {
return null;
}
public long estimateSize() { return Long.MAX_VALUE; };
public int characteristics() { return NONNULL | DISTINCT; }
}
Generate sequence: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, ...
IntStream sequence =
IntStream.rangeClosed(1, 50)
.flatMap(i ->
IntStream.iterate(i, identity()).limit(i)
);
sequence.forEach(System.out::println);
Stream API is powerful!
Stream API - async processing
getEmployeeIds().stream()
.map(this::doHttpRequest)
.collect(Collectors.toList())
Output:
[main] processing request: c677c497
[main] processing request: 3b5320a9
[main] processing request: 9248b92e
[main] processing request: 97a68a53
Stream API - async processing
getEmployeeIds().stream()
.parallel()
.map(this::doHttpRequest)
.collect(Collectors.toList())
Output:
[main] processing request: 7674da72
[ForkJoinPool.commonPool-worker-2] processing request: 747ae948
[ForkJoinPool.commonPool-worker-1] processing request: 33fe0bac
[ForkJoinPool.commonPool-worker-3] processing request: 812f69f3
[main] processing request: 11dda466
[ForkJoinPool.commonPool-worker-2] processing request: 12e22a10
[ForkJoinPool.commonPool-worker-1] processing request: e2b324f9
[ForkJoinPool.commonPool-worker-3] processing request: 8f9f8a97
Stream API - async processing
ForkJoinPool forkJoinPool = new ForkJoinPool(80);
forkJoinPool.submit(() ->
getEmployeeIds().stream()
.parallel()
.map(this::doHttpRequest)
.collect(Collectors.toList())
).get();
Stream API has some limitations
Reactive Streams: what the difference
RxJava
http://reactivex.io
https://github.com/ReactiveX/RxJava
17,700 stars on GitHub
Short history
● From 2009 for .NET
● From 2013 for JVM (latest: 1.2.1, Oct 5, 2016)
● Now a lot of other languages
RxJava Observer
interface Observer<T> {
void onNext(T t);
void onCompleted();
void onError(Throwable e);
}
RxJava Subscription
interface Subscription {
void unsubscribe();
boolean isUnsubscribed();
}
Observable
● Central class in RxJava library
● It’s big: ~ 10k lines of code (with comments)
● It’s complex: ~ 100 static methods, ~ 150 non-static
Subscription sub =
Observable
.create(s -> {
s.onNext("A");
s.onNext("B");
s.onCompleted();
})
.subscribe(m -> log.info("Message received: " + m),
e -> log.warning("Error: " + e.getMessage()),
() -> log.info("Done!"));
Output:
Message received: A
Message received: B
Done!
Observable<Integer> empty = Observable.empty();
Observable<Integer> never = Observable.never();
Observable<Integer> error = Observable.error(exception);
Observable useful for tests
never() - never emit anything (no data, no errors)
public static <T, Resource> Observable<T> using(
final Func0<Resource> resourceFactory,
final Func1<Resource, Observable<T>> observableFactory,
final Action1<Resource> disposeAction
){ }
Observable from resource
Observable
.from(Arrays.asList(1, 2, 5, 7, 8, 12, 3, 6, 7, 8))
.filter(i -> (i > 3 && i < 8))
.forEach(System.out::println);
Output:
5
7
6
7
Marble diagram: filter
Marble diagram: last
Marble diagram: reduce
Marble diagram: buffer
Marble diagram: timer
Marble diagram: interval
Time series
Observable<Long> timer = Observable.timer(2, TimeUnit.SECONDS);
Observable<Long> interval = Observable.interval(1, TimeUnit.SECONDS);
timer.forEach(System.out::println);
interval.forEach(System.out::println);
Output:
Process finished with exit code 0
Time series
Observable<Long> timer = Observable.timer(2, TimeUnit.SECONDS);
Observable<Long> interval = Observable.interval(1, TimeUnit.SECONDS);
timer.forEach(i -> System.out.println(currentThread().getName() + " - " + i));
interval.forEach(i -> System.out.println(currentThread().getName() + " - " + i));
Output:
RxComputationThreadPool-2 - 0
RxComputationThreadPool-1 - 0
RxComputationThreadPool-2 - 1
Process finished with exit code 0
Thread.sleep(2000);
Schedulers
public final class Schedulers {
public static Scheduler immediate() {...}
public static Scheduler trampoline() {...}
public static Scheduler newThread() {...}
public static Scheduler computation() {...}
public static Scheduler io() {...}
public static TestScheduler test() {...}
public static Scheduler from(Executor executor) {...}
}
Schedulers
Observable.from("one", "two", "three", "four", "five")
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(/* an Observer */);
Marble diagram: merge
Merge
Observable<Integer> odds = Observable
.just(1, 3, 5).subscribeOn(Schedulers.io());
Observable<Integer> evens = Observable.just(2, 4, 6);
Observable.merge(odds, evens)
.subscribe(
System.out::println,
System.err::println,
() -> System.out.println("Finished"));
Marble diagram: zip
Zip
Observable<String> odds = Observable.just("A", "B", "C", "D");
Observable<Integer> evens = Observable.just(2, 4, 6);
Observable.zip(odds, evens, (a, b) -> a + "-" + b)
.subscribe(
System.out::println,
System.err::println,
() -> System.out.println("Finished"));
Output:
A-2
B-4
C-6
Finished
Cold & Hot Observables
● Cold Observable emits events only having subscriber
● Hot Observable emits events even without subscribers
Backpressure
Backpressure
Backpreasure is a way to slow down emission of elements
It can act on observing side
Strategies:
● Buffering items (buffer, window)
● Skipping items (sampling, throttling, etc)
● Request for specific number of elements
Backpressure: request for elements
public interface Producer {
void request(long n);
}
Stream API vs RxJava
RxJava:
● allows to process data in chosen thread, this is useful for IO,
computations, specialized threads (GUI threads),
● allows synchronization on clocks and application events,
● works in push mode, producer initiates data transfer, but consumer may
control data flow via backpressure.
Stream API:
● tuned for hot data processing,
● good for parallel computation,
● has rich set of collectors for data.
Shakespeare Plays Scrabble Benchmark (throughput)
Non-Parallel Streams 44.995 ± 1.718 ms/op
Parallel Streams 14.095 ± 0.616 ms/op
RxJava 310.378 ± 9.688 ms/op
RxJava (2.0.0-RC4) 156.334 ± 8.423 ms/op
Performance
Based on JMH benchmark by Jose Paumard
Scenarios where RxJava shines
● Observables are better callbacks (easily wrap callbacks)
● Observables are highly composable (on contrast with callbacks)
● Provide async stream of data (on contrast with CompletableFuture)
● Observables can handle errors (have retry / backup strategies)
● Give complete control over running threads
● Good for managing IO rich application workflows
● Perfect for Android development (no Java 8 required, retrolambda compatible)
● Netflix uses RxJava for most their internal APIs
Request flow
Created with draw.io
Stream API and RxJava are friends!
You can easily build Observable from Stream
Iterator<T> iterator = ...;
Observable<T> observable = Observable.from(() -> iterator);
You can map Observable to Stream by implementing adapter
public static<T> Iterator<T> of(Observable<? extends T> ob){
class Adapter implements Iterator<T>, Consumer<T> {
...
}
return new Adapter();
}
External libraries that work with RxJava
● hystrix - latency and fault tolerance bulkheading library
● camel RX - to reuse Apache Camel components
● rxjava-http-tail - allows you to follow logs over HTTP
● mod-rxvertx - extension for VertX that provides support Rx
● rxjava-jdbc - use RxJava with JDBC to stream ResultSets
● rtree - immutable in-memory R-tree and R*-tree with RxJava
● and many more ...
RxJava is not new
JDeferred
CompletableFuture<T>
Scala.Rx
Scala Actors
Spring Integration
What’s around?
● Google Agera - reactive streams for Android by Google
● Project Reactor - flow API implementation by Pivotal & Spring
● Akka-Streams - Akka based streams
● Monix - Scala based implementation of Reactive Streams
● Vert.x, Lagom - if you want more than streams
Let’s build something with RxJava
Use case: Stream of tweets
Created with Balsamiq Mockups
Twitter API
Twitter Stream API (WebSocket alike):
● Doc: https://dev.twitter.com/streaming/overview
● Library: com.twitter:hbc-core:2.2.0
Twitter REST API:
● GET https://api.twitter.com/1.1/users/show.json?screen_name=jeeconf
● GET https://api.twitter.com/1.1/search/tweets.json?q=from:jeeconf
Let’s look at entities
class Tweet {
String text;
int favorite_count;
String author;
int author_followers;
}
class Profile {
String screen_name;
String name;
String location;
int statuses_count;
int followers_count;
}
class UserWithTweet {
Profile profile;
Tweet tweet;
}
Marble diagram
Profile getUserProfile(String screenName) {
ObjectMapper om = new ObjectMapper();
return (Profile) om.readValue(om.readTree(
Unirest.get(API_BASE_URL + "users/show.json")
.queryString("screen_name", screenName)
.header("Authorization", bearerAuth(authToken.get()))
.asString()
.getBody()),
Profile.class);
}
Get user profile synchronously
Get user profile asynchronously
Observable<Profile> getUserProfile(String screenName) {
return Observable.fromCallable(() -> {
ObjectMapper om = new ObjectMapper();
return (Profile) om.readValue(om.readTree(
Unirest.get(API_BASE_URL + "users/show.json")
.queryString("screen_name", screenName)
.header("Authorization", bearerAuth(authToken.get()))
.asString()
.getBody()),
Profile.class);
});
}
Add some errors handling
Observable<Profile> getUserProfile(String screenName) {
if (authToken.isPresent()) {
return Observable.fromCallable(() -> {
ObjectMapper om = new ObjectMapper();
return (Profile) om.readValue(om.readTree(
Unirest.get(API_BASE_URL + "users/show.json")
.queryString("screen_name", screenName)
.header("Authorization", bearerAuth(authToken.get()))
.asString()
.getBody()),
Profile.class);
}).doOnCompleted(() -> log("getUserProfile completed for: " + screenName));
} else {
return Observable.error(new RuntimeException("Can not connect to twitter"));
}
}
Let’s do something concurrently
Illustration: Arsenal Firearms S.r.l.
Get data concurrently
Observable<UserWithTweet> getUserAndPopularTweet(String author){
return Observable.just(author)
.flatMap(u -> {
Observable<Profile> profile = client.getUserProfile(u)
.subscribeOn(Schedulers.io());
Observable<Tweet> tweet = client.getUserRecentTweets(u)
.defaultIfEmpty(null)
.reduce((t1, t2) ->
t1.retweet_count > t2.retweet_count ? t1 : t2)
.subscribeOn(Schedulers.io());
return Observable.zip(profile, tweet, UserWithTweet::new);
});
}
Let’s subscribe on stream of tweets!
streamClient.getStream("RxJava", "JavaDay", "Java")
streamClient.getStream("RxJava", "JavaDay", "Java", "Trump", "Hillary")
streamClient.getStream("RxJava", "JavaDay", "Java", "Trump", "Hillary")
.distinctUntilChanged()
.map(p -> p.author)
.flatMap(name -> getUserAndPopularTweet(name))
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.immediate())
.subscribe(p -> log.info("The most popular tweet of user "
+ p.profile.name + ": " + p.tweet));
.scan((u1, u2) -> u1.author_followers > u2.author_followers ? u1 : u2)
streamClient.getStream("RxJava", "JavaDay", "Java", "Trump")
.scan((u1, u2) -> u1.author_followers > u2.author_followers ? u1 : u2)
.distinctUntilChanged()
.map(p -> p.author)
.flatMap(name -> {
Observable<Profile> profile = client.getUserProfile(name)
.subscribeOn(Schedulers.io());
Observable<Tweet> tweet = client.getUserRecentTweets(name)
.defaultIfEmpty(null)
.reduce((t1, t2) ->
t1.retweet_count > t2.retweet_count ? t1 : t2)
.subscribeOn(Schedulers.io());
return Observable.zip(profile, tweet, UserWithTweet::new);
})
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.immediate())
.subscribe(p -> log.info("The most popular tweet of user "
+ p.profile.name + ": " + p.tweet));
Marble diagram
@Test public void correctlyJoinsHttpResults() throws Exception {
String testUser = "testUser";
Profile profile = new Profile("u1", "Name", "USA", 10, 20, 30);
Tweet tweet1 = new Tweet("text-1", 10, 20, testUser, 30);
Tweet tweet2 = new Tweet("text-2", 40, 50, testUser, 30);
TwitterClient client = mock(TwitterClient.class);
when(client.getUserProfile(testUser)).thenReturn(Observable.just(profile));
when(client.getUserRecentTweets(testUser)).thenReturn(Observable.just(tweet1, tweet2));
TestSubscriber<UserWithTweet> testSubscriber = new TestSubscriber<>();
new Solutions().getUserAndPopularTweet(client, testUser).subscribe(testSubscriber);
testSubscriber.awaitTerminalEvent();
assertEquals(singletonList(new UserWithTweet(profile, tweet2)),
testSubscriber.getOnNextEvents());
}
Don’t believe it works?
● API is big (150+ methods to remember)
● Requires to understand underlying magic
● Hard to debug
● Don’t forget about back pressure
Conclusions: pitfalls
● It is functional, it is reactive*
● Good for integration scenarios
● Allows to control execution threads
● Easy to compose workflows
● Easy to integrate into existing solutions
● Easy Ok to test
* RxJava is inspired by FRP (Functional Reactive Programming), but doesn’t implement it
Conclusions: strength
● RxJava 2.0 in few weeks!
● RxJava 2.0 is better and faster
● RxJava 2.0 is Java 9 Flow API compatible
● We will see more and more reactive streams
Conclusions: future
Q&A
https://github.com/aigor/rx-presentation
Thanks
Presentation template by SlidesCarnival
@siromaha
aigooor@gmail.com

More Related Content

What's hot

What's hot (20)

Introduction to Reactive Java
Introduction to Reactive JavaIntroduction to Reactive Java
Introduction to Reactive Java
 
Reactive programming with RxJava
Reactive programming with RxJavaReactive programming with RxJava
Reactive programming with RxJava
 
Practical RxJava for Android
Practical RxJava for AndroidPractical RxJava for Android
Practical RxJava for Android
 
Reactive Java (GeeCON 2014)
Reactive Java (GeeCON 2014)Reactive Java (GeeCON 2014)
Reactive Java (GeeCON 2014)
 
Reactive programming on Android
Reactive programming on AndroidReactive programming on Android
Reactive programming on Android
 
Non Blocking I/O for Everyone with RxJava
Non Blocking I/O for Everyone with RxJavaNon Blocking I/O for Everyone with RxJava
Non Blocking I/O for Everyone with RxJava
 
rx-java-presentation
rx-java-presentationrx-java-presentation
rx-java-presentation
 
The Road To Reactive with RxJava JEEConf 2016
The Road To Reactive with RxJava JEEConf 2016The Road To Reactive with RxJava JEEConf 2016
The Road To Reactive with RxJava JEEConf 2016
 
Functional Reactive Programming in Clojurescript
Functional Reactive Programming in ClojurescriptFunctional Reactive Programming in Clojurescript
Functional Reactive Programming in Clojurescript
 
Reactive Programming on Android - RxAndroid - RxJava
Reactive Programming on Android - RxAndroid - RxJavaReactive Programming on Android - RxAndroid - RxJava
Reactive Programming on Android - RxAndroid - RxJava
 
Streams, Streams Everywhere! An Introduction to Rx
Streams, Streams Everywhere! An Introduction to RxStreams, Streams Everywhere! An Introduction to Rx
Streams, Streams Everywhere! An Introduction to Rx
 
RxJava in practice
RxJava in practice RxJava in practice
RxJava in practice
 
RxJava 2.0 介紹
RxJava 2.0 介紹RxJava 2.0 介紹
RxJava 2.0 介紹
 
Rx java in action
Rx java in actionRx java in action
Rx java in action
 
Rxjava 介紹與 Android 中的 RxJava
Rxjava 介紹與 Android 中的 RxJavaRxjava 介紹與 Android 中的 RxJava
Rxjava 介紹與 Android 中的 RxJava
 
Reactive programming with RxAndroid
Reactive programming with RxAndroidReactive programming with RxAndroid
Reactive programming with RxAndroid
 
Reactive Programming in Java 8 with Rx-Java
Reactive Programming in Java 8 with Rx-JavaReactive Programming in Java 8 with Rx-Java
Reactive Programming in Java 8 with Rx-Java
 
Real world functional reactive programming
Real world functional reactive programmingReal world functional reactive programming
Real world functional reactive programming
 
JavaOne 2013: Java 8 - The Good Parts
JavaOne 2013: Java 8 - The Good PartsJavaOne 2013: Java 8 - The Good Parts
JavaOne 2013: Java 8 - The Good Parts
 
RxJava for Android - GDG DevFest Ukraine 2015
RxJava for Android - GDG DevFest Ukraine 2015RxJava for Android - GDG DevFest Ukraine 2015
RxJava for Android - GDG DevFest Ukraine 2015
 

Viewers also liked

Issues in knowledge representation
Issues in knowledge representationIssues in knowledge representation
Issues in knowledge representation
Sravanthi Emani
 
Knowledge representation in AI
Knowledge representation in AIKnowledge representation in AI
Knowledge representation in AI
Vishal Singh
 

Viewers also liked (7)

Reactive Thinking in Java
Reactive Thinking in JavaReactive Thinking in Java
Reactive Thinking in Java
 
04 pig data operations
04 pig data operations04 pig data operations
04 pig data operations
 
Issues in knowledge representation
Issues in knowledge representationIssues in knowledge representation
Issues in knowledge representation
 
Intro to Graph Databases Using Tinkerpop, TitanDB, and Gremlin
Intro to Graph Databases Using Tinkerpop, TitanDB, and GremlinIntro to Graph Databases Using Tinkerpop, TitanDB, and Gremlin
Intro to Graph Databases Using Tinkerpop, TitanDB, and Gremlin
 
Knowledge representation in AI
Knowledge representation in AIKnowledge representation in AI
Knowledge representation in AI
 
Knowledge representation and Predicate logic
Knowledge representation and Predicate logicKnowledge representation and Predicate logic
Knowledge representation and Predicate logic
 
Introduction to Machine Learning
Introduction to Machine LearningIntroduction to Machine Learning
Introduction to Machine Learning
 

Similar to RxJava applied [JavaDay Kyiv 2016]

Developing streaming applications with apache apex (strata + hadoop world)
Developing streaming applications with apache apex (strata + hadoop world)Developing streaming applications with apache apex (strata + hadoop world)
Developing streaming applications with apache apex (strata + hadoop world)
Apache Apex
 
PgQ Generic high-performance queue for PostgreSQL
PgQ Generic high-performance queue for PostgreSQLPgQ Generic high-performance queue for PostgreSQL
PgQ Generic high-performance queue for PostgreSQL
elliando dias
 

Similar to RxJava applied [JavaDay Kyiv 2016] (20)

Developing streaming applications with apache apex (strata + hadoop world)
Developing streaming applications with apache apex (strata + hadoop world)Developing streaming applications with apache apex (strata + hadoop world)
Developing streaming applications with apache apex (strata + hadoop world)
 
[245] presto 내부구조 파헤치기
[245] presto 내부구조 파헤치기[245] presto 내부구조 파헤치기
[245] presto 내부구조 파헤치기
 
Presto anatomy
Presto anatomyPresto anatomy
Presto anatomy
 
Javaone 2016 : Supercharge your (reactive) Streams
Javaone 2016 : Supercharge your (reactive) StreamsJavaone 2016 : Supercharge your (reactive) Streams
Javaone 2016 : Supercharge your (reactive) Streams
 
A Deep Dive into Query Execution Engine of Spark SQL
A Deep Dive into Query Execution Engine of Spark SQLA Deep Dive into Query Execution Engine of Spark SQL
A Deep Dive into Query Execution Engine of Spark SQL
 
Flink 0.10 @ Bay Area Meetup (October 2015)
Flink 0.10 @ Bay Area Meetup (October 2015)Flink 0.10 @ Bay Area Meetup (October 2015)
Flink 0.10 @ Bay Area Meetup (October 2015)
 
Finagle and Java Service Framework at Pinterest
Finagle and Java Service Framework at PinterestFinagle and Java Service Framework at Pinterest
Finagle and Java Service Framework at Pinterest
 
Functional UIs with Java 8 and Vaadin JavaOne2014
Functional UIs with Java 8 and Vaadin JavaOne2014Functional UIs with Java 8 and Vaadin JavaOne2014
Functional UIs with Java 8 and Vaadin JavaOne2014
 
Where the wild things are - Benchmarking and Micro-Optimisations
Where the wild things are - Benchmarking and Micro-OptimisationsWhere the wild things are - Benchmarking and Micro-Optimisations
Where the wild things are - Benchmarking and Micro-Optimisations
 
PgQ Generic high-performance queue for PostgreSQL
PgQ Generic high-performance queue for PostgreSQLPgQ Generic high-performance queue for PostgreSQL
PgQ Generic high-performance queue for PostgreSQL
 
JS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless Bebop
 
GDG DevFest 2015 - Reactive approach for slowpokes
GDG DevFest 2015 - Reactive approach for slowpokesGDG DevFest 2015 - Reactive approach for slowpokes
GDG DevFest 2015 - Reactive approach for slowpokes
 
What is new in java 8 concurrency
What is new in java 8 concurrencyWhat is new in java 8 concurrency
What is new in java 8 concurrency
 
Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2
 
How to Think in RxJava Before Reacting
How to Think in RxJava Before ReactingHow to Think in RxJava Before Reacting
How to Think in RxJava Before Reacting
 
Continuous Application with Structured Streaming 2.0
Continuous Application with Structured Streaming 2.0Continuous Application with Structured Streaming 2.0
Continuous Application with Structured Streaming 2.0
 
[JEEConf-2017] RxJava as a key component in mature Big Data product
[JEEConf-2017] RxJava as a key component in mature Big Data product[JEEConf-2017] RxJava as a key component in mature Big Data product
[JEEConf-2017] RxJava as a key component in mature Big Data product
 
Lambdas puzzler - Peter Lawrey
Lambdas puzzler - Peter LawreyLambdas puzzler - Peter Lawrey
Lambdas puzzler - Peter Lawrey
 
Realtime Statistics based on Apache Storm and RocketMQ
Realtime Statistics based on Apache Storm and RocketMQRealtime Statistics based on Apache Storm and RocketMQ
Realtime Statistics based on Apache Storm and RocketMQ
 
Nodejs性能分析优化和分布式设计探讨
Nodejs性能分析优化和分布式设计探讨Nodejs性能分析优化和分布式设计探讨
Nodejs性能分析优化和分布式设计探讨
 

Recently uploaded

Beyond the EU: DORA and NIS 2 Directive's Global Impact
Beyond the EU: DORA and NIS 2 Directive's Global ImpactBeyond the EU: DORA and NIS 2 Directive's Global Impact
Beyond the EU: DORA and NIS 2 Directive's Global Impact
PECB
 
Seal of Good Local Governance (SGLG) 2024Final.pptx
Seal of Good Local Governance (SGLG) 2024Final.pptxSeal of Good Local Governance (SGLG) 2024Final.pptx
Seal of Good Local Governance (SGLG) 2024Final.pptx
negromaestrong
 
1029 - Danh muc Sach Giao Khoa 10 . pdf
1029 -  Danh muc Sach Giao Khoa 10 . pdf1029 -  Danh muc Sach Giao Khoa 10 . pdf
1029 - Danh muc Sach Giao Khoa 10 . pdf
QucHHunhnh
 
Activity 01 - Artificial Culture (1).pdf
Activity 01 - Artificial Culture (1).pdfActivity 01 - Artificial Culture (1).pdf
Activity 01 - Artificial Culture (1).pdf
ciinovamais
 
Russian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in Delhi
Russian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in DelhiRussian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in Delhi
Russian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in Delhi
kauryashika82
 
An Overview of Mutual Funds Bcom Project.pdf
An Overview of Mutual Funds Bcom Project.pdfAn Overview of Mutual Funds Bcom Project.pdf
An Overview of Mutual Funds Bcom Project.pdf
SanaAli374401
 

Recently uploaded (20)

Ecological Succession. ( ECOSYSTEM, B. Pharmacy, 1st Year, Sem-II, Environmen...
Ecological Succession. ( ECOSYSTEM, B. Pharmacy, 1st Year, Sem-II, Environmen...Ecological Succession. ( ECOSYSTEM, B. Pharmacy, 1st Year, Sem-II, Environmen...
Ecological Succession. ( ECOSYSTEM, B. Pharmacy, 1st Year, Sem-II, Environmen...
 
Beyond the EU: DORA and NIS 2 Directive's Global Impact
Beyond the EU: DORA and NIS 2 Directive's Global ImpactBeyond the EU: DORA and NIS 2 Directive's Global Impact
Beyond the EU: DORA and NIS 2 Directive's Global Impact
 
Mehran University Newsletter Vol-X, Issue-I, 2024
Mehran University Newsletter Vol-X, Issue-I, 2024Mehran University Newsletter Vol-X, Issue-I, 2024
Mehran University Newsletter Vol-X, Issue-I, 2024
 
ICT Role in 21st Century Education & its Challenges.pptx
ICT Role in 21st Century Education & its Challenges.pptxICT Role in 21st Century Education & its Challenges.pptx
ICT Role in 21st Century Education & its Challenges.pptx
 
Unit-IV- Pharma. Marketing Channels.pptx
Unit-IV- Pharma. Marketing Channels.pptxUnit-IV- Pharma. Marketing Channels.pptx
Unit-IV- Pharma. Marketing Channels.pptx
 
psychiatric nursing HISTORY COLLECTION .docx
psychiatric  nursing HISTORY  COLLECTION  .docxpsychiatric  nursing HISTORY  COLLECTION  .docx
psychiatric nursing HISTORY COLLECTION .docx
 
SECOND SEMESTER TOPIC COVERAGE SY 2023-2024 Trends, Networks, and Critical Th...
SECOND SEMESTER TOPIC COVERAGE SY 2023-2024 Trends, Networks, and Critical Th...SECOND SEMESTER TOPIC COVERAGE SY 2023-2024 Trends, Networks, and Critical Th...
SECOND SEMESTER TOPIC COVERAGE SY 2023-2024 Trends, Networks, and Critical Th...
 
SOCIAL AND HISTORICAL CONTEXT - LFTVD.pptx
SOCIAL AND HISTORICAL CONTEXT - LFTVD.pptxSOCIAL AND HISTORICAL CONTEXT - LFTVD.pptx
SOCIAL AND HISTORICAL CONTEXT - LFTVD.pptx
 
Mattingly "AI & Prompt Design: Structured Data, Assistants, & RAG"
Mattingly "AI & Prompt Design: Structured Data, Assistants, & RAG"Mattingly "AI & Prompt Design: Structured Data, Assistants, & RAG"
Mattingly "AI & Prompt Design: Structured Data, Assistants, & RAG"
 
Advance Mobile Application Development class 07
Advance Mobile Application Development class 07Advance Mobile Application Development class 07
Advance Mobile Application Development class 07
 
Seal of Good Local Governance (SGLG) 2024Final.pptx
Seal of Good Local Governance (SGLG) 2024Final.pptxSeal of Good Local Governance (SGLG) 2024Final.pptx
Seal of Good Local Governance (SGLG) 2024Final.pptx
 
1029 - Danh muc Sach Giao Khoa 10 . pdf
1029 -  Danh muc Sach Giao Khoa 10 . pdf1029 -  Danh muc Sach Giao Khoa 10 . pdf
1029 - Danh muc Sach Giao Khoa 10 . pdf
 
Activity 01 - Artificial Culture (1).pdf
Activity 01 - Artificial Culture (1).pdfActivity 01 - Artificial Culture (1).pdf
Activity 01 - Artificial Culture (1).pdf
 
Accessible design: Minimum effort, maximum impact
Accessible design: Minimum effort, maximum impactAccessible design: Minimum effort, maximum impact
Accessible design: Minimum effort, maximum impact
 
Introduction to Nonprofit Accounting: The Basics
Introduction to Nonprofit Accounting: The BasicsIntroduction to Nonprofit Accounting: The Basics
Introduction to Nonprofit Accounting: The Basics
 
Holdier Curriculum Vitae (April 2024).pdf
Holdier Curriculum Vitae (April 2024).pdfHoldier Curriculum Vitae (April 2024).pdf
Holdier Curriculum Vitae (April 2024).pdf
 
Russian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in Delhi
Russian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in DelhiRussian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in Delhi
Russian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in Delhi
 
fourth grading exam for kindergarten in writing
fourth grading exam for kindergarten in writingfourth grading exam for kindergarten in writing
fourth grading exam for kindergarten in writing
 
An Overview of Mutual Funds Bcom Project.pdf
An Overview of Mutual Funds Bcom Project.pdfAn Overview of Mutual Funds Bcom Project.pdf
An Overview of Mutual Funds Bcom Project.pdf
 
Class 11th Physics NEET formula sheet pdf
Class 11th Physics NEET formula sheet pdfClass 11th Physics NEET formula sheet pdf
Class 11th Physics NEET formula sheet pdf
 

RxJava applied [JavaDay Kyiv 2016]

  • 1. RxJava Applied: Concise Examples where It Shines Igor Lozynskyi JavaDay Kyiv - Oct 14-15, 2016 Software Engineer at GlobalLogic
  • 3. Outline ● RxJava vs Java 8 Streams ● RxJava usage example
  • 4. Pre Java 8 data processing interface Iterator<T> { T next(); boolean hasNext(); void remove(); }
  • 5. Pre Java 8 data processing List<Employee> employees = service.getEmployees(); Map<Integer, List<Employee>> ageDistribution = new HashMap<>(); for (Employee employee : employees) { if (employee.getAge() > 25){ List<Employee> thisAge = ageDistribution.get(employee.getAge()); if (thisAge != null){ thisAge.add(employee); } else{ List<Employee> createThisAge = new ArrayList<>(); createThisAge.add(employee); ageDistribution.put(employee.getAge(), createThisAge); } } } System.out.println(ageDistribution);
  • 6. Java 8 Stream ... ● Connects data source and client ● Do not hold any data ● Implements map / filter / reduce pattern ● Enforces functional style of data processing
  • 7. Stream collectors List<Employee> employees = service.getEmployees(); Map<Integer, List<Employee>> ageDistribution = new HashMap<>(); for (Employee employee : employees) { if (employee.getAge() > 25) { List<Employee> thisAge = ageDistribution.get(employee.getAge()); if (thisAge != null){ thisAge.add(employee); } else { List<Employee> createThisAge = new ArrayList<>(); createThisAge.add(employee); ageDistribution.put(employee.getAge(), createThisAge); } } } System.out.println(ageDistribution);
  • 8. Stream collectors List<Employee> employees = service.getEmployees(); Map<Integer, List<Employee>> ageDistribution = employees.stream() .filter(e -> e.getAge() > 25) .collect(Collectors.groupingBy(Employee::getAge)); System.out.println(ageDistribution);
  • 9. Stream collectors List<Employee> employees = service.getEmployees(); Map<Integer, Long> ageDistribution = employees.stream() .filter(e -> e.getAge() > 25) .collect(Collectors.groupingBy( Employee::getAge, Collectors.counting() ));
  • 10. Stream sources Stream<String> stream1 = Arrays.asList("A", "B").stream(); Stream<String> stream2 = Stream.of("Q", "P", "R"); IntStream chars = "some text".chars(); Stream<String> words = Pattern.compile(" ").splitAsStream("some other text");
  • 11. Non-standard stream sources Can we use non-standard stream sources? ?
  • 12. Stream generator Stream .generate(() -> UUID.randomUUID().toString()) .limit(4) .forEach(System.out::println);
  • 13. Spliterator interface public interface Spliterator<T> { boolean tryAdvance(Consumer<? super T> action); Spliterator<T> trySplit(); long estimateSize(); int characteristics(); }
  • 14. class RandomSpliterator implements Spliterator<String> { public boolean tryAdvance(Consumer<? super String> action) { action.accept(UUID.randomUUID().toString()); return true; } // for parallel streams public Spliterator<String> trySplit() { return null; } public long estimateSize() { return Long.MAX_VALUE; }; public int characteristics() { return NONNULL | DISTINCT; } }
  • 15. Generate sequence: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, ... IntStream sequence = IntStream.rangeClosed(1, 50) .flatMap(i -> IntStream.iterate(i, identity()).limit(i) ); sequence.forEach(System.out::println);
  • 16. Stream API is powerful!
  • 17. Stream API - async processing getEmployeeIds().stream() .map(this::doHttpRequest) .collect(Collectors.toList()) Output: [main] processing request: c677c497 [main] processing request: 3b5320a9 [main] processing request: 9248b92e [main] processing request: 97a68a53
  • 18. Stream API - async processing getEmployeeIds().stream() .parallel() .map(this::doHttpRequest) .collect(Collectors.toList()) Output: [main] processing request: 7674da72 [ForkJoinPool.commonPool-worker-2] processing request: 747ae948 [ForkJoinPool.commonPool-worker-1] processing request: 33fe0bac [ForkJoinPool.commonPool-worker-3] processing request: 812f69f3 [main] processing request: 11dda466 [ForkJoinPool.commonPool-worker-2] processing request: 12e22a10 [ForkJoinPool.commonPool-worker-1] processing request: e2b324f9 [ForkJoinPool.commonPool-worker-3] processing request: 8f9f8a97
  • 19. Stream API - async processing ForkJoinPool forkJoinPool = new ForkJoinPool(80); forkJoinPool.submit(() -> getEmployeeIds().stream() .parallel() .map(this::doHttpRequest) .collect(Collectors.toList()) ).get();
  • 20. Stream API has some limitations
  • 21. Reactive Streams: what the difference
  • 23. Short history ● From 2009 for .NET ● From 2013 for JVM (latest: 1.2.1, Oct 5, 2016) ● Now a lot of other languages
  • 24. RxJava Observer interface Observer<T> { void onNext(T t); void onCompleted(); void onError(Throwable e); }
  • 25. RxJava Subscription interface Subscription { void unsubscribe(); boolean isUnsubscribed(); }
  • 26. Observable ● Central class in RxJava library ● It’s big: ~ 10k lines of code (with comments) ● It’s complex: ~ 100 static methods, ~ 150 non-static
  • 27. Subscription sub = Observable .create(s -> { s.onNext("A"); s.onNext("B"); s.onCompleted(); }) .subscribe(m -> log.info("Message received: " + m), e -> log.warning("Error: " + e.getMessage()), () -> log.info("Done!")); Output: Message received: A Message received: B Done!
  • 28. Observable<Integer> empty = Observable.empty(); Observable<Integer> never = Observable.never(); Observable<Integer> error = Observable.error(exception); Observable useful for tests never() - never emit anything (no data, no errors)
  • 29. public static <T, Resource> Observable<T> using( final Func0<Resource> resourceFactory, final Func1<Resource, Observable<T>> observableFactory, final Action1<Resource> disposeAction ){ } Observable from resource
  • 30. Observable .from(Arrays.asList(1, 2, 5, 7, 8, 12, 3, 6, 7, 8)) .filter(i -> (i > 3 && i < 8)) .forEach(System.out::println); Output: 5 7 6 7
  • 37. Time series Observable<Long> timer = Observable.timer(2, TimeUnit.SECONDS); Observable<Long> interval = Observable.interval(1, TimeUnit.SECONDS); timer.forEach(System.out::println); interval.forEach(System.out::println); Output: Process finished with exit code 0
  • 38. Time series Observable<Long> timer = Observable.timer(2, TimeUnit.SECONDS); Observable<Long> interval = Observable.interval(1, TimeUnit.SECONDS); timer.forEach(i -> System.out.println(currentThread().getName() + " - " + i)); interval.forEach(i -> System.out.println(currentThread().getName() + " - " + i)); Output: RxComputationThreadPool-2 - 0 RxComputationThreadPool-1 - 0 RxComputationThreadPool-2 - 1 Process finished with exit code 0 Thread.sleep(2000);
  • 39. Schedulers public final class Schedulers { public static Scheduler immediate() {...} public static Scheduler trampoline() {...} public static Scheduler newThread() {...} public static Scheduler computation() {...} public static Scheduler io() {...} public static TestScheduler test() {...} public static Scheduler from(Executor executor) {...} }
  • 40. Schedulers Observable.from("one", "two", "three", "four", "five") .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(/* an Observer */);
  • 42. Merge Observable<Integer> odds = Observable .just(1, 3, 5).subscribeOn(Schedulers.io()); Observable<Integer> evens = Observable.just(2, 4, 6); Observable.merge(odds, evens) .subscribe( System.out::println, System.err::println, () -> System.out.println("Finished"));
  • 44. Zip Observable<String> odds = Observable.just("A", "B", "C", "D"); Observable<Integer> evens = Observable.just(2, 4, 6); Observable.zip(odds, evens, (a, b) -> a + "-" + b) .subscribe( System.out::println, System.err::println, () -> System.out.println("Finished")); Output: A-2 B-4 C-6 Finished
  • 45. Cold & Hot Observables ● Cold Observable emits events only having subscriber ● Hot Observable emits events even without subscribers
  • 47. Backpressure Backpreasure is a way to slow down emission of elements It can act on observing side Strategies: ● Buffering items (buffer, window) ● Skipping items (sampling, throttling, etc) ● Request for specific number of elements
  • 48. Backpressure: request for elements public interface Producer { void request(long n); }
  • 49. Stream API vs RxJava RxJava: ● allows to process data in chosen thread, this is useful for IO, computations, specialized threads (GUI threads), ● allows synchronization on clocks and application events, ● works in push mode, producer initiates data transfer, but consumer may control data flow via backpressure. Stream API: ● tuned for hot data processing, ● good for parallel computation, ● has rich set of collectors for data.
  • 50. Shakespeare Plays Scrabble Benchmark (throughput) Non-Parallel Streams 44.995 ± 1.718 ms/op Parallel Streams 14.095 ± 0.616 ms/op RxJava 310.378 ± 9.688 ms/op RxJava (2.0.0-RC4) 156.334 ± 8.423 ms/op Performance Based on JMH benchmark by Jose Paumard
  • 51. Scenarios where RxJava shines ● Observables are better callbacks (easily wrap callbacks) ● Observables are highly composable (on contrast with callbacks) ● Provide async stream of data (on contrast with CompletableFuture) ● Observables can handle errors (have retry / backup strategies) ● Give complete control over running threads ● Good for managing IO rich application workflows ● Perfect for Android development (no Java 8 required, retrolambda compatible) ● Netflix uses RxJava for most their internal APIs
  • 53. Stream API and RxJava are friends! You can easily build Observable from Stream Iterator<T> iterator = ...; Observable<T> observable = Observable.from(() -> iterator); You can map Observable to Stream by implementing adapter public static<T> Iterator<T> of(Observable<? extends T> ob){ class Adapter implements Iterator<T>, Consumer<T> { ... } return new Adapter(); }
  • 54. External libraries that work with RxJava ● hystrix - latency and fault tolerance bulkheading library ● camel RX - to reuse Apache Camel components ● rxjava-http-tail - allows you to follow logs over HTTP ● mod-rxvertx - extension for VertX that provides support Rx ● rxjava-jdbc - use RxJava with JDBC to stream ResultSets ● rtree - immutable in-memory R-tree and R*-tree with RxJava ● and many more ...
  • 55. RxJava is not new JDeferred CompletableFuture<T> Scala.Rx Scala Actors Spring Integration
  • 56. What’s around? ● Google Agera - reactive streams for Android by Google ● Project Reactor - flow API implementation by Pivotal & Spring ● Akka-Streams - Akka based streams ● Monix - Scala based implementation of Reactive Streams ● Vert.x, Lagom - if you want more than streams
  • 57. Let’s build something with RxJava
  • 58. Use case: Stream of tweets Created with Balsamiq Mockups
  • 59. Twitter API Twitter Stream API (WebSocket alike): ● Doc: https://dev.twitter.com/streaming/overview ● Library: com.twitter:hbc-core:2.2.0 Twitter REST API: ● GET https://api.twitter.com/1.1/users/show.json?screen_name=jeeconf ● GET https://api.twitter.com/1.1/search/tweets.json?q=from:jeeconf
  • 60. Let’s look at entities class Tweet { String text; int favorite_count; String author; int author_followers; } class Profile { String screen_name; String name; String location; int statuses_count; int followers_count; } class UserWithTweet { Profile profile; Tweet tweet; }
  • 62. Profile getUserProfile(String screenName) { ObjectMapper om = new ObjectMapper(); return (Profile) om.readValue(om.readTree( Unirest.get(API_BASE_URL + "users/show.json") .queryString("screen_name", screenName) .header("Authorization", bearerAuth(authToken.get())) .asString() .getBody()), Profile.class); } Get user profile synchronously
  • 63. Get user profile asynchronously Observable<Profile> getUserProfile(String screenName) { return Observable.fromCallable(() -> { ObjectMapper om = new ObjectMapper(); return (Profile) om.readValue(om.readTree( Unirest.get(API_BASE_URL + "users/show.json") .queryString("screen_name", screenName) .header("Authorization", bearerAuth(authToken.get())) .asString() .getBody()), Profile.class); }); }
  • 64. Add some errors handling Observable<Profile> getUserProfile(String screenName) { if (authToken.isPresent()) { return Observable.fromCallable(() -> { ObjectMapper om = new ObjectMapper(); return (Profile) om.readValue(om.readTree( Unirest.get(API_BASE_URL + "users/show.json") .queryString("screen_name", screenName) .header("Authorization", bearerAuth(authToken.get())) .asString() .getBody()), Profile.class); }).doOnCompleted(() -> log("getUserProfile completed for: " + screenName)); } else { return Observable.error(new RuntimeException("Can not connect to twitter")); } }
  • 65. Let’s do something concurrently Illustration: Arsenal Firearms S.r.l.
  • 66.
  • 67. Get data concurrently Observable<UserWithTweet> getUserAndPopularTweet(String author){ return Observable.just(author) .flatMap(u -> { Observable<Profile> profile = client.getUserProfile(u) .subscribeOn(Schedulers.io()); Observable<Tweet> tweet = client.getUserRecentTweets(u) .defaultIfEmpty(null) .reduce((t1, t2) -> t1.retweet_count > t2.retweet_count ? t1 : t2) .subscribeOn(Schedulers.io()); return Observable.zip(profile, tweet, UserWithTweet::new); }); }
  • 68.
  • 69. Let’s subscribe on stream of tweets!
  • 72. streamClient.getStream("RxJava", "JavaDay", "Java", "Trump", "Hillary") .distinctUntilChanged() .map(p -> p.author) .flatMap(name -> getUserAndPopularTweet(name)) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.immediate()) .subscribe(p -> log.info("The most popular tweet of user " + p.profile.name + ": " + p.tweet)); .scan((u1, u2) -> u1.author_followers > u2.author_followers ? u1 : u2)
  • 73. streamClient.getStream("RxJava", "JavaDay", "Java", "Trump") .scan((u1, u2) -> u1.author_followers > u2.author_followers ? u1 : u2) .distinctUntilChanged() .map(p -> p.author) .flatMap(name -> { Observable<Profile> profile = client.getUserProfile(name) .subscribeOn(Schedulers.io()); Observable<Tweet> tweet = client.getUserRecentTweets(name) .defaultIfEmpty(null) .reduce((t1, t2) -> t1.retweet_count > t2.retweet_count ? t1 : t2) .subscribeOn(Schedulers.io()); return Observable.zip(profile, tweet, UserWithTweet::new); }) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.immediate()) .subscribe(p -> log.info("The most popular tweet of user " + p.profile.name + ": " + p.tweet));
  • 75. @Test public void correctlyJoinsHttpResults() throws Exception { String testUser = "testUser"; Profile profile = new Profile("u1", "Name", "USA", 10, 20, 30); Tweet tweet1 = new Tweet("text-1", 10, 20, testUser, 30); Tweet tweet2 = new Tweet("text-2", 40, 50, testUser, 30); TwitterClient client = mock(TwitterClient.class); when(client.getUserProfile(testUser)).thenReturn(Observable.just(profile)); when(client.getUserRecentTweets(testUser)).thenReturn(Observable.just(tweet1, tweet2)); TestSubscriber<UserWithTweet> testSubscriber = new TestSubscriber<>(); new Solutions().getUserAndPopularTweet(client, testUser).subscribe(testSubscriber); testSubscriber.awaitTerminalEvent(); assertEquals(singletonList(new UserWithTweet(profile, tweet2)), testSubscriber.getOnNextEvents()); }
  • 77. ● API is big (150+ methods to remember) ● Requires to understand underlying magic ● Hard to debug ● Don’t forget about back pressure Conclusions: pitfalls
  • 78. ● It is functional, it is reactive* ● Good for integration scenarios ● Allows to control execution threads ● Easy to compose workflows ● Easy to integrate into existing solutions ● Easy Ok to test * RxJava is inspired by FRP (Functional Reactive Programming), but doesn’t implement it Conclusions: strength
  • 79. ● RxJava 2.0 in few weeks! ● RxJava 2.0 is better and faster ● RxJava 2.0 is Java 9 Flow API compatible ● We will see more and more reactive streams Conclusions: future
  • 81. Thanks Presentation template by SlidesCarnival @siromaha aigooor@gmail.com