Java Optional API

Java

Мудрый человек сказал, что вы не настоящий Java-программист, если не имеете дело с исключениями нулевого указателя. Это, конечно, шутка, но исключения нулевого указателя действительно являются источником многих программных ошибок. Итак, в Java 8 введеноjava.util.Optional, Необязательный используется для представленияможет или не можетданные, которые можно использовать для решения проблемы исключений нулевого указателя.

Проще говоря, Необязательный используется, чтобы избежать такого кода:

String version = "UNKNOWN";
if(computer != null){
  Soundcard soundcard = computer.getSoundcard();
  if(soundcard != null){
    USB usb = soundcard.getUSB();
    if(usb != null){
      version = usb.getVersion();
    }
  }
}

Что, если он представлен опциональным? Вероятно, что-то вроде этого:

String version = computer.flatMap(Computer::getSoundcard)
                         .flatMap(Soundcard::getUSB)
                         .map(USB::getVersion)
                         .orElse("UNKNOWN");

Фактически, Необязательный — это «Может быть» в функциональном программировании.

Следующий пример находится вOptionalTest.javaсередина.

Создайте

Есть три способа создать необязательный, пустой, of и ofNullable.

empty

emptyИспользуется для создания пустого дополнительного

@Test
public void create_optional_with_empty() {
    Optional<String> empty = Optional.empty();
    assertFalse(empty.isPresent());
}

of

ofИспользуется для создания непустого опционального:

@Test
public void create_optional_with_of() {
    Optional<String> java = Optional.of("Java");
    assertTrue(java.isPresent());
}

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

@Test(expected = NullPointerException.class)
public void create_optional_with_of_with_null() {
    Optional.of(null);
}

ofNullable

ofNullableИспользуется для создания возможно пустого опционального:

@Test
public void create_optional_with_ofNullable() {
    Optional<String> java = Optional.ofNullable("Java");
    assertTrue(java.isPresent());

    Optional<Object> o = Optional.ofNullable(null);
    assertFalse(o.isPresent());
}

Проверить, существует ли значение

можно использоватьisPresentа такжеisEmptyПроверьте, не пусто ли значение Необязательный.

isPresent

Возвращает true, если значение в необязательном не равно null, в противном случае — false.

@Test
public void check_optional_with_isPresent() {
    Optional<String> java = Optional.ofNullable("java");
    Optional<Object> aNull = Optional.ofNullable(null);

    assertTrue(java.isPresent());
    assertFalse(aNull.isPresent());
}

isEmpty

Доступно с Java 11isEmpty.

isEmptyа такжеisPresentИ наоборот, возвращает true, если null.

@Test
public void check_optional_with_isEmpty() {
    Optional<String> java = Optional.ofNullable("java");
    Optional<Object> aNull = Optional.ofNullable(null);

    assertFalse(java.isEmpty());
    assertTrue(aNull.isEmpty());
}

условное действие

Условные действияifPresent,orElse,orElseGet,orElseThrow,or,ifPresentOrElse, их выполнение зависит от того, является ли значение Необязательного пустым.

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

if (name != null){
    System.out.println(name.length);
}

Необязательный использует функциональную альтернативу вышеуказанному.

ifPresent

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

@Test
public void condition_action_ifPresent() {
    Optional<String> java = Optional.ofNullable("java");
    java.ifPresent((value) -> System.out.println("ifPresent accept " + value));

    Optional<Object> aNull = Optional.ofNullable(null);
    aNull.ifPresent(value -> System.out.println("this will never execute"));
}

orElse

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

@Test
public void condition_action_orElse() {
    assertTrue(Optional.ofNullable("java").orElse("javascript").equals("java"));
    assertTrue(Optional.ofNullable(null).orElse("java").equals("java"));
}

orElseGet

orElseGet похож на orElse, но orElseGet принимает Поставщика, а значение, возвращаемое Поставщиком, используется как значение по умолчанию для Необязательного.

@Test
public void condition_action_orElseGet() {
    assertTrue(Optional.ofNullable("java").orElseGet(() -> "javascript").equals("java"));
    assertTrue(Optional.ofNullable(null).orElseGet(() -> "java").equals("java"));
}

Разница между orElse и orElseGet

orElseа такжеorElseGetСигнатуры функций различаются, но если бы мы хотели использовать возвращаемое значение той же функции в качестве значения по умолчанию для Optional , мы, вероятно, сделали бы это:

public String getDefaultName() {
    System.out.println("You got a default name");
    return "default";
}
    
@Test
public void difference_between_orElse_and_orElseGet() {
    Optional<String> java = Optional.of("java");

    System.out.println("orElse:");
    assertEquals("java", java.orElse(getDefaultName()));
    System.out.println("orElseGet:");
    assertEquals("java", java.orElseGet(this::getDefaultName));
}

Если java имеет значение null или orElse и orElseGet не отличаются друг от друга, метод getDefaultName выполняется и возвращает значение как значение по умолчанию для параметра Optional.

Если в приведенном выше примере java не равно null, то вызов orElse getDefaultName все равно будет выполняться, а orElseGet — нет. вывод:

orElse:
You got a default name
orElseGet:

Будьте осторожны, когда getDefaultName имеет побочные эффекты или операции, требующие много времени.

orElseThrow

orElseThrow, как и orElse, также срабатывает, когда необязательное значение равно null, но вместо этого генерирует указанное исключение:

@Test(expected = IllegalArgumentException.class)
public void condition_action_orElseThrow() {
    Optional.ofNullable(null).orElseThrow(IllegalArgumentException::new);
}

or

или это новый метод в Java 9. Подобно orElseGet, или принимает Поставщика, но или возвращает новый Необязательный.

@Test
public void condition_or_optional() {
    Optional<String> java = Optional.of("java")
                                    .or(() -> Optional.of("javascript"));
    Optional<Object> java1 = Optional.empty()
                                     .or(() -> Optional.of("java"));
    assertEquals("java", java.get());
    assertEquals("java", java1.get());
}

ifPresentOrElse

ifPresentOrElse — это новый метод в Java 9. ifPresent похож на императивное программированиеif-elseОн принимает два параметра, первый из потребителей, необязательный, вызываемый, когда есть значение, второй - это Runnable, вызывается, когда нет значения:

@Test
public void condition_ifPresentOrElse() {
    // value is java
    Optional.of("java")
            .ifPresentOrElse(value -> System.out.println("value is " + value), () -> System.out.println("ooops"));

    // ooops
    Optional.empty()
            .ifPresentOrElse(value -> System.out.println("value is " + value), () -> System.out.println("ooops"));
}

получить значение

Опционально предоставляетgetМетод получает значение, но метод get можно использовать только в том случае, если у параметра Optional есть значение, в противном случае он выдастNoSuchElementExceptionаномальный:

@Test
public void get_optional_with_of() {
    Optional<String> java = Optional.of("Java");
    assertEquals("java", java.get());
}

@Test(expected = NoSuchElementException.class)
public void get_optional_with_of_with_null() {
    Optional.empty().get();
}

Проверить значение

filterМетод используется для проверки того, соответствует ли значение параметра «Необязательный» условиям, и принимает предикат в качестве параметра. Если значение Optional равно null или предикат не работает, верните пустое значение; в противном случае верните Optional.

@Test
public void test_optional_by_filter() {
    Integer nullYear = null;
    Optional<Integer> integer = Optional.ofNullable(nullYear)
                                        .filter(value -> value == 2018);
    assertEquals(Optional.empty(), integer);

    Integer year = 2019;
    Optional<Integer> integer1 = Optional.ofNullable(year)
                                         .filter(value -> value == 2018);
    assertEquals(Optional.empty(), integer1);

    Optional<Integer> integer2 = Optional.ofNullable(year)
                                         .filter(value -> value == 2019);
    assertEquals("Optional[2019]", integer2.toString());
}

filter экономит много шаблонного кода по сравнению с традиционным if, например:

public boolean priceIsInRange1(Modem modem) {
    boolean isInRange = false;
 
    if (modem != null && modem.getPrice() != null
      && (modem.getPrice() >= 10
        && modem.getPrice() <= 15)) {
 
        isInRange = true;
    }
    return isInRange;
}

Используйте Необязательно для достижения того же подхода:

public boolean priceIsInRange2(Modem modem2) {
     return Optional.ofNullable(modem2)
       .map(Modem::getPrice)
       .filter(p -> p >= 10)
       .filter(p -> p <= 15)
       .isPresent();
}

Значение процесса

Есть карта и плоская карта для обработки значений.

map

Используйте карту для обработки и возврата значений в необязательном.

@Test
public void map_optional() {
    Optional<String> java = Optional.of("java");
    String result = java.map(String::toUpperCase).orElse("");
    assertEquals("JAVA", result);
}

flatMap

Преобразователь, передаваемый flatMap, требует, чтобы возвращаемое значение было подклассом Optional, и пользователи могут настраивать класс-оболочку.

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public Optional<String> getName() {
        return Optional.ofNullable(name);
    }
}

@Test
public void flatMap_optional() {
    Person person = new Person("john");
    Optional<Person> personOptional = Optional.of(person);

    String byMap = personOptional.map(Person::getName)
                                 // 需要手动打开包装
                                 .orElse(Optional.empty())
                                 .orElse("");

    String byFlatMap = personOptional.flatMap(Person::getName)
                                     .orElse("");

    assertEquals("john", byMap);
    assertEquals("john", byFlatMap);
}

потоковая операция

В Java 9 был добавлен метод потока для создания потока в необязательном, после чего можно использовать все методы в потоке.

Если необязательный параметр пуст, создайте пустой поток.

@Test
public void treat_optional_as_stream() {
    List<String> collect = Optional.of("java")
                                   .stream()
                                   .map(value -> value.concat("script"))
                                   .collect(Collectors.toList());

    assertArrayEquals(new String[]{"javascript"}, collect.toArray());


    // empty optional
    Optional<String> value = Optional.empty();
    List<String> emptyStream = value.stream()
                                    .map(String::toUpperCase)
                                    .collect(Collectors.toList());

    assertEquals(0, emptyStream.size());
}

Таким образом, использование stram также может отфильтровывать ненулевые необязательные значения:

@Test
public void filter_empty_by_stream() {
    List<Optional<String>> languages = List.of(Optional.of("java"), Optional.empty(), Optional.empty(), Optional.of("javascript"));
    List<String> collect = languages.stream()
                                    .flatMap(Optional::stream)
                                    .collect(Collectors.toList());

    assertArrayEquals(new String[]{"java", "javascript"}, collect.toArray());
}

Ссылаться на