当前位置: 首页 > 工具软件 > Vavr > 使用案例 >

java8元组_Java8+ 函数库Vavr简介

曹伟泽
2023-12-01

1 概述

Vavr 是Java 8+中一个函数式库,提供了一些不可变数据类型及函数式控制结构。

1.1 Maven 依赖

添加依赖,可以到maven仓库中查看最新版本。

io.vavr

vavr-control

0.10.2

2. Option

Option的作用是消除代码中的null检查。在Vavr中Option是一个对象容器,与Optional类似,有一个最终结果。 Vavr中的Option实现了Serializable, Iterable接口,并且具有更加丰富的API。在Java中,我们通常通过if语句来检查引用是否为null,以此来保证系统健壮与稳定。如果不检查会出现空指针异常。

@Test

public void givenValue_whenNullCheckNeeded_thenCorrect() {

Object object = null;

if (object == null) {

object = "someDefaultValue";

}

assertNotNull(possibleNullObj);

}

如果包含较多的if检查,同时带有嵌套语句,那么代码开始变得臃肿。Option通过将null替换为一个有效对象来解决这个问题。使用Option null值会通过None实例来表示,而非null值则是某个具体对象实例。

@Test

public void givenValue_whenCreatesOption_thenCorrect() {

Option noneOption = Option.of(null);

Option someOption = Option.of("val");

assertEquals("None", noneOption.toString());

assertEquals("Some(val)", someOption.toString());

}

代码中调用toString时,并没有进行检查来处理NullPointerException问题。Option的toString会返回给我们一个有意义的值,这里是 “None”。当值为null时,还可以指定默认值。

@Test

public void givenNull_whenCreatesOption_thenCorrect() {

String name = null;

Option nameOption = Option.of(name);

assertEquals("baeldung", nameOption.getOrElse("baeldung"));

}

当为非null时返回值本身。

@Test

public void givenNonNull_whenCreatesOption_thenCorrect() {

String name = "baeldung";

Option nameOption = Option.of(name);

assertEquals("baeldung", nameOption.getOrElse("notbaeldung"));

}

这样在处理null相关检查时,只需要写一行代码即可,与Optional类似。

3. 元组Tuple

Java中没有与元组(Tuple)相对应的结构。Tuple是函数式编程中一种常见的概念。Tuple是一个不可变,并且能够以类型安全的形式保存多个不同类型的对象。Tuple中最多只能有8个元素。

public void whenCreatesTuple_thenCorrect1() {

Tuple2 java8 = Tuple.of("Java", 8);

String element1 = java8._1;

int element2 = java8._2();

assertEquals("Java", element1);

assertEquals(8, element2);

}

引用元素时从1开始,而不是0。

Tuple中的元素必须是所声明的类型。

@Test

public void whenCreatesTuple_thenCorrect2() {

Tuple3 java8 = Tuple.of("Java", 8, 1.8);

String element1 = java8._1;

int element2 = java8._2();

double element3 = java8._3();

assertEquals("Java", element1);

assertEquals(8, element2);

assertEquals(1.8, element3, 0.1);

}

当需要返回多个对象时可以考虑使用Tuple。使用方法与Pair类似。

org.apache.commons.lang3.tuple提供了三个值的Triple

4. Try

在Vavr, Try是一个容器,来包装一段可能产生异常的代码。Option用来包装可能产生null的对象,而Try用来包装可能产生异常的代码块,这样就不用显式的通过try-catch来处理异常。下面的代码用来检查是否产生了异常。

@Test

public void givenBadCode_whenTryHandles_thenCorrect() {

Try result = Try.of(() -> 1 / 0);

assertTrue(result.isFailure());

}

我们也可以在产生异常时获取一个默认值。

@Test

public void givenBadCode_whenTryHandles_thenCorrect2() {

Try computation = Try.of(() -> 1 / 0);

int errorSentinel = result.getOrElse(-1);

assertEquals(-1, errorSentinel);

}

或者根据具体需求再抛出一个异常。

@Test(expected = ArithmeticException.class)

public void givenBadCode_whenTryHandles_thenCorrect3() {

Try result = Try.of(() -> 1 / 0);

result.getOrElseThrow(ArithmeticException::new);

}

5. 函数式接口

Java 8中的函数式接口最多接收两个参数,Vavr对其进行了扩展,最多支持8个参数。

@Test

public void whenCreatesFunction_thenCorrect5() {

Function5 concat =

(a, b, c, d, e) -> a + b + c + d + e;

String finalString = concat.apply(

"Hello ", "world", "! ", "Learn ", "Vavr");

assertEquals("Hello world! Learn Vavr", finalString);

}

此外可以通过静态工厂方法FunctionN.of使用方法引用来创建一个Vavr函数。

public int sum(int a, int b) {

return a + b;

}

@Test

public void whenCreatesFunctionFromMethodRef_thenCorrect() {

Function2 sum = Function2.of(this::sum);

int summed = sum.apply(5, 6);

assertEquals(11, summed);

}

6. 集合Collections

Java中的集合通常是可变集合,这通常是造成错误的根源。特别是在并发场景下。

此外Jdk中的集合类存在一些不足。例如JDK中的集合接口提供的一个方法clear,

该方法删除所有元素而且没有返回值。

interface Collection {

void clear();

}

在并发场景下大多集合都会会产生问题,因此有了诸如ConcurrentHashMap这样的类。

此外JDK还通过一些其它的方法创建不可变集集合,但误用某些方法时会产生异常。如

下,创建不可修改List,在误调用add的情况下会产生UnsupportedOperationException

