Рубрики
Без рубрики

Руководство по сопоставлению шаблонов в Var

Краткое и практическое руководство по использованию функций сопоставления шаблонов в различных

Автор оригинала: baeldung.

1. Обзор

В этой статье мы сосредоточимся на сопоставлении шаблонов с Var. Если вы не знаете, что такое Var, пожалуйста, сначала прочитайте обзор Var //.

Сопоставление шаблонов-это функция, которая изначально недоступна в Java. Можно было бы подумать об этом как о продвинутой форме переключателя/| оператора.

Преимущество сопоставления шаблонов Vavr заключается в том, что оно избавляет нас от написания стеков switch случаев или if-then-else операторов. Таким образом, уменьшает объем кода и представляет условную логику в удобочитаемом для человека виде.

Мы можем использовать API сопоставления шаблонов, выполнив следующий импорт:

import static io.vavr.API.*;

2. Как Работает Сопоставление Шаблонов

Как мы видели в предыдущей статье, сопоставление шаблонов можно использовать для замены переключателя блока:

@Test
public void whenSwitchWorksAsMatcher_thenCorrect() {
    int input = 2;
    String output;
    switch (input) {
    case 0:
        output = "zero";
        break;
    case 1:
        output = "one";
        break;
    case 2:
        output = "two";
        break;
    case 3:
        output = "three";
        break;
    default:
        output = "unknown";
        break;
    }

    assertEquals("two", output);
}

Или несколько если утверждений:

@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);
}

Фрагменты, которые мы видели до сих пор, являются подробными и, следовательно, подвержены ошибкам. При использовании сопоставления шаблонов мы используем три основных строительных блока: два статических метода Совпадение , Случай и атомарные шаблоны.

Атомарные шаблоны представляют условие, которое должно быть оценено для возврата логического значения:

  • $() : $() : шаблон подстановочных знаков, аналогичный по умолчанию
  • регистру в инструкции switch. Он обрабатывает сценарий, в котором совпадение не найдено $(значение)
  • : это шаблон равенства, в котором значение просто равно-по сравнению с входными данными. $(предикат)

Подходы switch и if могут быть заменены более коротким и кратким фрагментом кода, как показано ниже:

@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);
}

Если входные данные не совпадают, шаблон подстановочных знаков оценивается:

@Test
public void whenMatchesDefault_thenCorrect() {
    int input = 5;
    String output = Match(input).of(
      Case($(1), "one"), 
      Case($(), "unknown"));

    assertEquals("unknown", output);
}

Если шаблон подстановочных знаков отсутствует и входные данные не совпадают, мы получим сообщение об ошибке совпадения:

@Test(expected = MatchError.class)
public void givenNoMatchAndNoDefault_whenThrows_thenCorrect() {
    int input = 5;
    Match(input).of(
      Case($(1), "one"), 
      Case($(2), "two"));
}

В этом разделе мы рассмотрели основы сопоставления шаблонов Var, а в следующих разделах будут рассмотрены различные подходы к решению различных случаев, с которыми мы, вероятно, столкнемся в нашем коде.

3. Сопоставьте С Опцией

Как мы видели в предыдущем разделе, шаблон подстановочных знаков $() соответствует случаям по умолчанию, когда для входных данных не найдено совпадения.

Однако другой альтернативой включению шаблона подстановочных знаков является перенос возвращаемого значения операции сопоставления в экземпляр Option :

@Test
public void whenMatchWorksWithOption_thenCorrect() {
    int i = 10;
    Option s = Match(i)
      .option(Case($(0), "zero"));

    assertTrue(s.isEmpty());
    assertEquals("None", s.toString());
}

Чтобы лучше понять Вариант в Var, вы можете обратиться к вводной статье.

4. Сопоставление Со Встроенными Предикатами

Vavr поставляется с некоторыми встроенными предикатами, которые делают наш код более удобочитаемым для человека. Поэтому наши начальные примеры могут быть дополнительно улучшены с помощью предикатов:

