Spring Boot integrated vavr quick start demo
1. What is VAVR?
When I first heard about VAVR, I felt very strange, why did I see the name laterOfficial websiteI was silent, suspicious of the start-up team’s paid information, how to choose the name of the UC shock department, good guys, vavr is java, these four words are reversed, it is really ‘subverted’ java…..

Screenshot of the official website

After the screenshot of the official website is upside downNext, I will introduce some simple features of VAVR, in order to avoid becoming a translation of the official documentation, I will refine it and add some demos, and will not go into the details of the source code, but focus on use.If you don’t know what VAVR is for, here are three important ‘subversions’ that I think are important for this library:
- VAVR provides an enhanced function interface (which provides a more powerful and convenient interface than the JDK comes with).
- Provides a number of features (methods) that depend on functional interfaces.
- Provides a collection library that is close to Scala (immutable collections that conform to the characteristics of functional programming).
2. Introduction to VAVR knowledge points
Enhancements to the Function interface
jdk’s own functional interface was introduced in the first part, in fact, both the requirements and the function are a little weak, Function remember, the abstract method of Function is apply, and its function function is to pass in a type to convert it into another type. So what if I want to pass in two different types and turn them into a third type, if I’ve seen itjava.util.function
After the package, you will definitely say that there is BiFunction, what about three, what about four, that is not going to be extended by yourself.
Fcuntion(0….8) interface
vavr provides us with more functions that can be extended, such as the Function class, which provides Function0 to Function8, which can accept up to 8 parameters. For example, one of the stitches shown below:
@Test
public void multiFunctionTest() {
Function4<String, String, Boolean, Integer, String> func =
(country, name, isMan, score) -> String.format("%s-%s-%s-%d", country, name, isMan ? "man" : "female", score);
System.out.println(func.apply("china", "xiaoming", true, 10));
}
// chian-xiaoming-man-10
More functional features
vavr also has enhancements to functions, in addition to andThen() and compose() which are also available in the jdk. The interface of vavr also has true closure features for function programming, such as coryization, lifting, memoization, etc., which are described one by one below
Composition
This JDK actually comes with its own, which is actually the concept of composite functions in mathematics, and the y of f(x) can be the x of g(x).g(f(x))
。 There are two ways that can be done, one isandThen(), one is onecompose(), demo to know
@Test
public void andThenTest() {
Function4<String, String, Boolean, Integer, String> func1 =
(country, name, isMan, score) -> String.format("%s-%s-%s-%d", country, name, isMan ? "man" : "female", score);
Function4<String, String, Boolean, Integer, String> func2 = func1
.andThen(str -> String.join(":", StrUtil.split(str, '-')));
System.out.println(func2.apply("china", "xiaoming", true, 10));
}
// china:xiaoming:man:10
There is also compose in this similarity, but this method is only available in Function1, and the essence is actually to change the execution order, in fact, it is similar to the conforming function
@Test
public void composeTest() {
Function1<Long, String> func1 = num -> num + "%";
Function1<Double, String> func2 = func1.compose((Double num) -> Math.round(num));
System.out.println(func2.apply(12.25));
}
// 12%
PartialApply
Some applications mean that if there are 5 input parameters in Fcuntion, and you pass 2 in apply(), then the compiler will not report an error, but apply will not execute your function normally, but generate another function, which has only 3 input parameters, which is converted from the original five arguments two of which are written with fixed values. show code
@Test
public void partialApplyTest() {
Function4<String, String, Boolean, Integer, String> func1 =
(country, name, isMan, score) -> String.format("%s-%s-%s-%d", country, name, isMan ? "man" : "female", score);
Function3<String, Boolean, Integer, String> func2 = func1.apply("china");
System.out.println(func2.apply("xiaoming", true, 10));
Function2<Boolean, Integer, String> func3 = func1.apply("china", "xiaoming");
System.out.println(func3.apply(true, 10));
Function1<Integer, String> func4 = func1.apply("china", "xiaoming", true);
System.out.println(func4.apply(10));
System.out.println(func1.apply("china", "xiaoming", true, 10));
}
// china-xiaoming-man-10
// china-xiaoming-man-10
// china-xiaoming-man-10
// china-xiaoming-man-10
Coryization
Coryization is a technique that transforms a function that accepts multiple arguments into a function that accepts a single argument (the first argument of the original function) and returns a new function that accepts the remaining arguments and returns the result. You only need to call the curried() method of func to corilate the function, and then you can only pass in one value each time apply(), and its return value is still a coried function. See the code below for details.
@Test
public void curriedTest() {
Function4<String, String, Boolean, Integer, String> func1 =
(country, name, isMan, score) -> String.format("%s-%s-%s-%d", country, name, isMan ? "man" : "female", score);
Function1<String, Function1<Boolean, Function1<Integer, String>>> func2 = func1.curried().apply("china");
Function1<Boolean, Function1<Integer, String>> func3 = func2.apply("xiaoming");
Function1<Integer, String> func4 = func3.apply(true);
String result = func4.apply(10);
System.out.println(result);
}
// china-xiaoming-man-10
In this way, any of the above functions can be extended, the reuse rate is greatly improved, and it is easy to call.
Memorization
As the name suggests, it is to save the result of a function, and the next time you call the function again, you will directly return the result of the first calculation. To use the method, you only need to call the interfacememoized()Method. Emmm… There is not much actual effect, if I want to demonstrate, I can only find a random number to operate, and I feel that there are not many scenes in the project events.
@Test
public void memorizeTest() {
Function0<Double> hashCache = Function0.of(Math::random).memoized();
double randomValue1 = hashCache.apply();
System.out.println(randomValue1);
double randomValue2 = hashCache.apply();
System.out.println(randomValue2);
}
// 0.6590067689384973
// 0.6590067689384973
New features that take advantage of functional interfaces
Pattern matching
Good news, good news, Java 14 already supports pattern matching, what? Your company hasn’t eaten 14 yet, oh, and our company hasn’t either….but you can experience Scala-like pattern matching with VAVR. Java’s switch can only work on constants, and there are a lot of restrictions, although jdk7 adds strings, but the bottom layer uses equals() to compare, which means that you pass in a null, and the NPE is thrown directly instead of going to default. Pattern matching can not only avoid various problems, but also work on the return value of another function, and the code function can also save a lot.
- Grammar Basics Demonstration,
Match(which puts the variable to be matched).of starts the case matching, where $() is followed by the value that needs to be returned after the match. Note that pattern matching will automatically break, and if $() doesn’t write anything, it’s like switch default
@Test
public void showTest() {
int input = 2;
String result = Match(input).of(
Case($(1), "one"),
Case($(2), "two"),
Case($(3), "three"),
Case($(), "?"));
System.out.println(result);
}
// two
- Syntax Advanced Matching Demo
$() actually has an overloaded method, which is to pass in a predicate function, vavr has its own predicate functional interface, which has many methods, for example, isIn() in the following code block is the method in predicate, and its return value is a predicate function, which can match multiple values.
- $(): Similar to the one in the switch statement default case. It handles cases where no match can be found.
- $(value): This is the equivalence mode, where one of the values is simply compared to the input.
- $(predicate): This is a conditional pattern where the predicate function is applied to the input and the resulting boolean is used to make a decision.
@Test
public void isInTest() {
int input = 1;
String result = Match(input).of(
Case($(isIn(0, 1)), "zero or one"),
Case($(2), "two"),
Case($(3), "three"),
Case($(), "?"));
System.out.println(result);
}
// zero or one
@Test
public void anyOfTest() {
Integer year = 1990;
String result = Match(year).of(
Case($(anyOf(isIn(1990, 1991, 1992), is(1986))), "Age match"),
Case($(), "No age match"));
System.out.println(result);
}
// Age match
@Test
public void customTest() {
int i = 5;
List<Integer> container = Lists.newArrayList(1, 2, 3, 4);
String result = Match(i).of(
Case($(e -> container.contains(e)), "Even Single Digit"),
Case($(), "Out of range"));
System.out.println(result);
}
// Out of range
- Side effects shown
Seeing the above example, in fact, each case returns a value, sometimes we match, but nothing returns, just dosomething through side effects. The following code looks a bit winding, let me explain it a little, because the second parameter of the case we originally put the return value, and now if you want to use side effects, you must put a supplier, don’t ask me why this is required by others, so it must be used() ->
,So what is returned?,Put the run() method here,The input parameter of the run() method is a Runnable,The output parameter is a Void,Then this Void can be ignored,Note that this Void is provided by vavr,Not the jdk keyword void。 All you need to do is put the side effects code into the runnable interface. Runnable is the Runnable under the lang package.,I don't need to say more about this.。
@Test
public void sideEffectsTest() {
int i = 4;
Match(i).of(
Case($(isIn(2, 4, 6, 8)), () -> run(() -> System.out.println("first"))),
Case($(isIn(1, 3, 5, 7, 9)), () -> run(() -> System.out.println("two"))),
Case($(), o -> run(() -> System.out.println("no found"))));
}
Try
Try is similar to the JDK’s try catch. The code executed in the Try will not throw an exception, and both the exception and the normal return value will be taken over by vavr and then returned by the try. The specific usage is Try.of(). Then a supplier is passed in the of, and the input parameter is fixed() ->
, the return value is the result of your function.
- Basic demo
@Test
public void tryTest() {
Try<Integer> result = Try.of(() -> 1 / 0);
System.out.println(result.isSuccess());
System.out.println(result.getCause());
System.out.println(result.getOrNull());
System.out.println(result.getOrElse(0));
}
// false
// java.lang.ArithmeticException: / by zero
// null
// 0
In fact, it also comes with a lot of methods, a bit like the optional of JDK, and it is also similar to a “container”, except that it contains the behavior that can go wrong, and allows you to carry out the next processing or fallback solution. I usually use Try for simple processing, because it’s really convenient. For example, I usually use try catch to wrap it in JSON.parseObject(), hoping to be robust, and the ghost knows what string is passed upstream, but try catch is written very ugly, and it looks more comfortable if you use Try to wrap it.
@Test
public void trySeniorTest() {
List<Integer> list = Try.of(() -> JSON.parseArray("json", Integer.class))
.getOrElse(Collections.emptyList());
System.out.println(list);
}
// []
Immutable collection classes
Tuple
As we all know, java does not have a progenitor, but sometimes the progenitor is really easy to use, vavr implements the progenitor through generics, you can use Tuple’s static factory to create the progenitor, and use the automatic inference of idea or the var type of java10 to infer the direct efficiency to the point of explosion. The usage method is similar to Scala,
Tuples are made up of different elements, each of which can store different types of data. It’s a bit like multiple generic lists, for example, the list of List can only put Integer, the ancestor is Tuple<Integer, String> which means that you can put Integer and String in it, but it often needs to specify the number, because you need to specify which type of element in that position is.
- Basic use
Tuple.of can be initialized, you only need to put the element in the of, idea will automatically help you deduce the number of Tuple, and then you only need to use the element _ number, for example element 1 is_1
@Test
public void tupleTest() {
Tuple2<Integer, String> t2 = Tuple.of(1, "1");
System.out.println(t2._1);
System.out.println(t2._2);
}
- Other uses
Tuple is immutable, you can modify it or add to it, but making changes will always return a new progenitor. The change is simple to callupdate()
Methods, augmentation, are also very simple to callappend()
method
@Test
public void tupleSeniorTest() {
Tuple2<Integer, String> t2 = Tuple.of(1, "1");
System.out.println(t2);
Tuple2<Integer, String> t2s = t2.update1(2);
System.out.println(t2s);
Tuple3<Integer, String, Double> t3 = t2.append(1.0);
System.out.println(t3);
}
I like the ancestor very much, because sometimes I’m lazy, I don’t want to create a pojo for everything, and I don’t want the map to fly around, the ancestor is easy to use and very clear, it’s a trade-off between the two, especially with the matching mode to use elegance to take off directly. But!!! Please note that VAVAR’s Tuple does not support jackson and json serialization, I have already stepped on this pit for you, please do not use http return value or rpc communication.
List/Set/Map
One of the most important features of functional programming is immutability, JDK’s Collections can make a collection class immutable, but….show code
@Test
public void collectionsTest() {
List<Integer> list = Lists.newArrayList(1, 2, 3);
System.out.println(list);
List<Integer> unmodifiableList = Collections.unmodifiableList(list);
System.out.println(unmodifiableList);
list.add(1);
System.out.println(list);
System.out.println(unmodifiableList);
unmodifiableList.add(1);
}
// [1, 2, 3]
// [1, 2, 3]
// [1, 2, 3, 1]
// [1, 2, 3, 1]
//
// java.lang.UnsupportedOperationException
As you can see in the above code, the Collections immutable list is a shallow copy of the original list, and the change of the elements of the original list will still change the so-called ‘immutable’ list.
- vavr’s list
VAVR list useList.of()
to create, immutable after creation, but you can add or remove elements, and you must know that a new immutable list will be generated after each change.
@Test
public void collectionsTest() {
io.vavr.collection.List<Integer> list = io.vavr.collection.List.of(1, 2);
io.vavr.collection.List<Integer> appendList = list.append(3);
io.vavr.collection.List<Integer> dropList = list.drop(1);
List<Integer> javaList = list.asJava();
}
In addition, vavr’s list can directly use the stream operator, and it is not allowed to convert it to a stream stream through stream(), and then use the operator, which cannot be said to be exactly the same as scala, but can only be said to be no different. Similarly, APIs that provide more functional tools are available, such as
- take(Integer) takes the first n values
- tail() takes the set except the head node
- zipWithIndex() makes it easy to get the index (without fori)
- find(Predicate) is based on conditional query values, and filter + findFirst must be used in the Java standard library to achieve …..
Other functional programming features
Option
Don’t pretend.,I’m showdown.,This option is the same as jdk’s optional.,It should be inspired by guava’s Optional.。 However, vavr’s Otion is an interface that has two implementation classes, Some and None. The former has a valued state, and the latter has no value. The way to consume is:Option.of()
@Test
public void multiFunctionTest() {
Integer num = null;
Option<Integer> opt = Option.of(num);
Integer result = opt.getOrElse(0);
System.out.println(result);
boolean isEmpty = opt.isEmpty();
System.out.println(isEmpty);
Optional<Integer> optional = opt.toJavaOptional();
}
// 0
// true
Because there are a lot of methods, I don’t put them.,Most of the methods are the same as optional.,And some of them are common to vavr.,It’s not exclusive to option.。
Lazy
Delayed computation is also a feature of functional programming, especially used a lot in Scala, and the value is cached after the first calculation. It helps a lot to save memory and improve performance. In Scala it is done by keywords, but how does Vavr do it in Java? Similar to option, load the variable into a “container” and load the value.
@Test
public void lazyTest() {
Lazy<Double> lazy = Lazy.of(Math::random);
System.out.println(lazy.isEvaluated());
System.out.println(lazy.get());
System.out.println(lazy.isEvaluated());
System.out.println(lazy.get());
}
// false
// 0.896267693320266
// true
// 0.896267693320266
Of course, if you are really interested, I recommend you to take a lookresilience4j, which is a current-limiting fuse degradation middleware written in vavr instead of Hystrix. The quality of the code is really very high, and I think it’s the best material to learn functional programming at the moment, but it’s more difficult to chew, because functional programming itself is very cool to write but not very friendly to viewers.
3. Code engineering
Objectives of the experiment
Use VAVR to write an interface for querying GitHub user information
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-demo</artifactId>
<groupId>com.et</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>vavr</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--vavr-->
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.10.4</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.20</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
controller
package com.et.vavr.controller;
import com.et.vavr.domain.User;
import com.et.vavr.service.GithubService;
import io.vavr.control.Try;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
* @author armena
*/
@RestController
@RequestMapping("/api/v1/github")
public class GithubController {
@Autowired
private GithubService githubService;
@GetMapping(path = "/{username}", produces = "application/json;charset=UTF-8")
public ResponseEntity<?> get(@Valid @PathVariable String username
) {
Try<User> githubUserProfile = githubService.findGithubUser(username);
if (githubUserProfile.isFailure()) {
return ResponseEntity.status(HttpStatus.FAILED_DEPENDENCY).body(githubUserProfile.getCause().getMessage());
}
if (githubUserProfile.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Response is empty");
}
if (githubUserProfile.isSuccess()) {
return ResponseEntity.status(HttpStatus.OK).body(githubUserProfile.get());
}
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body("username is not valid");
}
@GetMapping(path = "/fail/{username}", produces = "application/json;charset=UTF-8")
public ResponseEntity<?> getFail(@Valid @PathVariable String username
) {
Try<User> githubUserProfile = githubService.findGithubUserAndFail(username);
if (githubUserProfile.isFailure()) {
System.out.println("Fail case");
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(githubUserProfile.getCause().getMessage());
}
if (githubUserProfile.isEmpty()) {
System.out.println("Empty case");
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Response is empty");
}
if (githubUserProfile.isSuccess()) {
System.out.println("Success case");
return ResponseEntity.status(HttpStatus.OK).body(githubUserProfile.get());
}
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body("username is not valid");
}
}
service
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.et.vavr.service;
import com.et.vavr.domain.User;
import io.vavr.control.Try;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
/**
*
* @author Admin
*/
@Service
public class GithubService {
@Autowired
private RestTemplate restTemplate;
public Try<User> findGithubUser(String username) {
return Try.of(() -> restTemplate.getForObject("https://api.github.com/users/{username}", User.class, username));
}
public Try<User> findGithubUserAndFail(String username) {
return Try.of(() -> restTemplate.getForObject("https://api.twitter.com/users/fail/{username}", User.class, username));
}
}
entity
package com.et.vavr.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
public class User {
private String id;
private String login;
private String location;
}
config
package com.et.vavr.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
The above are just some of the key codes, all of which can be found in the repositories below
Code repositories
4. Testing
Start the Spring Boot application
Test the query interface
- Access http://127.0.0.1:8088/api/v1/github/{username}
- Returns the corresponding user information