Before we dive deep into the reactive world of RxJava, here is a quick immersion to get your feet wet first. In ReactiveX, the core type you will work with is the Observable class. We will be learning more about the Observable class throughout the rest of this book. But essentially, an Observable pushes things. A given Observable<T> pushes things of type T through a series of operators until it arrives at an Observer object that consumes the items.
For instance, create a new Ch1_1.java file in your project and put in the following code:
import io.reactivex.rxjava3.core.Observable;
public class Ch1_1 {
public static void main(String[] args) {
Observable<String> myStrings =
Observable.just("Alpha", "Beta", "Gamma");
}
}
In our main() method, we have an Observable<String> that will push three string objects. An Observable can push data or events from virtually any source, whether it is a database query or live Twitter feeds. In this case, we are quickly creating an Observable using Observable.just(), which will emit a fixed set of items.
However, running this main() method is not going to do anything other than declare Observable<String>. To make this Observable actually push (or emit) these three strings, we need an Observer object to subscribe to it and receive the items. We can quickly create and connect an Observer object by passing a lambda expression that specifies what to do with each value it receives:
import io.reactivex.rxjava3.core.Observable;
public class Ch1_1 {
public static void main(String[] args) {
Observable<String> myStrings =
Observable.just("Alpha", "Beta", "Gamma");
myStrings.subscribe(s -> System.out.println(s));
}
}
When we run this code, we should get the following output:
Alpha
Beta
Gamma
What happened here is that our Observable<String> pushed each string object one at a time to our Observer object, which we shorthanded using the s -> System.out.println(s) lambda expression. We passed each string through the (arbitrarily named) s parameter and instructed it to print each one. Lambda expressions are essentially mini-functions that allow us to quickly pass instructions on what action to take with each incoming item. Everything to the left of the arrow (->) are arguments (which, in this case, is a string we named s), and everything to the right is the action (which is System.out.println(s)).
If you are unfamiliar with lambda expressions, turn to Appendix A, Introducing Lambda Expressions, to learn more about how they work. If you want to invest extra time in understanding lambda expressions, I highly recommend that you read at least the first few chapters of Java 8 Lambdas (O'Reilly) (http://shop.oreilly.com/product/0636920030713.do), by Richard Warburton. Lambda expressions are a critical topic in modern programming and have become especially relevant to Java developers since their adoption in Java 8. We will be using lambdas constantly in this book, so definitely take some time to get comfortable with them.
We can also use several operators in the pipeline between Observable and Observer to transform each pushed item or manipulate it in some way. Each such operator applies the transformation and returns a new Observable that emits the transformed item. For example, we can use map() to turn each string emission into its length(), and each length integer will then be pushed to Observer, as shown in the following code snippet:
import io.reactivex.rxjava3.core.Observable;
public class Ch1_2 {
public static void main(String[] args) {
Observable<String> myStrings =
Observable.just("Alpha", "Beta", "Gamma");
myStrings.map(s -> s.length())
.subscribe(s -> System.out.println(s));
}
}
When we run this code, we should get the following output:
5
4
5
If you have used Java 8 streams or Kotlin sequences, you might be wondering how Observable is any different. The key difference is that Observable pushes the items, while the streams and sequences pull the items. This may seem subtle, but the impact of a push-based iteration is far more powerful than a pull-based one. As we saw earlier, you can push not only data but also events. For instance, Observable.interval() will push a consecutive Long at each specified time interval, as shown in the following code snippet. This Long emission is not only data but also an event! Let's take a look:
import io.reactivex.rxjava3.core.Observable;
import java.util.concurrent.TimeUnit;
public class Ch1_3 {
public static void main(String[] args) {
Observable<Long> secondIntervals =
Observable.interval(1, TimeUnit.SECONDS);
secondIntervals.subscribe(s -> System.out.println(s));
/* Hold main thread for 5 seconds
so Observable above has chance to fire */
sleep(5000);
}
public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
When we run this code, we should get the following output:
0
1
2
3
4
Notice that a consecutive emission fires every second. This application runs for about 5 seconds before it quits, and you likely see emissions 0 to 4 fired, each separated by a just a second-long gap. This simple idea that data is a series of events over time unlocks new possibilities in programming.
As a side note, we will get more into concurrency later, but we had to create a sleep() method because this Observable fires emissions on a computation thread when the Observable is subscribed to. The main thread used to launch our application is not going to wait on this Observable since it fires on a computation thread, not the main thread. Therefore, we use sleep() to pause the main thread for 5,000 milliseconds and then allow it to reach the end of the main() method (which will cause the application to terminate). This gives Observable.interval() a chance to fire for the 5-second window before the application quits.
Throughout this book, we will uncover many mysteries about Observable and the powerful abstractions it takes care of for us. If you've conceptually understood what's been going on here so far, congrats! You are already becoming familiar with how reactive code works. To emphasize again, emissions are pushed one at a time, all the way to Observer. Emissions represent both data and an event, which can be emitted over time. Of course, beyond map(), there are hundreds of operators in RxJava, and we will learn about the key ones in this book. Learning which operators to use for a situation and how to combine them is the key to mastering RxJava. In the next chapter, we will cover Observable and Observer much more comprehensively. We will also demystify how events and data are being represented in Observable a bit more.