@Test
public void whenMatchWorksWithPredicate_thenCorrect() {
    int i = 3;
    String s = Match(i).of(
      Case($(is(1)), "one"), 
      Case($(is(2)), "two"), 
      Case($(is(3)), "three"),
      Case($(), "?"));

    assertEquals("three", s);
}

Вавр предлагает больше предикатов, чем это. Например, мы можем заставить ваше условие проверять класс входных данных вместо этого:

@Test
public void givenInput_whenMatchesClass_thenCorrect() {
    Object obj=5;
    String s = Match(obj).of(
      Case($(instanceOf(String.class)), "string matched"), 
      Case($(), "not string"));

    assertEquals("not string", s);
}

Или независимо от того, является ли ввод нулевым или нет:

@Test
public void givenInput_whenMatchesNull_thenCorrect() {
    Object obj=5;
    String s = Match(obj).of(
      Case($(isNull()), "no value"), 
      Case($(isNotNull()), "value found"));

    assertEquals("value found", s);
}

Вместо сопоставления значений в равно стиле мы можем использовать содержит стиль. Таким образом, мы можем проверить, существует ли вход в списке значений с предикатом isIn :

@Test
public void givenInput_whenContainsWorks_thenCorrect() {
    int i = 5;
    String s = Match(i).of(
      Case($(isIn(2, 4, 6, 8)), "Even Single Digit"), 
      Case($(isIn(1, 3, 5, 7, 9)), "Odd Single Digit"), 
      Case($(), "Out of range"));

    assertEquals("Odd Single Digit", s);
}

Есть еще кое-что, что мы можем сделать с предикатами, например, объединить несколько предикатов в один случай совпадения.Чтобы соответствовать только тогда, когда входные данные передают всю заданную группу предикатов, мы можем И предикаты использовать allOf предикат.

Практическим случаем было бы, если бы мы хотели проверить, содержится ли число в списке, как мы делали в предыдущем примере. Проблема в том, что список также содержит нули. Итак, мы хотим применить фильтр, который, помимо отклонения чисел, которых нет в списке, также будет отклонять нули:

@Test
public void givenInput_whenMatchAllWorks_thenCorrect() {
    Integer i = null;
    String s = Match(i).of(
      Case($(allOf(isNotNull(),isIn(1,2,3,null))), "Number found"), 
      Case($(), "Not found"));

    assertEquals("Not found", s);
}

Чтобы сопоставить, когда входные данные соответствуют любой из данной группы, мы можем ИЛИ предикаты, используя любой из предикатов.

Предположим, что мы проверяем кандидатов по их году рождения, и нам нужны только кандидаты, родившиеся в 1990,1991 или 1992 годах.

Если такой кандидат не найден, то мы можем принять только тех, кто родился в 1986 году, и мы хотим, чтобы это было ясно и в вашем коде:

@Test
public void givenInput_whenMatchesAnyOfWorks_thenCorrect() {
    Integer year = 1990;
    String s = Match(year).of(
      Case($(anyOf(isIn(1990, 1991, 1992), is(1986))), "Age match"), 
      Case($(), "No age match"));
    assertEquals("Age match", s);
}

Наконец, мы можем убедиться, что никакие предоставленные предикаты не совпадают, используя метод none Of .

Чтобы продемонстрировать это, мы можем отменить условие в предыдущем примере таким образом, чтобы мы получили кандидатов, которые не относятся к вышеуказанным возрастным группам:

@Test
public void givenInput_whenMatchesNoneOfWorks_thenCorrect() {
    Integer year = 1990;
    String s = Match(year).of(
      Case($(noneOf(isIn(1990, 1991, 1992), is(1986))), "Age match"), 
      Case($(), "No age match"));

    assertEquals("No age match", s);
}

5. Сопоставление С Пользовательскими Предикатами

