Who is it?
As you know, Java8 supports functional programming to some extent, but the functional API provided by the standard library is not very complete and friendly.
In order to do functional programming well, we have to rely on third-party libraries, and VAVR is the best at this, reducing the amount of code and improving the quality of the code.
VAVR is no unknown, its predecessor is Javaslang, which was released in 2014, and currently has nearly 4K star on Github.
See here, a lot of people say I headlines party, a Java library also to subvert Java?
The front page of VAVR turns Down says “Vavr-Turns Java ™ Upside Down” in bold letters.
Doesn’t that translate to subverting Java?
Eating guide
Reading this article requires some knowledge of Java8’s lambda syntax and common apis.
Since this is an introduction to the framework, there are some constraints in order to avoid translation as an official document
- It’s not going to exhaust all the features and apis, it’s just going to be an introduction
- Will not go into the source details
As for the sample code, it’s basically given as a unit test and guaranteed to pass
Note: VAVR version 0.10.3 and JDK version 11 are used in this article.
Let’s start with an overview
Set up, fresh start
I have to say that Java8’s collection library works well with the introduction of Stream, but it also reduces the experience by having to write a lot of boilerplate code.
// the of method is the static factory that Java9 started providing
java.util.List.of(1.2.3.4.5)
.stream()
.filter(i -> i > 3)
.map(i -> i * 2)
.collect(Collectors.toList());
Copy the code
And since Java’s collection libraries are mutable, which obviously violates the basic feature of functional programming — immutable — VAVR has designed a whole new set of collection libraries that are much closer to Scala’s experience.
A cleaner API
io.vavr.collection.List.of(1.2.3.4.5)
.filter(i -> i > 3)
.map(i -> i * 2);
Copy the code
Appending data to a collection produces a new collection, thus ensuring immutability
var list = io.vavr.collection.List.of(1.2)
var list2 = list
.append(List.of(3.4))
.append(List.of(5.6))
.append(7);
// list = [1, 2]
// list2 = [1, 2, 3, 4, 5, 6]
Copy the code
Powerful compatibility, can be very convenient with the Java standard collection library conversion
var javaList = java.util.List.of(1.2.3);
java.util.List<Integer> javaList2 = io.vavr.collection.List.ofAll(javaList)
.filter(i -> i > 1)
.map(i -> i * 2)
.toJavaList();
Copy the code
A slightly more complex example would be to filter adult data from a group of users, grouping them by age and displaying only the user’s name in each group.
/** * User information */
@Data
class User {
private Long id;
private String name;
private Integer age;
}
Copy the code
Using the Java Standard Collection library to implement this requirement, you can see that collect(…) This long list of nesting is really uncomfortable
public Map<Integer, List<String>> userStatistic(List<User> users) {
return users.stream()
.filter(u -> u.getAge() >= 18)
.collect(Collectors.groupingBy(User::getAge, Collectors.mapping(User::getName, Collectors.toList())));
}
Copy the code
Let’s take a look at the implementation of VAVR, is it more concise, more intuitive?
public Map<Integer, List<String>> userStatistic(List<User> users) {
return users.filter(u -> u.getAge() >= 18)
.groupBy(User::getAge)
.mapValues(usersGroup -> usersGroup.map(User::getName));
}
Copy the code
VAVR’s collection library provides more Functional apis, such as
- Take (Integer) Takes the first n values
- Tail () takes the set except for the head nodes
- ZipWithIndex () makes it convenient to get the index (without fori)
- Find (Predicate) queries values based on conditions. In the Java standard library, you need to use Filter + findFirst
- .
Although the code instances use lists, the above features are available for Queue, Set, and Map, all of which support conversions to the Java standard library.
Tuples, the missing structure in Java
Those familiar with Haskell and Scala are certainly familiar with the data structure “tuple”.
A tuple is like an array. It can hold objects of different types and maintain their type information, so that values are not cast.
// Scala tuples, constructed with parentheses
val tup = (1."ok".true)
// Perform the operation based on the index value
val sum = tup._1 + 2 / / int addition
val world = "hello "+tup._2 // String concatenation
valres = ! tup._3// Boolean is inverted
Copy the code
Of course, Java has no native syntax for creating tuples, and the standard library has no tuple-related classes.
However, VAVR implements tuples through generics, and with the static factory of tuples, we can easily create tuples (with Java10’s var syntax, it’s not so nice).
import io.vavr.Tuple;
public TupleTest {
@Test
public void testTuple(a) {
/ / a tuple
var oneTuple = Tuple.of("string");
String oneTuple_1 = oneTuple._1;
/ / the dualistic group
var twoTuple = Tuple.of("string".1);
String twoTuple_1 = twoTuple._1;
Integer twoTuple_2 = twoTuple._2;
Five yuan / / group
var threeTuple = Tuple.of("string".2.1.2 F.2.4D, 'c'); String threeTuple_1 = threeTuple._1; Integer threeTuple_2 = threeTuple._2; Float threeTuple_3 = threeTuple._3; Double threeTuple_4 = threeTuple._4; Character threeTuple_5 = threeTuple._5; }}Copy the code
Without var, you would have to write the following verbose variable definition
Tuple5<String, Integer, Float, Double, Character> tuple5 = Tuple.of("string".2.1.2 F.2.4D, 'c');
Copy the code
Currently, VAVR supports the construction of a maximum of octuples, that is, up to eight types, not up to eight values.
When tuples and “pattern matching” are used together, they are even more powerful
PS: It’s a little early to talk about pattern matching (we’ll see you later), but we can still get a feel for it
var tup = Tuple.of("hello".1);
// Pattern matching
Match(tup).of(
Case($Tuple2($(is("hello")), $(is(1))), (t1, t2) -> run(() -> {})),
Case($Tuple2($(), $()),(t1, t2) ->run(() -> {}))
);
Copy the code
The above code is equivalent to if… else
// equivalent to if... else
if (tup._1.equalas("hello") && tup._2 == 1) {
/ /... do something
} else {
// ... do something
}
Copy the code
In addition to Option, Try, Either, Future……
Java8 introduces Optional to solve the infamous NullPointerException, while VAVR has a similar tool, Option, but with a different design.
In addition to Options, VAVR implements functional constructs such as Try, Either, and Future, which are all very powerful tools that the Java standard library does not have.
Option
Option is similar to the Java standard library’s Optional in that it represents an Optional value, but the design is quite different. (VAVR’s Option design is closer to Scala’s)
In VAVR, Option is an interface, and its concrete implementations are Some and None
- Some: there is value
- None: indicates no value
You can verify this with the following unit tests
@Test
public void testOption(a) {
// Construct with the of factory method
Assert.assertTrue(Option.of(null) instanceof Option.None);
Assert.assertTrue(Option.of(1) instanceof Option.Some);
// Construct by None or some
Assert.assertTrue(Option.none() instanceof Option.Some);
Assert.assertTrue(Option.some(1) instanceof Option.Some);
}
Copy the code
For Java.util.Optional, it’s the same type no matter how it’s constructed.
@Test
public void testOptional(a) {
Assert.assertTrue(Optional.ofNullable(null) instanceof Optional);
Assert.assertTrue(Optional.of(1) instanceof Optional);
Assert.assertTrue(Optional.empty() instanceof Optional);
Assert.assertTrue(Optional.ofNullable(1) instanceof Optional);
}
Copy the code
Why the design difference?
Essentially, the answer to “Is the function of Option to make null calculations safe?” Different answers to this question.
If you use Option and Optional, you get different results. If you use Option and Optional, you get different results
@Test
public void testWithJavaOptional(a) {
// Java Optional
var result = Optional.of("hello")
.map(str -> (String) null)
.orElseGet(() -> "world");
// result = "world"
Assert.assertEquals("word", result);
}
@Test
public void testWithVavrOption(a) {
// Vavr Option
var result = Option.of("hello")
.map(str -> (String) null)
.getOrElse(() -> "world");
// result = null
Assert.assertNull(result);
}
Copy the code
In VAVR’s test code, passing Optional. Of (“hello”) actually gives you an Some(“hello”) object.
A subsequent call to map(STR -> (String)null) still returns an Some object (Some represents a value), so the final result = null, Rather than the world string returned by getOrElse(() -> “world”).
OrElseGet (() -> “world”); / / map(STR -> null); / / map(STR -> null); / / map(STR -> null); / / map(STR -> null); / / map(STR -> null); / / map(STR -> null);
This is one of the points where functional developers criticize the java.util.Optional design
In addition to the design differences, io.vavr.control.Option has more friendly apis than Java.util
@Test
public void testVavrOption(a) {
// Convert option directly to List
List<String> result = Option.of("vavr hello world")
.map(String::toUpperCase)
.toJavaList();
Assert.assertNotNull(result);
Assert.assertEquals(1, result.size());
Assert.assertEquals("vavr hello world", result.iterator().next());
// exists(Function)
boolean exists = Option.of("ok").exists(str -> str.equals("ok"));
Assert.assertTrue(exists);
// contains
boolean contains = Option.of("ok").contains("ok");
Assert.assertTrue(contains);
}
Copy the code
Considering the compatibility with the standard library, Option can be easily interchanged with Optional
Option.of("toJava").toJavaOptional();
Option.ofOptional(Optional.empty());
Copy the code
Try
Try is similar to Option. It also acts like a “container” except that it contains behavior that can go wrong. The catch?
try {
/ /..
} catch (Throwable t) {
/ /...
} finally {
//....
}
Copy the code
Using VAVR’s Try, you can also make another more functional Try… The catch.
/** * output * failure: / by zero * finally */
Try.of(() -> 1 / 0)
.andThen(r -> System.out.println("and then " + r))
.onFailure(error -> System.out.println("failure" + error.getMessage()))
.andFinally(() -> {
System.out.println("finally");
});
Copy the code
Try is also an interface. The implementation is Success or Failure
- Success: Indicates that there is no exception
- Failure: indicates that the execution is abnormal
Like Optoin, it can also be built through the OF factory method
@Test
public void testTryInstance(a) {
// Divide by 0 to build Failure
var error = Try.of(() -> 0 / 0);
Assert.assertTrue(error instanceof Try.Failure);
// Build Success with a valid addition
var normal = Try.of(() -> 1 + 1);
Assert.assertTrue(normal instanceof Try.Success);
}
Copy the code
With the recoverWith method of Try, we can implement the degradation strategy gracefully
@Test
public void testTryWithRecover(a) {
Assert.assertEquals("NPE", testTryWithRecover(new NullPointerException()));
Assert.assertEquals("IllegalState", testTryWithRecover(new IllegalStateException()));
Assert.assertEquals("Unknown", testTryWithRecover(new RuntimeException()));
}
private String testTryWithRecover(Exception e) {
return (String) Try.of(() -> {
throw e;
})
.recoverWith(NullPointerException.class, Try.of(() -> "NPE"))
.recoverWith(IllegalStateException.class, Try.of(() -> "IllegalState"))
.recoverWith(RuntimeException.class, Try.of(() -> "Unknown"))
.get();
}
Copy the code
The result of the Try can be converted by map or, conveniently, by Option.
You can also use a Map to transform the results and interact with Options
@Test
public void testTryMap(a) {
String res = Try.of(() -> "hello world")
.map(String::toUpperCase)
.toOption()
.getOrElse(() -> "default");
Assert.assertEquals("HELLO WORLD", res);
}
Copy the code
Future
The Future is not a Java. Util. Concurrent. The Future, but they are the results of the calculation of asynchronous an abstract.
Vavr Future offers than Java. Util. Concurrent. The Future is more friendly callback mechanism
- OnFailure Callback for failure
- OnSuccess Successful callback
@Test
public void testFutureFailure(a) {
final var word = "hello world";
io.vavr.concurrent.Future
.of(Executors.newFixedThreadPool(1), () -> word)
.onFailure(throwable -> Assert.fail("Shouldn't go to the failure branch."))
.onSuccess(result -> Assert.assertEquals(word, result));
}
@Test
public void testFutureSuccess(a) {
io.vavr.concurrent.Future
.of(Executors.newFixedThreadPool(1), () - > {throw new RuntimeException();
})
.onFailure(throwable -> Assert.assertTrue(throwable instanceof RuntimeException))
.onSuccess(result -> Assert.fail("Shouldn't go to the SUCCESS branch."));
}
Copy the code
It also rotates with a Java CompleableFuture
Future.of(Executors.newFixedThreadPool(1), () - >"toJava").toCompletableFuture();
Future.fromCompletableFuture(CompletableFuture.runAsync(() -> {}));
Copy the code
other
Finally, a quick look at Either and Lazy
-
Either indicates that a value may be of one of two types, such as the following compute() function where the return value of Either indicates that the structure may be Exception or String.
Right is usually used to represent the correct value.
public Either<Exception, String> compute(a) { / /... } public void test(a) { Either<Exception, String> either = compute(); / / the outliers if (either.isLeft()) { Exception exception = compute().getLeft(); throw new RuntimeException(exception); } / / correct value if (either.isRight()) { String result = compute().get(); // ...}}Copy the code
-
Lazy is also a container that delays a calculation until it is called for the first time, after which the result is cached and subsequent calls get the result directly.
Lazy<Double> lazy = Lazy.of(Math::random); lazy.isEvaluated(); // = false lazy.get(); // = 0.123 (random generated) lazy.isEvaluated(); // = true lazy.get(); / / = 0.123 (memoized) Copy the code
The io.vavr.API provides a number of static methods to emulate Scala’s syntax for constructing options and tries, but use them in conjunction with Java’s static imports
import static io.vavr.API.*;
@Test
public void testAPI(a) {
/ / construction Option
var some = Some(1);
var none = None();
/ / the Future construction
var future = Future(() -> "ok");
/ / constructs a Try
var tryInit = Try(() -> "ok");
}
Copy the code
Of course, this uppercase function name is a bit against Java method naming conventions, which is a Hack.
For more details, check out the documentation on our website
Pattern matching: if.. The bane of the else
The pattern here refers to the composition pattern of the data structure, which can be used directly in Scala with the match keyword
def testPatternMatch(nameOpt: Option[String], nums: List[Int]) = {
/** * Matches the structure of Option */
nameOpt match {
case Some(name) => println(S "hello,$name")
case None => println("Nobody")}/** * Matches the List structure */
nums match {
case Nil => println("Empty list")
case List(v) => println(s"size=1 $v")
case List(v, v2) => println(s"size=2 $v,$v2")
case _ => println("size > 2")}}Copy the code
Since there is no concept of pattern matching in Java, there is no syntax for it (switch does not count).
However, VAVR uses OOP for pattern matching, which is not quite as good as the native Scala experience, but it’s pretty close
Java in the JEP 375: The proposal implements a Pattern Matching feature for instanceof (expected in Java15), but I think it’s still a long way from Scala’s Pattern Matching
Let’s implement a requirement to format BMI values into a literal description, first in Java imperative style
public String bmiFormat(double height, double weight) {
double bmi = weight / (height * height);
String desc;
if (bmi < 18.5) {
desc = "A little wobbly!;
} else if (bmi < 25) {
desc = "Keep going!";
} else if (bmi < 30) {
desc = "You're really steady!;
} else {
desc = "Miserable!;
}
return desc;
}
Copy the code
Then use VAVR’s pattern matching to refactor to eliminate these if.. The else.
To make the syntax experience more user-friendly, it is best to import the API through static Import first.
import static io.vavr.API.*;
Copy the code
Here is the refactored code snippet
public String bmiFormat(double height, double weight) {
double bmi = weight / (height * height);
return Match(bmi).of(
// else if (bmi < 18.5)
Case($(v -> v < 18.5), () - >"A little wobbly!),
// else if (bmi < 25)
Case($(v -> v < 25), () - >"Keep going!"),
// else if (bmi < 30)
Case($(v -> v < 30), () - >"You're really steady!),
// else
Case($(), () -> "Miserable!)); }Copy the code
-
Match(…) , Case (…). , $(…). Both are static methods of io.vavr.API to simulate the syntax of “pattern matching.
-
The last $() means to match everything but the above
For the sake of the reader’s understanding, I have briefly listed the signatures of each method (Case and $methods have many overloads, so I will not list them all).
public static <T> Match<T> Match(T value) {... }public static <T, R> Case<T, R> Case(Pattern0<T> pattern, Function<? super T, ? extends R> f) {... }public static <T> Pattern0<T> $(Predicate<? superT> predicate) {... }Copy the code
Of is the method of the Match object
public final <R> R of(Case<? extends T, ? extends R>... cases) {... }Copy the code
Here, let’s show you some more of our own grammatical memorization
To match the structure, is it one of the following// Match(XXX).Of(- The structure is the same as A, what do you do//Case( $(A), () -> doSomethingA() ),- The structure is the same as B, what do you do//Case( $(B), () -> doSomethingB() ),-... - Do something different from the above structure//Case( $(), () -> doSomethingOthers())
/ /);
Copy the code
When pattern matching is combined with the options, Try, Either, and Tuple mentioned earlier, it is a combination of 1 + 1 > 3.
The following code shows how “pattern matching” can make Options more powerful
import static io.vavr.API.*;
import static io.vavr.Patterns.$None;
import static io.vavr.Patterns.$Some;
public class PatternMatchTest {
@Test
public void testMatchNone(a) {
/ / match None
var noneOpt = Option.none();
Match(noneOpt).of(
Case($None(), r -> {
Assert.assertEquals(Option.none(), r);
return true;
}),
Case($(), this::failed)
);
}
@Test
public void testMatchValue(a) {
// Match Some value of Nice
var opt2 = Option.of("Nice");
Match(opt2).of(
Case($Some($("Nice")), r -> {
Assert.assertEquals("Nice", r);
return true;
}),
Case($(), this::failed)
);
}
@Test
public void testMatchAnySome(a) {
// Match Some with any value
var opt = Option.of("hello world");
Match(opt).of(
Case($None(), this::failed),
Case($Some($()), r -> {
Assert.assertEquals("hello world", r);
return true; })); }private boolean failed(a) {
Assert.fail("This branch should not be executed.");
return false; }}Copy the code
There is also a Try. By the way, sometimes when the Case does not return a value, the second argument can be replaced by api.run ()
import static io.vavr.API.*;
import static io.vavr.Patterns.*;
import static io.vavr.Predicates.instanceOf;
public class PatternMatchTest {
@Test
public void testMatchFailure(a) {
var res = Try.of(() -> {
throw new RuntimeException();
});
Match(res).of(
// Match success
Case($Success($()), r -> run(Assert::fail)),
// The match exception is RuntimeException
Case($Failure($(instanceOf(RuntimeException.class))), r -> true),
// The match exception is IllegalStateException
Case($Failure($(instanceOf(IllegalStateException.class))), r -> run(Assert::fail)),
// The matching exception is NullPointerException
Case($Failure($(instanceOf(NullPointerException.class))), r -> run(Assert::fail)),
// Match the remaining failures
Case($Failure($()), r -> run(Assert::fail))
);
}
@Test
public void testMatchSuccess(a) {
var res = Try.of(() -> "Nice");
Match(res).of(
// Match any successful case
Case($Success($()), r -> run(() -> Assert.assertEquals("Nice", r))),
// Match any failureCase($Failure($()), r -> run(Assert::fail)) ); }}Copy the code
Now looking back at the tuple code, you can try your own pattern matching for triples.
The last
This article only covers some of the common features, but VAVR also supports advanced features such as Curring, Memoization, Partial Application, and so on. If you want to learn more about VAVR, check out the website.
Finally, this brick has been thrown out, can lead to your piece of jade?
Advertising:
If you’re looking for a Java9+ based project to learn new features, I recommend PrettyZoo,
This is a ZooKeeper desktop client based on Java11 development, using modularity, var and many other new features, welcome star, fork, issue.