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.