Functional programming is not a new idea; actually, it's pretty old. For example, Lisp, which is a functional language, is the second oldest of today's commonly-used programming languages.
Functional programs are built using small pieces of reusable pure functions (lambdas). The program logic is composed of small declarative steps and not complex algorithms. That's because functional programs minimize the use of state, which makes imperative programs complex and hard to refactor/support.
With Java 8, the Java world got the lambda expressions and the ability to pass functions to functions. With them, we can code in a more functional style and get rid of a lot of the boilerplate code. The other new thing we got with Java 8 is the streams—something very similar to RxJava's observables but not asynchronous. Combining these streams and the lambdas, we are able to create more functional-like programs.
We are going to familiarize ourselves with these new constructions and look at how they can be used with RxJava's abstractions. Our programs will be simpler and easier to follow by using the lambdas, and the concepts introduced in this chapter will be of help while designing applications.
This chapter covers:
The most important change in Java 8 is the introduction of lambda expressions. They enable faster, clearer coding and make it possible to use functional programming.
Java was created back in the '90s as an object-oriented programming language, with the idea that everything should be an object. At that time, object-oriented programming was the principal paradigm for software development. But, recently, functional programming has become increasingly popular because it is well-suited for concurrent and event-driven programming. This doesn't mean that we should stop writing code using object-oriented languages. Instead, the best strategy is to mix elements of object-oriented and functional programming. Adding lambdas to Java 8 ties in with this idea—Java is an object-oriented language, but now it has lambdas, we are able to code using the functional style too.
Let's look at this new feature in detail.
In order to introduce lambda expressions, we need to see their actual value. This is why this chapter will begin with one example implemented without using lambda expressions, followed by re-implementing the same example using lambda expressions.
Remember the map(Func1)
method from the Observable
class? Let's try to implement something similar for the java.util.List
collections. Of course, Java doesn't support adding methods to existing classes, so the implementation will be a static method that takes a list and transformation and returns a new list containing the transformed elements. In order to pass a transformation to the method, we'll need an interface with one method representing it.
Let's look at the code:
interface Mapper<V, M> { // (1) M map(V value); // (2) } // (3) public static <V, M> List<M> map(List<V> list, Mapper<V, M> mapper) { List<M> mapped = new ArrayList<M>(list.size()); // (4) for (V v : list) { mapped.add(mapper.map(v)); // (5) } return mapped; // (6) }
What is happening here?
Mapper
.M map(V)
, that receives a value of type V
and transforms it to a value of type M
.List<M> map(List<V>, Mapper<V, M>)
takes one list with elements of type V
and a Mapper
implementation. Using this Mapper
implementation's map()
method on every element of the source list, it converts the list to a new list of type M
containing the transformed elements.M
with the same size as the source list.Mapper
implementation and added to the new list.In this implementation, every time we want to create a new list by transforming another, we will have to implement the Mapper
interface with the right transformation. Until Java 8, the right way of passing custom logic to methods was exactly like this—with anonymous class instances, implementing the given methods.
But let's look at how we use this List<M> map(List<V>, Mapper<V, M>)
method:
List<Integer> mapped = map(numbers, new Mapper<Integer, Integer>() {
@Override
public Integer map(Integer value) {
return value * value; // actual mapping
}
});
In order to apply a mapping to a list, we need to write four lines of boilerplate code. The actual mapping is very simple and is only one of these lines. The real problem here is that instead of passing an action, we are passing an object. This obscures the real intention of this program—to pass an action that produces transformation from every item of the source list and to get a list with applied changes at the end.
Here is what this call looks like using the new lambda syntax of Java 8:
List<Integer> mapped = map(numbers, value -> value * value);
Pretty straight forward, isn't it? And it just works. Instead of passing an object and implementing an interface, we pass a block of code, a nameless function.
What is going on? We defined an arbitrary interface with an arbitrary method, but we could pass this lambda in place of an instance of the interface. In Java 8, if you define interface with only one abstract method and you create a method that receives a parameter of this type of interface, you can pass a lambda instead. If the interface single method takes two arguments of type string and returns an integer value, the lambda will have to be composed of two arguments before the ->
and to return integer, the arguments will be inferred as strings.
Interfaces of this type are called functional interfaces. It is important for the single method to be abstract and not default. Another new thing in Java 8 is the default methods of interfaces:
interface Program {
default String fromChapter() {
return "Two";
}
}
The default methods are useful when changing already existing interfaces. When we add default methods to them, the classes implementing them won't break. An interface with only one default method is not functional; a single method shouldn't be default.
Lambdas act as implementations of the functional interfaces. So, it is possible to assign them to variables of type interface as follows:
Mapper<Integer, Integer> square = (value) -> value * value;
And we can reuse the square object as it's an implementation of the Mapper
interface.
Maybe you've noticed, but in the examples up until now, the parameters of lambda expressions have no type. That is because the types are inferred. So this expression is absolutely the same as the preceding expression:
Mapper<Integer, Integer> square = (Integer value) -> value * value;
The fact that the example with a parameter without a type works is not magic. Java is a statically typed language, so the parameter of the single method of the functional interface is used for type checking.
How about the body of the lambda expression? There is no return
statement anywhere. It turns out that these two examples are exactly the same:
Mapper<Integer, Integer> square = (value) -> value * value; // and Mapper<Integer, Integer> square = (value) -> { return value * value; };
The first expression is just a short form of the second. It is preferred for the lambda to be only one line of code. But if the lambda expression contains more than one line, the only way to define it is using the second approach, like this:
Mapper<Integer, Integer> square = (value) -> { System.out.println("Calculating the square of " + value); return value * value; };
Under the hood, lambda expressions are not just syntax sugar for anonymous inner classes. They are implemented to perform quickly inside the Java Virtual Machine (JVM), so if your code is designed to be compatible only with Java 8+, you should definitely use them. Their main idea is to pass around behavior in the same way that data is passed. This makes your program more human readable.
One last thing related to the new syntax is the ability to pass to methods and assign to variables already defined functions and methods. Let's define a new functional interface:
interface Action<V> { void act(V value); }
We can use it to execute arbitrary actions for each value in a list; for example, logging the list. Here is a method that uses this interface:
public static <V> void act(List<V> list, Action<V> action) { for (V v : list) { action.act(v); } }
This method is similar to the map()
function. It iterates through the list and calls the passed action's act()
method on every element. Let's call it using a lambda that simply logs the elements in the list:
act(list, value -> System.out.println(value));
This is quite simple but not necessary because the println()
method can be passed itself to the act()
method. This is done as follows:
act(list, System.out::println);
The code for these examples can be viewed/downloaded at https://github.com/meddle0x53/learning-rxjava/blob/master/src/main/java/com/packtpub/reactive/chapter02/Java8LambdasSyntaxIntroduction.java.
This is valid syntax in Java 8—every method can become a lambda and can be assigned to a variable or passed to a method. All these are valid:
Now that we've revealed the lambda syntax, we will be using it in our RxJava examples instead of anonymous inner classes.
Java 8 comes with a special package containing functional interfaces for common cases. This package is java.util.function
, and we are not going to look at it in detail in this book, but will present some of them that are worth mentioning:
Consumer<T>
: This represents a function that accepts an argument and returns nothing. Its abstract method is void accept(T)
. As an example, we can use it to assign the System.out::println
method to a variable, as follows:Consumer<String> print = System.out::println;
Function<T,R>
: This represents a function that accepts one argument of a given type and returns a result of an arbitrary type. Its abstract method is R accept(T)
, and it can be used for mapping. We don't need the Mapper
interface at all! Let's take a look at the following code snippet:Function<Integer, String> toStr = (value) -> (value + "!"); List<String> string = map(integers, toStr);
Predicate<T>
: This stands for a function with only one argument that returns a Boolean result. Its abstract method is boolean test(T)
and it can be used for filtering. Let's take a look at the following code:Predicate<Integer> odd = (value) -> value % 2 != 0;
There are a lot of functional interfaces similar to these; for example, a function with two arguments, or a binary operator. This is again a function with two arguments, but both of the same type and returning a result with the same type. They are there to help reuse lambdas in our code.
The good thing is that RxJava is lambda compatible. This means that the actions we were passing to the subscribe
method are in fact functional interfaces!
RxJava's functional interfaces are in the rx.functions
package. All of them extend a base marker
interface (interface with no methods, used for type checking), called Function
. Additionally, there is another marker interface, extending the Function
one, called Action
. It is used to mark consumers (functions, returning nothing).
RxJava has eleven Action
interfaces:
Action0 // Action with no parameters Action1<T1> // Action with one parameter Action2<T1,T2> // Action with two parameters Action9<T1,T2,T3,T4,T5,T6,T7,T8,T9> // Action with nine parameters ActionN // Action with arbitrary number of parameters
They can be used mainly for subscriptions (Action1
and Action0
). The Observable.OnSubscribe<T>
parameter, which we saw in Chapter 1, An Introduction to Reactive Programming, (used for creating custom observables) extends the Action
interface too.
Analogically, there are eleven Function
extenders representing function returning result. They are Func0<R>
, Func1<T1, R>
… Func9<T1,T2,T3,T4,T5,T6,T7,T8,T9,R>
, and FuncN<R>
. They are used for mapping, filtering, combining, and many other purposes.
Every operator and subscribe method in RxJava is applicable to one or more of these interfaces. This means that we can use lambda expressions instead of anonymous inner classes in RxJava almost everywhere. From this point on, all our examples will use lambdas in order to be more readable and somewhat functional.
Now, let's look at one big RxJava example implemented with lambdas. This is our familiar Reactive Sum example!
3.137.220.92