Whenever a new version of the JDK is released, some people say “I won’t upgrade, I’ll keep using Java 8”, but many people at work are still not very good at using the new features of Java8, and these features often make Java less “bloated”. However, I personally think that the most representative of all the new features of Java8 must be functional programming. Some may say that this style is too abstract and difficult to understand, but when you master this setting, you will feel great. Slowly you will also appreciate the charm and essence of functional programming. Today, we introduce a functional Java toolkit that exhibits many excellent functional programming ideas. The previously introduced alternative to the fusion downgrade component Hystrix, resilience4j, is based on the vavr library.

Vavr

Vavr is a Java8 function library that uses a large number of functional programming paradigms . Creatively encapsulates some persistent data structures and functional control structures. And there are many useful programming ideas to learn from it.

Observable side effects

There are often invisible traps in our code that are unobservable from the code semantics. For example.

1
2
3
int divide(int a, int b){
 return a/b;
}

We know that a/b will give us an integer, but it is not clear from the code that if b=0 an java.lang.ArithmeticException will be thrown; whereas if it is a+b it will not give any side effects. So we need to make this side effect observable. For this Vavr has made a design.

1
2
3
Try<Integer> divide(Integer a, Integer b) {
    return Try.of(() -> a / b);
}

Encapsulating possible side effects in a container makes it clear what can fail, and when you see that Try<Integer> is returned, it means that the result may not be “good” so that you can target it for prevention.

Immutable data structures

Many languages use immutable data structures, such as Golang and Kotlin, mainly because immutable values have the following properties.

  • are intrinsically thread-safe and therefore do not require synchronization
  • is reliable for equals and hashCode
  • does not require cloning
  • is type-safe in unchecked unchecked type conversions
  • immutable values are the most transparent for functional programming

For this reason Vavr has designed a collection class library designed to replace the collection framework in Java.Vavr’s collection library contains a rich set of functional data structures built on top of lambdas. The only interface they share with Java’s original collections is Iterable . These data structures are persistent, and once initialized are inherently immutable; you can use some operations to return a changed copy. An example is the classic data structure of a one-way linked table.

1
2
// 1   2  3
List<Integer> source = List.of(1, 2, 3);

java

If we put a new element 0 in front of the end of the original chain.

1
2
3
4
//  0  2  3
List<Integer> newHeadList = source.tail().prepend(0);
//  1  2  3
System.out.println(source);

sobyte

The original chain remains unchanged, and the new chain is replaced with elements of the same size. Of course you can use other APIs to generate a copy of the changed size, but you can be sure that the original linked table will not change.

1
2
3
4
// 0 1 2 3
List<Integer> prepend = source.prepend(0);
// 1 2 3 0
List<Integer> append = source.append(0);

This is just part of the programming ideas, next I will introduce some of the features of Vavr.

Some of Vavr’s features

Vavr provides some very useful and distinctive APIs.

Tuple

Anyone familiar with Python will be familiar with tuples. A tuple groups a fixed number of elements together so that they can be passed as a whole. Unlike arrays or lists, a tuple can contain different types of objects, but it is also immutable . Vavr currently provides a tuple structure with up to 8 elements.

1
2
3
4
5
6
// (felord.cn, 22)
Tuple2<String, Integer> java8 = Tuple.of("felord.cn", 22); 
// felord.cn
String s = java8._1; 
// 22
Integer i = java8._2;

This can be used to simulate the multiple return value feature that is not available in Java.

Function

Java itself provides the Function interface, but Vavr provides richer Function extensions, such as the ability to combine multiple Functions.

1
2
3
4
Function1<Integer, Integer> multiplyByTwo = a -> a * 2;
Function1<Integer, Integer> compose = multiplyByTwo.compose(a -> a + 1);
// 6
Integer apply = compose.apply(2);

In addition to this, it is possible to allow potential side effects to be downgraded (lift), somewhat similar to microservice fusion, to avoid handling exceptions during function execution.

1
2
3
4
5
6
7
8
Function2<Integer, Integer, Integer> divide = (a, b) -> a / b;
// 降级 
Function2<Integer, Integer, Option<Integer>> safeDivide = Function2.lift(divide);
// 返回一个加强版的Optional
Option<Integer> apply = safeDivide.apply(1, 0);
boolean empty = apply.isEmpty();
// true
System.out.println(empty);

There are also derivative operations.

1
2
3
 Function2<Integer, Integer, Integer> divide = (a, b) -> a / b;
 Function1<Integer, Integer> a = divide.apply(4);
 Integer apply = a.apply(2);

This is somewhat analogous to collinearization, which only becomes more apparent when we use more inclusions.

1
2
3
Function3<Integer, Integer, Integer, Integer> sum = (a, b, c) -> a + b + c;
final Function1<Integer, Function1<Integer, Integer>> add2 = sum.curried().apply(1);
Integer apply = add2.apply(2).apply(3);

Guess what the answer is?

Value containers with properties

Vavr provides a number of value containers with unique properties, such as the Try mentioned at the beginning, to explicitly indicate that an exception may be encountered.

Option

Similar to Optional, but more powerful than Optional.

Lazy

Lazy is a container for inert computation, indicating that it will be computed when used and only once.

1
2
3
4
5
6
7
Lazy<Double> lazy = Lazy.of(Math::random);
lazy.isEvaluated(); // = false
lazy.get();         // = 0.123  
lazy.isEvaluated(); // = true
lazy.get();         // = 0.123 
// 需要使用数据时才从数据源加载
Data lazyData = Lazy.val(DataSourceService::get, Data.class);

There are some other very useful containers, you can try them.

Pattern matching

Most functional programming languages support pattern matching, which is available in Scala, the same JVM language, but not in Java at the moment. It can be useful to help us reduce if-else, as an example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
   public static String convert(int input) {

        String output;
        if (input == 1) {
            output = "one";
        } else if (input == 2) {
            output = "two";
        } else if (input == 3) {
            output = "three";
        } else {
            output = "unknown";
        }
        return output;
    }

Does it feel a bit confusing? Vavr is much more refreshing.

1
2
3
4
5
6
7
8
    public static String vavrMatch(int input) {
        return Match(input).of(
                Case($(1), "one"),
                Case($(2), "two"),
                Case($(3), "three"),
                Case($(), "unknown")
        );
    }

Of course there are other ways to play that you need to discover for yourself.

Summary

Functional programming, one of the biggest highlights of Java8 (in my opinion), is not easy for developers who are used to traditional OOP programming to accept. You may want to start with the Vavr class library to learn the idea of functional programming. Today’s introduction is only a very small part of it, there are more waiting for you to discover, to learn from. I forgot to mention that if you want to use it in your project, you can import this coordinate below.

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/io.vavr/vavr -->
<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr</artifactId>
    <version>0.10.3</version>
</dependency>