В предыдущем разделе мы исследовали встроенные предикаты Vavr. Но Вар на этом не останавливается. Обладая знаниями лямбд, мы можем создавать и использовать наши собственные предикаты или даже просто записывать их в строку.

С помощью этих новых знаний мы можем встроить предикат в первый пример предыдущего раздела и переписать его следующим образом:

@Test
public void whenMatchWorksWithCustomPredicate_thenCorrect() {
    int i = 3;
    String s = Match(i).of(
      Case($(n -> n == 1), "one"), 
      Case($(n -> n == 2), "two"), 
      Case($(n -> n == 3), "three"), 
      Case($(), "?"));
    assertEquals("three", s);
}

Мы также можем применить функциональный интерфейс вместо предиката, если нам понадобится больше параметров. Пример contains можно переписать следующим образом, хотя и немного более подробно, но это дает нам больше власти над тем, что делает наш предикат:

@Test
public void givenInput_whenContainsWorks_thenCorrect2() {
    int i = 5;
    BiFunction, Boolean> contains 
      = (t, u) -> u.contains(t);

    String s = Match(i).of(
      Case($(o -> contains
        .apply(i, Arrays.asList(2, 4, 6, 8))), "Even Single Digit"), 
      Case($(o -> contains
        .apply(i, Arrays.asList(1, 3, 5, 7, 9))), "Odd Single Digit"), 
      Case($(), "Out of range"));

    assertEquals("Odd Single Digit", s);
}

В приведенном выше примере мы создали функцию Java 8 Bi , которая просто проверяет связь isIn между двумя аргументами.

Для этого вы также могли бы использовать функцию /Вавра|/. Поэтому, если встроенные предикаты не совсем соответствуют вашим требованиям или вы хотите контролировать всю оценку, используйте пользовательские предикаты.

6. Декомпозиция объекта

Декомпозиция объекта-это процесс разбиения объекта Java на составные части. Например, рассмотрим случай абстрагирования биографических данных сотрудника наряду с информацией о занятости:

public class Employee {

    private String name;
    private String id;

    //standard constructor, getters and setters
}

Мы можем разложить запись сотрудника на составные части: имя и идентификатор . Это совершенно очевидно в Java:

@Test
public void givenObject_whenDecomposesJavaWay_thenCorrect() {
    Employee person = new Employee("Carl", "EMP01");

    String result = "not found";
    if (person != null && "Carl".equals(person.getName())) {
        String id = person.getId();
        result="Carl has employee id "+id;
    }

    assertEquals("Carl has employee id EMP01", result);
}

Мы создаем объект employee, затем сначала проверяем, является ли он нулевым, прежде чем применять фильтр, чтобы убедиться, что мы получим запись сотрудника, имя которого Карл . Затем мы идем дальше и получаем его идентификатор . Способ Java работает, но он многословен и подвержен ошибкам.

То, что мы в основном делаем в приведенном выше примере, – это сопоставление того, что мы знаем, с тем, что приходит. Мы знаем , что нам нужен сотрудник по имени Карл , поэтому мы пытаемся сопоставить это имя с входящим объектом.

Затем мы разбиваем его данные, чтобы получить удобочитаемый вывод. Нулевые проверки-это просто защитные накладные расходы, которые нам не нужны.

С помощью API сопоставления шаблонов Vavr мы можем забыть о ненужных проверках и просто сосредоточиться на том, что важно, в результате чего получается очень компактный и читаемый код.

Чтобы использовать это положение, в вашем проекте должна быть установлена дополнительная зависимость var-match . Вы можете получить его, перейдя по этой ссылке .

Приведенный выше код затем может быть написан следующим образом:

@Test
public void givenObject_whenDecomposesVavrWay_thenCorrect() {
    Employee person = new Employee("Carl", "EMP01");

    String result = Match(person).of(
      Case(Employee($("Carl"), $()),
        (name, id) -> "Carl has employee id "+id),
      Case($(),
        () -> "not found"));
         
    assertEquals("Carl has employee id EMP01", result);
}

