What do we want to achieve?
In the last part of this series on Functional Reactive with Core Java, we have done the initial conversions and seen, how the source code changes when we use a few functional elements and aspects. But the question, starting from when do we talk about functional development, still remains open. Let’s start with some examples just for this.
In addition to the source text examples given in this article, I will also use the sources from the Open Source project
Functional-Reactive http://www.functional-reactive.org/. The sources are available at https://github.com/functional-reactive/functional-reactive-lib
FunctionalInterfaces and Lambdas
We have got the FunctionalInterfaces with Java 8, which have then been enhanced in Java 9. This is a frequently used technical method for implementing functional elements in Java.
A few minor things about interfaces and changes in Java 9 here as a repetition once again.
We have an interface with two methods. One of these is static doMore()
, the other doSomething()
has a default implementation.
public interface DemoInterface {
static void doMore() {
System.out.println(DemoInterface.class.getSimpleName() + " : " + "doMore");
}
default void doSomething() {
System.out.println(DemoInterface.class.getSimpleName() + " : " + "doSomething");
}
}
We now create an interface with the name InterfaceA
and inherit from DemoInterface
. Here, we overwrite the method doSomething()
public interface InterfaceA extends DemoInterface {
default void doSomething() {
System.out.println(InterfaceA.class.getSimpleName() + " : " + "doSomething");
}
}
Lastly, we create an interface with the name InterfaceB
, which inherits not only from the interface DemoInterface
, but also has a method with the name doSomething
and a default implementation.
If we now write the following source code and execute it subsequently, what kind of error messages do we get on the screen?
public static void main(String[] args) {
DemoInterface.doMore();
new ImplA().doSomething();
ImplA.doMore();
new ImplB().doSomething();
}
The first thing that happens is that the compiling aborts. The line ImplA.doMore()
is responded by the complier with a cannot find symbol doMore()
. Therefore, static methods can be called only on the original interface. Unfortunately, one forgets this again and again and notices it only later in the implementation. Therefore, if we comment out this line, we can execute the source code and get the following output.
DemoInterface : doMore
InterfaceA : doSomething
ImplB : doSomething
Hence, always the last implementation from the inheritance chain is used. In the case of InterfaceB
, the compiler cannot itself decide, which implementation is the dominant of the two. The developer must take a decision himself here by implementing the same.
In Java 9, we can now implement or declare additional methods with the visibility private
. A pure declaration is not possible, because even in inheriting interfaces this cannot be equipped with an implementation. But now we are again in a position to divide bigger default implementations cleanly in several methods within an interface. In Java 8, this must either be outsourced or else it leads to ugly source code fragments.
We now come to the FunctionalInterfaces. The definition of functional interfaces in Java 9 is still the same as in Java 8. It is called a functional interface, if a method to be implemented is still available. The number of methods with default implementation has no influence here as also, whether the annotation @FunctionalInterface
is present or not.
Lambdas
I will now discuss here all the possible forms. Only to the extent that wherever a functional interface can be specified as an attribute, a lambda can also be used. The use of lambdas is often equated with functional programming. But is it really functional programming?
Let us have a look at the following example.
public class Main {
@FunctionalInterface
public static interface Service {
public Integer doWork(String value);
}
public static class ServiceImpl implements Service {
@Override
public Integer doWork(String value) {
return Integer.valueOf(value);
}
}
public static void main(String[] args) {
Integer integer = new ServiceImpl().doWork("1");
Service serviceA = (value)-> { return Integer.valueOf(value);};
Service serviceB = Integer::valueOf;
serviceA.doWork("1");
serviceB.doWork("1");
}
}
We have here an interface with the name Service
, which is implemented once in the classical way using the class ServiceImpl
. The usage is now done as usual in Java by generating an instance of the class, in order to call the following method.
But even both the other implementations can be used as usual by means of lambda and subsequently with the help of method reference. Only here we have a somewhat different life cycle in case of initialization and there can be no class attributes (state), as in the case of the first classical implementation. One can include this in the area of functional aspects. That is, value in, value out and no state that can spoil anything.
I am intentionally leaving out here the differences between lambda and method reference.
It can now happen that the method is called with the input value HoppelPoppel. What should now be the response of the method? Either an exception or a zero? Both are against the fundamental considerations of the functional programming.
Optional
Let us have a look here at the class Optional<T>
available since Java 8. It helps in returning a reference even when the final value is not available. Instead of zero or the value (the instance of the desired type), one now gets an instance of the class Optional.
To do this, we rewrite the interface as follows. I am leaving out here the classical implementation in the listing. It can be found in the git repository, package v003.
@FunctionalInterface
public static interface Service {
public Optional<Integer> doWork(String value);
}
public static class ServiceImpl implements Service {
@Override
public Optional<Integer> doWork(String value) {
try {
return Optional.of(Integer.valueOf(value));
} catch (NumberFormatException e) {
e.printStackTrace();
}
return Optional.empty();
}
}
public static void main(String[] args) {
Optional<Integer> integer = new ServiceImpl().doWork("1");
Service serviceA = (value) -> {
try {
return Optional.of(Integer.valueOf(value));
} catch (NumberFormatException e) {
e.printStackTrace();
}
return Optional.empty();
};
//SNIPP usage of the implementations
}
In this case, therefore, always values are specified, or else zero is never used as return value. Even no value from the value reserve of the target quantity, here integer, has been used to signal an error. The runtime exception need no longer be intercepted during usage. Hence, we have now essentially written a more robust piece of source code. If we now have a look at the use of the instance Optional, we come one step further in the direction of functional aspects. Here, as in the last part of the series, we can now rewrite the method in a Function<String, Optional<Integer>>
and implement this as the return value of a static method.
@FunctionalInterface
public static interface Service {
public Optional<Integer> doWork(String value);
}
static Function<String, Optional<Integer>> service() {
return (value) -> {
try {
return Optional.of(Integer.valueOf(value));
} catch (NumberFormatException e) {
e.printStackTrace();
}
return Optional.empty();
};
}
public static void main(String[] args) {
Optional<Integer> apply = service().apply("1");
}
Again, we now have a function, but the reference to the functional instance Service
has been lost. It is now possible here again, if needed, to correct it by means of inheritance in the functional name range Service
.
public class Main {
@FunctionalInterface
public static interface Service extends Function<String, Optional<Integer>> {
public default Optional<Integer> doWork(String value) {
return apply(value);
}
}
static Service service() {
return (value) -> {
try {
return Optional.of(Integer.valueOf(value));
} catch (NumberFormatException e) {
e.printStackTrace();
}
return Optional.empty();
};
}
public static void main(String[] args) {
Optional<Integer> applyA = service().apply("1");
Optional<Integer> applyB = service().doWork("1");
}
}
It is to be noted here that the method is delegated to the method apply by means of default implementation and, as a result, we again get a functional interface. The return value can now be processed in a different way.
Optional and its fine points
We now have an Optional, in which an integer is packed. What now? In order to find out the possibilities, we will go through the features offered to us by an Optional.
Optional<Integer> apply = service().doWork("1");
final Integer intA = apply.get();
//if(apply.isPresent()){
if (intA != null) {
//do something useful
}
else {
//do something ???
}
We can naturally remove the contained value simply from the Optional. However, if this is zero, then a NoSuchElementException
is thrown. We must enquire the Optional itself by using the method isPresent()
, whether a value is contained.
Here the original implementation from JDK.
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
However, the aim is to replace these direct control structures by functional aspects.
final Integer intB1 = apply.orElseGet(() -> - 1);
final Integer intB2 = apply.orElse(- 1);
final Integer intB3 = apply.orElseThrow(() -> new RuntimeException("panic ;-)"));
An Optional can also be equipped with alternative values, or else can throw an exception, if no value is present. However, the latter is to be used with caution, since we are confronted with this only at runtime.
Now a connection can be made with an action only when a value is present.
apply.ifPresent(v -> {
System.out.println("v = " + v);
});
When using Java 8, it is quickly noticed that there are some discrepancies here. For instance, there is no possibility of defining one more action when no value is present, in addition to an action if a value is present. This has been enhanced in Java 9.
//since 9
apply.ifPresentOrElse(v -> {
System.out.println("v = " + v);
} , () -> {
System.out.println("value not present");
});
Similarly, the combination ifPresent()
and get()
is often used. For this reason, there is now in Java 9 a stream()
, which returns a Stream<T>
from the Optional<T>
.
final Stream<Integer> stream = apply.stream();
And in the same way, a possibility of an alternative has been included. But this time not for the value, but as Optional. However, the type T cannot be changed in doing so. The alternative is returned, when the method isPresent()
returns false.
apply.or(() -> Optional.of(Integer.MAX_VALUE));
Summary
If one summarizes here everything, then it results in different starting points, which one can use directly in the daily routine. For instance, one can equip the APIs with the Optional, which one counteracts at the point of the NullPointerException
. The use of Optional<T>
also causes that one can formulate the control structures in a different way. No imperative case differentiations are needed, which definitively goes in the direction of functional development.
But if one rethinks it a little, one can then work a bit differently with the method map()
in case of an Optional.
String value = "";
service()
.doWork(value)
.or(() -> Optional.of(0)) // per definition
.map((Function<Integer, Function<Integer, String>>) integer
-> (valueToAdd) -> integer + valueToAdd + " was calculated")
.ifPresentOrElse(
fkt -> System.out.println("f(10) ==> = " + fkt.apply(10)),
() -> System.out.println("value not present (usless here)"));
We will see later what one can do with this and how we can bypass the discrepancies of the Optional in Java 8 also in Java 9.
You can find the source code at:
https://github.com/Java-Publications/functional-reactive-with-core-java-002.git
If you have questions or comments, simply contact me at sven@vaadin.com or via Twitter @SvenRuppert.
Happy coding!