Где свято?
Как мы все знаем, Java8 в определенной степени поддерживает функциональное программирование, но функциональный API, предоставляемый стандартной библиотекой, не очень полный и удобный.
Чтобы лучше выполнять функциональное программирование, приходится прибегать к сторонним библиотекам, и VAVR является лидером в этом отношении, что позволяет эффективно уменьшить объем кода и улучшить качество кода.
VAVRНеизвестно, его предшественником был Javaslang, выпущенный в 2014 году, и в настоящее время он имеет почти 4 тысячи звезд на github.
Увидев это, многие люди сказали, что я был хедлайнером вечеринки.Библиотека Java также собирается ниспровергнуть Java?
это не я играюударное тело, откройте VAVRОфициальный сайт, его первая страница гласит: «vavr - turns java™с ног на голову
Разве это не означает подрывную деятельность Java?
Съедобные рекомендации
Для чтения этой статьи читатели должны иметь определенное представление о синтаксисе лямбда-выражений Java8 и общих API.
Поскольку это введение в фреймворк (продвижение по земле), чтобы избежать перевода официальных документов, эта статья будет иметь некоторые ограничения.
- Не исчерпаны все функции и API
- не буду вдаваться в детали источника
Что касается примера кода, он в основном будет представлен в виде модульных тестов и гарантированно пройдет
Примечание. В этой статье используется версия VAVR 0.10.3, а версия JDK — 11.
Давайте сделаем обзор
Коллекция, новый старт
Должен сказать, что библиотека коллекций Java8 действительно полезна после введения Stream, но именно из-за использования Stream мне приходится писать много шаблонного кода, что снижает массу опыта.
// of 方法是 Java9 开始提供的静态工厂
java.util.List.of(1, 2, 3, 4, 5)
.stream()
.filter(i -> i > 3)
.map(i -> i * 2)
.collect(Collectors.toList());
Более того, сама библиотека-коллекция Java является изменяемой, что явно нарушает основную черту функционального программирования - неизменяемость.По этой причине VAVR разработал новый набор библиотек-коллекций, а опыт использования бесконечно близок к Scala.
Более чистый API
io.vavr.collection.List.of(1, 2, 3, 4, 5)
.filter(i -> i > 3)
.map(i -> i * 2);
Добавление данных в коллекцию приводит к созданию новой коллекции, которая гарантированно будет неизменной.
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]
Сильная совместимость, может быть легко преобразована с помощью стандартной библиотеки коллекций Java.
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();
Давайте рассмотрим немного более сложный пример: отфильтруйте группу пользователей, которые являются взрослыми, сгруппируйте их по возрасту и отобразите в каждой группе только имя пользователя.
/**
* 用户信息
*/
@Data
class User {
private Long id;
private String name;
private Integer age;
}
Сначала используйте стандартную библиотеку коллекций Java для выполнения этого требования, вы можете увидетьcollect(...)Этот длинный список вложений действительно неудобен
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())));
}
Давайте посмотрим на реализацию VAVR, она проще и понятнее?
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));
}
Библиотека коллекций VAVR предоставляет более функциональные API, такие как
- take(Integer) принимает первые n значений
- tail() берет коллекцию, кроме головного узла
- zipWithIndex() упрощает получение индекса (без fori)
- find(Predicate) основан на условном значении запроса, что может быть достигнуто только с помощью filter + findFirst в стандартной библиотеке Java.
- .....
Хотя во всех примерах кода используется список, перечисленные выше функции можно использовать в Queue, Set и Map, и все они поддерживают преобразование со стандартной библиотекой Java.
Кортежи, недостающая конструкция Java
Студенты, знакомые с Haskell и Scala, должны быть знакомы со структурой данных «кортеж».
Кортеж подобен массиву, который может хранить объекты разных типов и поддерживать информацию об их типе, поэтому приведение не требуется при получении значения.
// scala 的元组,用括号构建
val tup = (1, "ok", true)
// 按索引取值,执行对应类型的操作
val sum = tup._1 + 2 // int 加法
val world = "hello "+tup._2 // 字符串拼接
val res = !tup._3 // 布尔取反
Конечно, в Java нет собственного синтаксиса для поддержки создания кортежей, а в стандартной библиотеке нет классов, связанных с кортежами.
Однако VAVR реализует кортежи через дженерики, черезTupleСтатическая фабрика, мы можем очень легко создавать кортежи (с синтаксисом var Java10 не слишком красиво)
import io.vavr.Tuple;
public TupleTest {
@Test
public void testTuple() {
// 一元组
var oneTuple = Tuple.of("string");
String oneTuple_1 = oneTuple._1;
// 二元组
var twoTuple = Tuple.of("string", 1);
String twoTuple_1 = twoTuple._1;
Integer twoTuple_2 = twoTuple._2;
// 五元组
var threeTuple = Tuple.of("string", 2, 1.2F, 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;
}
}
если нетvar, вы должны написать следующее подробное определение переменной
Tuple5<String, Integer, Float, Double, Character> tuple5 = Tuple.of("string", 2, 1.2F, 2.4D, 'c');
В настоящее время VAVR поддерживает создание октуплетов максимум, то есть до 8 типов, а не до 8 значений.
Когда кортежи используются в сочетании с «сопоставлением с образцом», это еще более серьезный беспорядок.
PS: хоть и рано говорить о сопоставлении с образцом (мы еще встретимся позже), мы все же можем почувствовать его заранее.
var tup = Tuple.of("hello", 1);
// 模式匹配
Match(tup).of(
Case($Tuple2($(is("hello")), $(is(1))), (t1, t2) -> run(() -> {})),
Case($Tuple2($(), $()),(t1, t2) ->run(() -> {}))
);
Приведенный выше код фактически эквивалентен if...else
// 等同于 if...else
if (tup._1.equalas("hello") && tup._2 == 1) {
// ... do something
} else {
// ... do something
}
В дополнение к Option есть Try, Choose, Future...
Java8 представила необязательный вариант для решения печально известного исключения NullPointerException, и у VAVR есть аналогичный инструмент —Option, но у него другой дизайн.
В дополнение к Option, VAVR также реализует функциональные структуры, такие как Try, Someone, Future и т. д., которые являются очень мощными инструментами, недоступными в стандартной библиотеке Java.
Option
Optionсо стандартной библиотекой JavaOptionalОчень похожи, оба представляют собой необязательное значение, но их дизайн очень отличается. (Дизайн VAVR Option ближе к Scala)
В ВАВР,Optionэто интерфейс, конкретными реализациями которого являются Some и None
- Some: представляет значение
- Нет: означает отсутствие значения
Вы можете проверить с помощью следующего модульного теста
@Test
public void testOption() {
// 通过 of 工厂方法构造
Assert.assertTrue(Option.of(null) instanceof Option.None);
Assert.assertTrue(Option.of(1) instanceof Option.Some);
// 通过 none 或 some 构造
Assert.assertTrue(Option.none() instanceof Option.Some);
Assert.assertTrue(Option.some(1) instanceof Option.Some);
}
И дляjava.util.OptionalДругими словами, независимо от того, как он построен, это один и тот же тип.
@Test
public void testOptional() {
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);
}
Почему между ними такая разница в дизайне?
По сути, это другой ответ на вопрос «Является ли роль Option безопасным вычисление нуля?».
Следующие два метода тестирования с одинаковой логикой используют Option и Optional, но дают разные результаты.
@Test
public void testWithJavaOptional() {
// Java Optional
var result = Optional.of("hello")
.map(str -> (String) null)
.orElseGet(() -> "world");
// result = "world"
Assert.assertEquals("word", result);
}
@Test
public void testWithVavrOption() {
// Vavr Option
var result = Option.of("hello")
.map(str -> (String) null)
.getOrElse(() -> "world");
// result = null
Assert.assertNull(result);
}
В тестовом коде VAVR передайтеOptional.of("hello")на самом деле получилSome("hello")объект.
тогда позвониmap(str -> (String)null)вернулся по-прежнемуSomeобъект (Some представляет значение), поэтому окончательный результат = null, а неgetOrElse(() -> "world")Возвращаемая мировая строка.
В тестовом коде на Java вызовитеmap(str -> null), Необязательный был переключен на Необязательный.пустой, поэтому он, наконец, возвращаетorElseGet(() -> "world")результат.
Это также тот момент, когда функциональные разработчики критикуют дизайн java.util.Optional.
Помимо разницы в дизайне, io.vavr.control.Option имеет более дружественные API, чем java.util.Optional.
@Test
public void testVavrOption() {
// option 直接转为 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);
}
Учитывая совместимость со стандартной библиотекой, Option можно легко конвертировать в и обратно.
Option.of("toJava").toJavaOptional();
Option.ofOptional(Optional.empty());
Try
Try похож на Option, а также похож на «контейнер», за исключением того, что он содержитвозможно неправильное поведение, вы сразу подумали о конструкции try..catch?
try {
//..
} catch (Throwable t) {
//...
} finally {
//....
}
С помощью Try VAVR также можно реализовать еще один более функциональный try...catch.
/**
* 输出
* 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");
});
Try — это тоже интерфейс, конкретная реализация — это Success или Failure.
- Успех: представляет выполнение без исключения
- Ошибка: представляет собой исключение из выполнения
Как и Optoins, он также может быть построен с помощью фабричного метода.
@Test
public void testTryInstance() {
// 除以 0 ,构建出 Failure
var error = Try.of(() -> 0 / 0);
Assert.assertTrue(error instanceof Try.Failure);
// 合法的加法,构建出 Success
var normal = Try.of(() -> 1 + 1);
Assert.assertTrue(normal instanceof Try.Success);
}
С помощью метода recoveryWith метода Try мы можем элегантно реализовать стратегию перехода на более раннюю версию.
@Test
public void testTryWithRecover() {
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();
}
Для результата расчета Try его можно преобразовать по карте или легко преобразовать с помощью Option.
Вы также можете использовать карту для преобразования результата и взаимодействия с Option.
@Test
public void testTryMap() {
String res = Try.of(() -> "hello world")
.map(String::toUpperCase)
.toOption()
.getOrElse(() -> "default");
Assert.assertEquals("HELLO WORLD", res);
}
Future
Это будущее неjava.util.concurrent.Future, но оба являются абстракцией над результатом асинхронного вычисления.
ваврFutureЛучше чемjava.util.concurrent.FutureБолее удобный механизм обратного вызова
- onFailure Неудачный обратный вызов
- Обратный вызов успеха onSuccess
@Test
public void testFutureFailure() {
final var word = "hello world";
io.vavr.concurrent.Future
.of(Executors.newFixedThreadPool(1), () -> word)
.onFailure(throwable -> Assert.fail("不应该走到 failure 分支"))
.onSuccess(result -> Assert.assertEquals(word, result));
}
@Test
public void testFutureSuccess() {
io.vavr.concurrent.Future
.of(Executors.newFixedThreadPool(1), () -> {
throw new RuntimeException();
})
.onFailure(throwable -> Assert.assertTrue(throwable instanceof RuntimeException))
.onSuccess(result -> Assert.fail("不应该走到 success 分支"));
}
Его также можно конвертировать с Java CompleableFuture.
Future.of(Executors.newFixedThreadPool(1), () -> "toJava").toCompletableFuture();
Future.fromCompletableFuture(CompletableFuture.runAsync(() -> {}));
разное
Наконец, давайте кратко поговорим об ином, и ленивом.
-
Либо Указывает, что значение может быть одного из двух типов, например:
compute()Либо возвращаемое значение функции представляет собой структуру, которая может быть Exception или String.Обычно право используется для представления правильного значения (право в английском языке имеет правильное значение)
public Either<Exception, String> compute() { //... } public void test() { Either<Exception, String> either = compute(); // 异常值 if (either.isLeft()) { Exception exception = compute().getLeft(); throw new RuntimeException(exception); } // 正确值 if (either.isRight()) { String result = compute().get(); // ... } } -
Ленивый также является контейнером, он может задерживать вычисление до тех пор, пока вычисление не будет вызвано в первый раз, после первого вызова результат будет кэширован, а последующие вызовы могут получить результат напрямую.
Lazy<Double> lazy = Lazy.of(Math::random); lazy.isEvaluated(); // = false lazy.get(); // = 0.123 (random generated) lazy.isEvaluated(); // = true lazy.get(); // = 0.123 (memoized)
существуетio.vavr.APIВ Scala предусмотрено множество статических методов для имитации синтаксических конструкций Scala Option, Try и этих структур, но их следует использовать в сочетании со статическим импортом Java.
import static io.vavr.API.*;
@Test
public void testAPI() {
// 构造 Option
var some = Some(1);
var none = None();
// 构造 Future
var future = Future(() -> "ok");
// 构造 Try
var tryInit = Try(() -> "ok");
}
Конечно, имя функции, начинающееся с заглавной буквы, немного не соответствует соглашению об именах методов Java, поэтому его можно рассматривать как метод Hack.
Для получения более подробной информации желающие могут перейти наОфициальная документация сайтаучиться
Сопоставление с образцом: возмездие if..else
Образец здесь относится кСхема композиции структуры данных, в Scala вы можете использовать сопоставление с образцом напрямую через ключевое слово match
def testPatternMatch(nameOpt: Option[String], nums: List[Int]) = {
/**
* 匹配 Option 的结构
*/
nameOpt match {
case Some(name) => println(s"你好,$name")
case None => println("无名之辈")
}
/**
* 匹配 List 的结构
*/
nums match {
case Nil => println("空列表")
case List(v) => println(s"size=1 $v")
case List(v, v2) => println(s"size=2 $v、 $v2")
case _ => println("size > 2")
}
}
В Java нет понятия сопоставления с образцом и, естественно, нет связанного с ним синтаксиса (переключатель не в счет).
Однако VAVR использует ООП для реализации сопоставления с образцом, хотя это и не сравнимо с собственным опытом Scala, но весьма близко.
Java вJEP 375: Pattern Matching for instanceofВ предложении реализована функция сопоставления с образцом для instanceof (ожидается, что она будет выпущена в Java15), но я думаю, что эта функция все еще далека от сопоставления с образцом в Scala.
Давайте реализуем требование форматировать значения BMI в текстовые описания, сначала используя императивный стиль Java.
public String bmiFormat(double height, double weight) {
double bmi = weight / (height * height);
String desc;
if (bmi < 18.5) {
desc = "有些许晃荡!";
} else if (bmi < 25) {
desc = "继续加油哦!";
} else if (bmi < 30) {
desc = "你是真的稳!";
} else {
desc = "难受!";
}
return desc;
}
Давайте проведем рефакторинг с сопоставлением шаблонов VAVR и удалим их if..else.
Чтобы сделать грамматику более удобной, лучше пройтиstatic importИмпорт API.
import static io.vavr.API.*;
Ниже приведен фрагмент кода с рефакторингом
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), () -> "有些许晃荡!"),
// else if (bmi < 25)
Case($(v -> v < 25), () -> "继续加油哦!"),
// else if (bmi < 30)
Case($(v -> v < 30), () -> "你是真的稳!"),
// else
Case($(), () -> "难受!")
);
}
-
Match(...), Case(...), $(...) все
io.vavr.APIСтатический метод для имитации синтаксиса «сопоставления с образцом». -
Последний $() означает соответствие всем случаям, кроме приведенного выше.
Чтобы облегчить понимание читателей, я кратко перечислил сигнатуры каждого метода (методы Case и $ имеют много перегрузок, поэтому я не буду перечислять их все)
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<? super T> predicate) {...}
ofэто метод объекта Match
public final <R> R of(Case<? extends T, ? extends R>... cases) {...}
Давай, покажи мне свою собственную грамматическую память
匹配一下(这个东西)的结构,是不是下面的情况之一
// Match(XXX).Of(
- 结构和 A 一样,做点什么事情
//Case( $(A), () -> doSomethingA() ),
- 结构和 B 一样,做点什么事情
//Case( $(B), () -> doSomethingB() ),
- .....
- 和上面的结构都不一样,也做点事情
//Case( $(), () -> doSomethingOthers())
//);
Когда сопоставление с образцом сочетается с вышеупомянутым Option, Try, Someone, Tuple, это комбинация 1 + 1 > 3.
В следующем коде показано, как «сопоставление с образцом» делает Option еще более мощным.
import static io.vavr.API.*;
import static io.vavr.Patterns.$None;
import static io.vavr.Patterns.$Some;
public class PatternMatchTest {
@Test
public void testMatchNone() {
// 匹配 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() {
// 匹配某一个值为 Nice 的 Some
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() {
// 匹配 Some,值任意
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() {
Assert.fail("不应该执行该分支");
return false;
}
}
Есть еще Try, кстати, иногда, когда Case не имеет возвращаемого значения, второй параметр можно заменить на 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() {
var res = Try.of(() -> {
throw new RuntimeException();
});
Match(res).of(
// 匹配成功情况
Case($Success($()), r -> run(Assert::fail)),
// 匹配异常为 RuntimeException
Case($Failure($(instanceOf(RuntimeException.class))), r -> true),
// 匹配异常为 IllegalStateException
Case($Failure($(instanceOf(IllegalStateException.class))), r -> run(Assert::fail)),
// 匹配异常为 NullPointerException
Case($Failure($(instanceOf(NullPointerException.class))), r -> run(Assert::fail)),
// 匹配其余失败的情况
Case($Failure($()), r -> run(Assert::fail))
);
}
@Test
public void testMatchSuccess() {
var res = Try.of(() -> "Nice");
Match(res).of(
// 匹配任意成功的情况
Case($Success($()), r -> run(() -> Assert.assertEquals("Nice", r))),
// 匹配任意失败的情况
Case($Failure($()), r -> run(Assert::fail))
);
}
}
Теперь вернитесь к коду для кортежей и попробуйте написать собственное сопоставление с образцом для троек.
Наконец
В этой статье представлены только некоторые часто используемые функции. Кроме того, VAVR также поддерживает расширенные функции, такие как Curring, Memoization и Partial application.Если вы хотите узнать больше, вы можете перейти на официальный сайт, чтобы узнать больше.
Наконец, этот кирпич был выброшен, может ли он привести к вашему нефриту?
рекламировать:
Если вы ищете проект на основе Java9+ для изучения новых функций, я рекомендую его.PrettyZoo,
Это настольный клиент zookeeper, разработанный на основе Java11, с использованием модуляризации, var и многих других новых функций, добро пожаловать, чтобы пометить, разветвить, выпустить.