Ключевыми конструкциями в приведенном выше примере являются атомарные шаблоны $(“Карл”) и $() , шаблон значений и шаблон подстановочных знаков соответственно. Мы подробно обсудили их во вступительной статье Var .

Оба шаблона извлекают значения из соответствующего объекта и сохраняют их в параметрах лямбда. Шаблон значения $(“Карл”) может совпадать только в том случае, если полученное значение соответствует тому, что находится внутри него, т. е. Карл .

С другой стороны, шаблон подстановочный знак $() соответствует любому значению в его позиции и возвращает значение в параметр id лямбда.

Чтобы эта декомпозиция работала, нам нужно определить шаблоны декомпозиции или то, что формально известно как неприменимые шаблоны.

Это означает, что мы должны научить API сопоставления шаблонов, как декомпозировать наши объекты, в результате чего для каждого объекта, подлежащего декомпозиции, будет создана одна запись:

@Patterns
class Demo {
    @Unapply
    static Tuple2 Employee(Employee Employee) {
        return Tuple.of(Employee.getName(), Employee.getId());
    }

    // other unapply patterns
}

Инструмент обработки аннотаций создаст класс с именем DemoPatterns.java которые мы должны статически импортировать туда, куда мы хотим применить эти шаблоны:

import static com.baeldung.vavr.DemoPatterns.*;

Мы также можем разложить встроенные объекты Java.

Например, java.time.LocalDate можно разложить на год, месяц и день месяца. Давайте добавим его неприменимый шаблон в Demo.java :

@Unapply
static Tuple3 LocalDate(LocalDate date) {
    return Tuple.of(
      date.getYear(), date.getMonthValue(), date.getDayOfMonth());
}

Затем тест:

@Test
public void givenObject_whenDecomposesVavrWay_thenCorrect2() {
    LocalDate date = LocalDate.of(2017, 2, 13);

    String result = Match(date).of(
      Case(LocalDate($(2016), $(3), $(13)), 
        () -> "2016-02-13"),
      Case(LocalDate($(2016), $(), $()),
        (y, m, d) -> "month " + m + " in 2016"),
      Case(LocalDate($(), $(), $()),  
        (y, m, d) -> "month " + m + " in " + y),
      Case($(), 
        () -> "(catch all)")
    );

    assertEquals("month 2 in 2017",result);
}

7. Побочные эффекты при подборе шаблонов

По умолчанию Match действует как выражение, то есть возвращает результат. Однако мы можем заставить его вызвать побочный эффект, используя вспомогательную функцию run в лямбде.

Он принимает ссылку на метод или лямбда-выражение и возвращает Void.

Рассмотрим сценарий , в котором мы хотим напечатать что-то, когда ввод представляет собой однозначное четное целое число, и другое, когда ввод представляет собой однозначное нечетное число, и создадим исключение, когда ввод не является ни одним из них.

Принтер четных чисел:

public void displayEven() {
    System.out.println("Input is even");
}

Принтер с нечетным числом:

public void displayOdd() {
    System.out.println("Input is odd");
}

И функция совпадения:

@Test
public void whenMatchCreatesSideEffects_thenCorrect() {
    int i = 4;
    Match(i).of(
      Case($(isIn(2, 4, 6, 8)), o -> run(this::displayEven)), 
      Case($(isIn(1, 3, 5, 7, 9)), o -> run(this::displayOdd)), 
      Case($(), o -> run(() -> {
          throw new IllegalArgumentException(String.valueOf(i));
      })));
}

Что бы напечатать:

Input is even

8. Заключение

В этой статье мы рассмотрели наиболее важные части API сопоставления шаблонов в Var. Действительно, теперь мы можем писать более простой и лаконичный код без подробных операторов switch и if, благодаря Var.

Чтобы получить полный исходный код этой статьи, вы можете проверить проект Github .