异常。

@Test(expected = UnsupportedOperationException.class)

public void whenImmutableCollectionThrows_thenCorrect() {

java.util.List wordList = Arrays.asList("abracadabra");

java.util.List list = Collections.unmodifiableList(wordList);

list.add("boom");

}

Vavr中的集合则会避免这些问题,并且保证了线程安全、不可变等特性。在Vavr中创建一个list,实例并且不包含那些会导致UnsupportedOperationException异常的方法,且不可变,这样避免误用,造成错误。

@Test

public void whenCreatesVavrList_thenCorrect() {

List intList = List.of(1, 2, 3);

assertEquals(3, intList.length());

assertEquals(new Integer(1), intList.get(0));

assertEquals(new Integer(2), intList.get(1));

assertEquals(new Integer(3), intList.get(2));

}

此外还可以通过提供的API执行计算任务。

@Test

public void whenSumsVavrList_thenCorrect() {

int sum = List.of(1, 2, 3).sum().intValue();

assertEquals(6, sum);

}

Vavr集合提供了在Java集合框架中绝大多数常见的类,并且实现了其所有特征。Vavr提供的集合工具使得编写的代码更加紧凑,健壮,并且提供了丰富的功能。

7. 验证Validation

Vavr将函数式编程中 Applicative Functor(函子)的概念引入Java。vavr.control.Validation类能够将错误整合。通常情况下,程序遇到错误未做处理就会终止。然而,Validation会继续处理,并将程序错误累积,最终做为一个整体处理。

例如我们希望注册用户,用户具有用户名和密码。我们会接收一个输入,然后

决定是否创建Person实例或返回一个错误。Person类如下。

public class Person {

private String name;

private int age;

}

接着,创建一个PersonValidator类。每个变量都会有一个方法来验证。此外还有方法可以将所有的验证结果整合到一个Validation实例中。

class PersonValidator {

String NAME_ERR = "Invalid characters in name: ";

String AGE_ERR = "Age must be at least 0";

public Validation, Person> validatePerson(

String name, int age) {

return Validation.combine(

validateName(name), validateAge(age)).ap(Person::new);

}

private Validation validateName(String name) {

String invalidChars = name.replaceAll("[a-zA-Z ]", "");

return invalidChars.isEmpty() ?

Validation.valid(name)

: Validation.invalid(NAME_ERR + invalidChars);

}

private Validation validateAge(int age) {

return age < 0 ? Validation.invalid(AGE_ERR)

: Validation.valid(age);

}

}

验证规则为age必须大于0,name不能包含特殊字符。

@Test

public void whenValidationWorks_thenCorrect() {

PersonValidator personValidator = new PersonValidator();

Validation, Person> valid =

personValidator.validatePerson("John Doe", 30);

Validation, Person> invalid =

personValidator.validatePerson("John? Doe!4", -1);

assertEquals(

"Valid(Person [name=John Doe, age=30])",

valid.toString());

assertEquals(

"Invalid(List(Invalid characters in name: ?!4,

Age must be at least 0))",

invalid.toString());

}

Validation.Valid实例包含了有效值。Validation.Invalid包含了错误。因此validation要么

返回有效值要么返回无效值。Validation.Valid内部是一个Person实例,而Validation.Invalid是一组错误信息。

8. 延迟计算Lazy

Lazy是一个容器,表示一个延迟计算的值。计算被推迟,直到需要时才计算。此外,计算的值被缓存或存储起来,当需要时被返回,而不需要重复计算。

@Test

public void givenFunction_whenEvaluatesWithLazy_thenCorrect() {

Lazy lazy = Lazy.of(Math::random);

assertFalse(lazy.isEvaluated());

double val1 = lazy.get();

assertTrue(lazy.isEvaluated());

double val2 = lazy.get();

assertEquals(val1, val2, 0.1);

}

上面的例子中,我们执行的计算是Math.random。当我们调用isEvaluated检查状态时,发现函数并没有被执行。随后调用get方法,我们得到计算的结果。第2次调用get时,再次返回之前计算的结果,而之前的计算结果已被缓存。

9. 模式匹配Pattern Matching

当我们执行一个计算或根据输入返回一个满足条件的值时,我们通常会用到if语句。

@Test

public void whenIfWorksAsMatcher_thenCorrect() {

int input = 3;

String output;

if (input == 0) {

output = "zero";

}

if (input == 1) {

output = "one";

}

if (input == 2) {

output = "two";

}

if (input == 3) {

output = "three";

}

else {

output = "unknown";

}

assertEquals("three", output);

}

上述代码仅仅执行若干比较与赋值操作,没个操作都需要3行代码,当条件数量大增时,代码将急剧膨胀。当改为switch时,情况似乎也没有好转。

在Vavr中,我们通过Match方法替换switch块。每个条件检查都通过Case方法调用来替换。 $()来替换条件并完成表达式计算得到结果。

@Test

public void whenMatchworks_thenCorrect() {

int input = 2;

String output = Match(input).of(

Case($(1), "one"),

Case($(2), "two"),

Case($(3), "three"),

Case($(), "?"));

assertEquals("two", output);

}

这样,代码变得紧凑,平均每个检查仅用一行。此外我们还可以通过谓词(predicate)来替换表达式。

Match(arg).of(

Case(isIn("-h", "--help"), o -> run(this::displayHelp)),

Case(isIn("-v", "--version"), o -> run(this::displayVersion)),

Case($(), o -> run(() -> {

throw new IllegalArgumentException(arg);

}))

);

 类似资料: