1. Обзор
В этом учебном пособии мы рассмотрим класс Optional
, который был представлен в Java 8.
Цель класса — предоставить решение на уровне типа для представления опциональных значений вместо null
(нулевых) ссылок.
Для более глубокого понимания того, почему мы должны обратить внимание на класс Optional
, ознакомьтесь с официальной статьей Oracle.
2. Создание объектов Optional
Существует несколько способов создания объектов Optional
.
Чтобы создать пустой объект Optional
, нужно просто использовать его статический метод empty()
:
@Test
public void whenCreatesEmptyOptional_thenCorrect() {
Optional<String> empty = Optional.empty();
assertFalse(empty.isPresent());
}
Обратите внимание, что мы использовали метод isPresent()
для проверки наличия значения внутри объекта Optional
. Значение присутствует, только если мы создали Optional
с non-null (ненулевым) значением. Мы рассмотрим метод isPresent()
в следующем разделе.
Можно также создать объект Optional
с помощью статического метода of()
:
@Test
public void givenNonNull_whenCreatesNonNullable_thenCorrect() {
String name = "baeldung";
Optional<String> opt = Optional.of(name);
assertTrue(opt.isPresent());
}
Однако аргумент, переданный в метод of()
, не может быть null
. В противном случае мы получим NullPointerException
:
@Test(expected = NullPointerException.class)
public void givenNull_whenThrowsErrorOnCreate_thenCorrect() {
String name = null;
Optional.of(name);
}
Но в случае, если мы предполагаем некоторые значения null
, то можно использовать метод ofNullable()
:
@Test
public void givenNonNull_whenCreatesNullable_thenCorrect() {
String name = "baeldung";
Optional<String> opt = Optional.ofNullable(name);
assertTrue(opt.isPresent());
}
Таким образом, если мы передаем null
ссылку, это не вызовет исключения, а вернет пустой объект Optional
:
@Test
public void givenNull_whenCreatesNullable_thenCorrect() {
String name = null;
Optional<String> opt = Optional.ofNullable(name);
assertFalse(opt.isPresent());
}
3. Проверка наличия значения: isPresent() и isEmpty()
Когда у нас есть объект Optional
, возвращенный из метода или созданный нами, мы можем проверить, есть ли в нем значение или нет, с помощью метода isPresent()
:
@Test
public void givenOptional_whenIsPresentWorks_thenCorrect() {
Optional<String> opt = Optional.of("Baeldung");
assertTrue(opt.isPresent());
opt = Optional.ofNullable(null);
assertFalse(opt.isPresent());
}
Этот метод возвращает true
, если обернутое значение не является null
.
Также, начиная с Java 11, мы можем сделать обратное с помощью метода isEmpty
:
@Test
public void givenAnEmptyOptional_thenIsEmptyBehavesAsExpected() {
Optional<String> opt = Optional.of("Baeldung");
assertFalse(opt.isEmpty());
opt = Optional.ofNullable(null);
assertTrue(opt.isEmpty());
}
4. Условное действие с помощью ifPresent()
Метод ifPresent()
позволяет нам запустить некоторый код для обернутого значения, если выяснится, что оно не является null
. До метода Optional
мы бы сделали следующее:
if(name != null) {
System.out.println(name.length());
}
Этот код проверяет, является ли переменная name null
или нет, прежде чем приступить к выполнению какого-либо кода над ней. Такой подход занимает много времени, и это не единственная проблема — он также склонен к ошибкам.
В самом деле, где гарантия, что после печати этой переменной мы не воспользуемся ею снова, а потом забудем выполнить проверку на null
?
Это может привести к NullPointerException
во время выполнения программы, если в этот код попадет значение null
. Когда программа терпит неудачу из-за проблем с вводом, это часто является результатом плохой практики программирования.
Optional
заставляет нас иметь дело с допускающими null
значениями в явном виде, как способ принуждения к хорошей практике программирования.
Теперь давайте посмотрим, каким образом приведенный выше код может быть рефакторизован в Java 8.
В типичном стиле функционального программирования мы можем выполнить действие над объектом, который действительно присутствует:
@Test
public void givenOptional_whenIfPresentWorks_thenCorrect() {
Optional<String> opt = Optional.of("baeldung");
opt.ifPresent(name -> System.out.println(name.length()));
}
5. Значение по умолчанию с помощью orElse()
Метод orElse()
используется для получения значения, обернутого внутри экземпляра Optional
. Он принимает один параметр, который выступает в качестве значения по умолчанию. Метод orElse()
возвращает обернутое значение, если оно присутствует, либо его аргумент в противном случае:
@Test
public void whenOrElseWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElse("john");
assertEquals("john", name);
}
6. Значение по умолчанию с помощью orElseGet()
Метод orElseGet()
аналогичен методу orElse()
. Однако вместо того, чтобы принимать значение для возврата, если Optional
значение отсутствует, он принимает функциональный интерфейс поставщика, который вызван и возвращает значение вызова:
@Test
public void whenOrElseGetWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(() -> "john");
assertEquals("john", name);
}
7. Разница между orElse и orElseGet()
Многим программистам, которые только начинают работать с Optional
или Java 8, разница между orElse()
и orElseGet()
непонятна. На самом деле, создается впечатление, что эти два метода перекрывают друг друга по функциональности.
Однако между ними есть тонкое, но очень важное различие, которое может сильно повлиять на производительность нашего кода, если его не понять.
Давайте создадим в тестовом классе метод getMyDefault()
, который не принимает никаких аргументов и возвращает значение по умолчанию:
public String getMyDefault() {
System.out.println("Getting Default Value");
return "Default Value";
}
Давайте рассмотрим два теста и понаблюдаем за их побочными эффектами, чтобы определить, где orElse()
и orElseGet()
пересекаются, а где отличаются:
@Test
public void whenOrElseGetAndOrElseOverlap_thenCorrect() {
String text = null;
String defaultText = Optional.ofNullable(text).orElseGet(this::getMyDefault);
assertEquals("Default Value", defaultText);
defaultText = Optional.ofNullable(text).orElse(getMyDefault());
assertEquals("Default Value", defaultText);
}
В приведенном выше примере мы обертываем null
текст внутри объекта Optional
и пытаемся получить обернутое значение, используя каждый из двух подходов.
Побочный эффект таков:
Getting default value...
Getting default value...
В каждом случае вызывается метод getMyDefault()
. Выходит, что если обернутое значение отсутствует, то и orElse()
, и orElseGet()
работают одинаково.
Теперь давайте проведем еще один тест, в котором значение присутствует, и в идеале значение по умолчанию даже не должно создаваться:
@Test
public void whenOrElseGetAndOrElseDiffer_thenCorrect() {
String text = "Text present";
System.out.println("Using orElseGet:");
String defaultText
= Optional.ofNullable(text).orElseGet(this::getMyDefault);
assertEquals("Text present", defaultText);
System.out.println("Using orElse:");
defaultText = Optional.ofNullable(text).orElse(getMyDefault());
assertEquals("Text present", defaultText);
}
В приведенном выше примере мы больше не оборачиваем значение null
, а остальная часть кода остается прежней.
Теперь давайте посмотрим на побочный эффект от выполнения этого кода:
Using orElseGet:
Using orElse:
Getting default value...
Обратите внимание, что при использовании orElseGet()
для извлечения обернутого значения метод getMyDefault()
даже не вызывается, поскольку содержащееся значение присутствует.
Однако при использовании orElse()
, независимо от того, есть ли обернутое значение или нет, создается объект по умолчанию. Таким образом, в данном случае мы просто создали один лишний объект, который никогда не используется.
В этом простом примере создание объекта по умолчанию не требует значительных затрат, поскольку JVM знает, как с ним обращаться. Однако когда такой метод, как getMyDefault()
, должен выполнить вызов веб-сервиса или даже запрос к базе данных, затраты становятся весьма очевидными.
8. Исключения с помощью orElseThrow()
Метод orElseThrow()
следует из orElse()
и orElseGet()
и добавляет новый подход к обработке отсутствующего значения.
Вместо того чтобы возвращать значение по умолчанию, когда обернутое значение отсутствует, он выбрасывает исключение:
@Test(expected = IllegalArgumentException.class)
public void whenOrElseThrowWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseThrow(
IllegalArgumentException::new);
}
Здесь пригодятся ссылки на методы в Java 8, чтобы передать в конструктор исключения.
Java 10 представила упрощенную версию метода orElseThrow()
без аргументов. В случае пустого Optional
он выбрасывает исключение NoSuchElementException
:
@Test(expected = NoSuchElementException.class)
public void whenNoArgOrElseThrowWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseThrow();
}
9. Возвращение значения с помощью get()
Последний способ получения обернутого значения — метод get()
:
@Test
public void givenOptional_whenGetsValue_thenCorrect() {
Optional<String> opt = Optional.of("baeldung");
String name = opt.get();
assertEquals("baeldung", name);
}
Однако, в отличие от предыдущих трех подходов, get()
может вернуть значение, только если обернутый объект не является null
; в противном случае он выбрасывает исключение нет такого элемента:
@Test(expected = NoSuchElementException.class)
public void givenOptionalWithNull_whenGetThrowsException_thenCorrect() {
Optional<String> opt = Optional.ofNullable(null);
String name = opt.get();
}
Это основной недостаток метода get()
. В идеале Optional
должен помочь нам избежать таких непредвиденных исключений. Поэтому данный подход противоречит целям Optional
и, вероятно, будет упразднен в одном из будущих выпусков.
Поэтому рекомендуется использовать другие варианты, которые позволяют нам быть готовыми к случаю null
и явно его обрабатывать.
10. Условный возврат с помощью filter()
Мы можем запустить встроенный тест на нашем обернутом значении с помощью метода filter
. Он принимает предикат в качестве аргумента и возвращает объект Optional
. Если обернутое значение проходит проверку предикатом, то Optional
возвращается как есть.
Однако если предикат вернет false
, то будет возвращен пустой Optional
:
@Test
public void whenOptionalFilterWorks_thenCorrect() {
Integer year = 2016;
Optional<Integer> yearOptional = Optional.of(year);
boolean is2016 = yearOptional.filter(y -> y == 2016).isPresent();
assertTrue(is2016);
boolean is2017 = yearOptional.filter(y -> y == 2017).isPresent();
assertFalse(is2017);
}
Метод filter
обычно используется таким образом для отклонения обернутых значений на основе предопределенного правила. Мы могли бы использовать его для отбраковки неправильного формата электронной почты или недостаточно надежного пароля.
Давайте рассмотрим другой показательный пример. Допустим, мы хотим купить модем, и нас интересует только его цена.
С определенного сайта нам приходят push-уведомления о ценах на модемы, и они сохраняются в объектах:
public class Modem {
private Double price;
public Modem(Double price) {
this.price = price;
}
// standard getters and setters
}
Затем мы передаем эти объекты некоторому коду, единственная цель которого — проверить, находится ли цена модема в пределах нашего бюджета.
Теперь давайте посмотрим на код без Optional
:
public boolean priceIsInRange1(Modem modem) {
boolean isInRange = false;
if (modem != null && modem.getPrice() != null
&& (modem.getPrice() >= 10
&& modem.getPrice() <= 15)) {
isInRange = true;
}
return isInRange;
}
Обратите внимание на то, как много кода нам приходится писать для достижения этой цели, особенно в условии if
. Единственная часть условия if
, которая критична для приложения, — это последняя проверка диапазона цен; остальные проверки носят вспомогательный характер:
@Test
public void whenFiltersWithoutOptional_thenCorrect() {
assertTrue(priceIsInRange1(new Modem(10.0)));
assertFalse(priceIsInRange1(new Modem(9.9)));
assertFalse(priceIsInRange1(new Modem(null)));
assertFalse(priceIsInRange1(new Modem(15.5)));
assertFalse(priceIsInRange1(null));
}
Кроме того, о проверках на null
можно забыть надолго, не получив ни одной ошибки во время компиляции.
Теперь давайте рассмотрим вариант с Optional#filter
:
public boolean priceIsInRange2(Modem modem2) {
return Optional.ofNullable(modem2)
.map(Modem::getPrice)
.filter(p -> p >= 10)
.filter(p -> p <= 15)
.isPresent();
}
Вызов map
используется для трансформации одного значения в другое. Следует помнить, что эта операция не изменяет исходное значение.
В нашем случае мы получаем объект цены из класса Model
. Метод map()
будет подробно рассмотрен в следующем разделе.
Во-первых, если в этот метод передается объект null
, то никаких проблем не предвидится.
Во-вторых, единственная логика, которую мы прописываем внутри его тела, это именно то, что описывает название метода — проверка ценового диапазона. Об остальном позаботится Optional
:
@Test
public void whenFiltersWithOptional_thenCorrect() {
assertTrue(priceIsInRange2(new Modem(10.0)));
assertFalse(priceIsInRange2(new Modem(9.9)));
assertFalse(priceIsInRange2(new Modem(null)));
assertFalse(priceIsInRange2(new Modem(15.5)));
assertFalse(priceIsInRange2(null));
}
Предыдущий подход обещает проверить диапазон цен, но при этом должен сделать нечто большее, чтобы защититься от присущей ему хрупкости. Поэтому мы можем использовать метод filter
, чтобы заменить ненужные операторы if
и отбросить нежелательные значения.
11. Трансформация значения с помощью map()
В предыдущем разделе мы рассмотрели, как отклонить или принять значение на основе фильтра.
Мы можем использовать аналогичный синтаксис для преобразования значения Optional
с помощью метода map()
:
@Test
public void givenOptional_whenMapWorks_thenCorrect() {
List<String> companyNames = Arrays.asList(
"paypal", "oracle", "", "microsoft", "", "apple");
Optional<List<String>> listOptional = Optional.of(companyNames);
int size = listOptional
.map(List::size)
.orElse(0);
assertEquals(6, size);
}
В этом примере мы оборачиваем список строк внутри объекта Optional
и используем его метод map
для выполнения действия над содержащимся в нем списком. Оно заключается в получении размера списка.
Метод map
возвращает результат вычислений, завернутый внутрь Optional
. Затем мы должны вызвать соответствующий метод для возвращенного Optional
, чтобы получить это значение.
Обратите внимание, что метод filter
просто выполняет проверку значения и возвращает Optional
, описывающий это значение, только если оно соответствует заданному предикату. В противном случае возвращается пустой Optional
. Метод map
берет существующее значение, выполняет вычисления, используя его, и возвращает результат вычислений, обернутый в объект Optional
:
@Test
public void givenOptional_whenMapWorks_thenCorrect2() {
String name = "baeldung";
Optional<String> nameOptional = Optional.of(name);
int len = nameOptional
.map(String::length)
.orElse(0);
assertEquals(8, len);
}
Мы можем соединить map
и filter
вместе, чтобы сделать что-то более мощное.
Допустим, нам нужно проверить правильность пароля, введенного пользователем. Можно очистить пароль с помощью трансформации map
и проверить его правильность с помощью filter
:
@Test
public void givenOptional_whenMapWorksWithFilter_thenCorrect() {
String password = " password ";
Optional<String> passOpt = Optional.of(password);
boolean correctPassword = passOpt.filter(
pass -> pass.equals("password")).isPresent();
assertFalse(correctPassword);
correctPassword = passOpt
.map(String::trim)
.filter(pass -> pass.equals("password"))
.isPresent();
assertTrue(correctPassword);
}
Как мы видим, без предварительной очистки ввода он будет отфильтрован. Однако пользователи могут считать само собой разумеющимся, что начальные и конечные пробелы — это всё входные данные. Поэтому мы преобразуем измененный пароль в очищенный с помощью map
, прежде чем отфильтровать неправильные пароли.
12. Трансформация значения с помощью flatMap()
Подобно методу map()
, у нас также есть метод flatMap()
в качестве альтернативы для трансформации значений. Разница в том, что map преобразует значения только тогда, когда они извлечены (развернуты), в то время как flatMap
берет обернутое значение и разворачивает его перед трансформацией.
Ранее мы создавали простые объекты String
и Integer
для обертывания в экземпляр Optional
. Теперь, зачастую, мы будем получать эти объекты от асессора сложного объекта.
Чтобы лучше понять разницу, давайте рассмотрим объект Person
, который принимает данные человека, такие как имя, возраст и пароль:
public class Person {
private String name;
private int age;
private String password;
public Optional<String> getName() {
return Optional.ofNullable(name);
}
public Optional<Integer> getAge() {
return Optional.ofNullable(age);
}
public Optional<String> getPassword() {
return Optional.ofNullable(password);
}
// normal constructors and setters
}
Обычно мы создаем такой объект и оборачиваем его в Optional
, как мы это делали со String.
В качестве альтернативы он может быть возвращен нам другим вызовом метода:
Person person = new Person("john", 26);
Optional<Person> personOptional = Optional.of(person);
Обратите внимание, что когда мы обернем объект Person
, он будет содержать вложенные экземпляры Optional
:
@Test
public void givenOptional_whenFlatMapWorks_thenCorrect2() {
Person person = new Person("john", 26);
Optional<Person> personOptional = Optional.of(person);
Optional<Optional<String>> nameOptionalWrapper
= personOptional.map(Person::getName);
Optional<String> nameOptional
= nameOptionalWrapper.orElseThrow(IllegalArgumentException::new);
String name1 = nameOptional.orElse("");
assertEquals("john", name1);
String name = personOptional
.flatMap(Person::getName)
.orElse("");
assertEquals("john", name);
}
Здесь мы пытаемся извлечь атрибут name объекта Person
, чтобы выполнить утверждение.
Обратите внимание, как мы достигаем этого с помощью метода map()
в третьем операторе, а затем заметьте, как мы делаем то же самое с помощью метода flatMap()
после этого.
Ссылка на метод Person::getName
похожа на вызов String::trim
, который мы использовали в предыдущем разделе для очистки пароля.
Единственное отличие заключается в том, что getName()
возвращает Optional
, а не String, как операция trim()
. Это, в сочетании с тем, что трансформация map
оборачивает результат в объект Optional
, и приводит к появлению вложенного Optional
.
Поэтому во время применения метода map()
необходимо добавить дополнительный вызов для получения значения перед использованием трансформированного значения. Таким образом, обертка Optional
будет удалена. Эта операция выполняется неявно при использовании flatMap
.
13. Цепочка Optional в Java 8
Иногда нам может понадобиться получить первый непустой объект Optional
из нескольких Optional
. В таких случаях было бы очень удобно использовать метод типа orElseOptional()
. К сожалению, в Java 8 такая операция напрямую не поддерживается.
Давайте сначала представим несколько методов, которые мы будем использовать на протяжении всего этого раздела:
private Optional<String> getEmpty() {
return Optional.empty();
}
private Optional<String> getHello() {
return Optional.of("hello");
}
private Optional<String> getBye() {
return Optional.of("bye");
}
private Optional<String> createOptional(String input) {
if (input == null || "".equals(input) || "empty".equals(input)) {
return Optional.empty();
}
return Optional.of(input);
}
Для того чтобы выстроить цепочку из нескольких объектов Optional
и получить первый непустой объект в Java 8, мы можем использовать Stream
API:
@Test
public void givenThreeOptionals_whenChaining_thenFirstNonEmptyIsReturned() {
Optional<String> found = Stream.of(getEmpty(), getHello(), getBye())
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
assertEquals(getHello(), found);
}
Недостатком этого подхода является то, что все наши методы get
выполняются всегда, независимо от того, где в Stream
появляется непустой Optional
.
Если мы хотим лениво оценить методы, переданные в Stream.of()
, нам нужно использовать ссылку на метод и интерфейс Supplier
:
@Test
public void givenThreeOptionals_whenChaining_thenFirstNonEmptyIsReturnedAndRestNotEvaluated() {
Optional<String> found =
Stream.<Supplier<Optional<String>>>of(this::getEmpty, this::getHello, this::getBye)
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
assertEquals(getHello(), found);
}
В случае если нам нужно использовать методы, принимающие аргументы, мы должны прибегнуть к лямбда-выражениям:
@Test
public void givenTwoOptionalsReturnedByOneArgMethod_whenChaining_thenFirstNonEmptyIsReturned() {
Optional<String> found = Stream.<Supplier<Optional<String>>>of(
() -> createOptional("empty"),
() -> createOptional("hello")
)
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
assertEquals(createOptional("hello"), found);
}
Часто мы хотим вернуть значение по умолчанию в случае, если все цепочки Optional
пусты. Мы можем сделать это, просто добавив вызов orElse()
или orElseGet()
:
@Test
public void givenTwoEmptyOptionals_whenChaining_thenDefaultIsReturned() {
String found = Stream.<Supplier<Optional<String>>>of(
() -> createOptional("empty"),
() -> createOptional("empty")
)
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.orElseGet(() -> "default");
assertEquals("default", found);
}
14. Optional API в JDK 9
Релиз Java 9 добавил еще больше новых методов в Optional API:
метод
or()
для предоставления поставщика, который создает альтернативныйOptional
;ifPresentOrElse()
метод, позволяющий выполнить действие, еслиOptional
присутствует, или другое действие, если нет;метод
stream()
для преобразованияOptional
вStream
.
Вот полный текст статьи для дальнейшего чтения.
15. Неправильное использование Optional
Наконец, давайте рассмотрим привлекательный, но опасный способ использования Optional
: передача параметра Optional
методу.
Представьте, что у нас есть список Person
, и мы хотим, чтобы метод искал в этом списке людей с заданным именем. Кроме того, желательно, чтобы этот метод находил записи, имеющие по крайней мере определенный возраст, если он указан.
Поскольку этот параметр является опциональным, мы пришли к такому методу:
public static List<Person> search(List<Person> people, String name, Optional<Integer> age) {
// Null checks for people and name
return people.stream()
.filter(p -> p.getName().equals(name))
.filter(p -> p.getAge().get() >= age.orElse(0))
.collect(Collectors.toList());
}
Затем мы публикуем наш метод, и другой разработчик пытается его использовать:
someObject.search(people, "Peter", null);
Теперь разработчик выполняет свой код и получает NullPointerException
. Мы вынуждены осуществлять проверку на null
нашего опционального параметра, что противоречит нашей первоначальной цели избежать подобной ситуации.
Вот некоторые возможности, которые можно было бы сделать, чтобы справиться с этим лучше:
public static List<Person> search(List<Person> people, String name, Integer age) {
// Null checks for people and name
final Integer ageFilter = age != null ? age : 0;
return people.stream()
.filter(p -> p.getName().equals(name))
.filter(p -> p.getAge().get() >= ageFilter)
.collect(Collectors.toList());
}
Здесь параметр по-прежнему является опциональным, но мы обрабатываем его только в одной проверке.
Другим возможным вариантом является создание двух перегруженных методов:
public static List<Person> search(List<Person> people, String name) {
return doSearch(people, name, 0);
}
public static List<Person> search(List<Person> people, String name, int age) {
return doSearch(people, name, age);
}
private static List<Person> doSearch(List<Person> people, String name, int age) {
// Null checks for people and name
return people.stream()
.filter(p -> p.getName().equals(name))
.filter(p -> p.getAge().get().intValue() >= age)
.collect(Collectors.toList());
}
Таким образом, мы предлагаем четкий API с двумя методами, делающими разные вещи (хотя они имеют общую имплементацию).
Итак, существуют решения, позволяющие избежать использования Optional
в качестве параметров метода. Намерением Java было при выпуске Optional
использовать его в качестве возвращаемого типа, таким образом указывая, что метод может вернуть пустое значение. На самом деле, практика использования Optional
в качестве параметра метода даже не рекомендуется некоторыми инспекторами кода.
16. Optional и сериализация
Как уже говорилось выше, Optional
предназначен для использования в качестве возвращаемого типа. Пытаться использовать его в качестве типа поля не рекомендуется.
Кроме того, использование Optional
в сериализуемом классе приведет к возникновению исключения NotSerializableException
. В нашей статье Java Optional как возвращаемый тип вопросы сериализации рассматриваются подробнее .
А в статье Использование Optional в Jackson мы объясняем, что происходит при сериализации полей Optional
, и также приводим несколько обходных путей для достижения желаемых результатов.
17. Заключение
В этой статье мы охватили большинство важнейших фич класса Optional
в Java 8.
Вкратце исследовали некоторые причины, побудившие нас использовать Optional
вместо явной проверки на null
и валидации ввода.
Также узнали, как получить значение Optional
или значение по умолчанию, если оно пустое, с помощью методов get()
, orElse()
и orElseGet()
(и заметили важную разницу между двумя последними).
Затем рассмотрели, как трансформировать или фильтровать наши Optional
с помощью map()
, flatMap()
и filter()
. Обсудили, что предлагает текучий API Optional
, позволяющий легко соединять различные методы в цепочку.
Наконец, увидели, почему использование Optional
в качестве параметров метода является плохой идеей и как этого избежать.
Исходный код всех примеров, приведенных в статье, доступен на GitHub.
Приглашаем все желающих на открытое занятие «Объектно-ориентированное и функциональное программирование», на котором разберем отличия между этими двумя подходами на максимально простом уровне. Примеры рассмотрим на языке Java. Во второй части занятия вас ждет подробное описание особенностей специализации Java-разработчик. Регистрация — по ссылке.
dopusteam
В чем смысл почти каждый тест именовать_thenCorrect()?
Почему не написать нормально ожидаемый результат?
И вот это givenAnEmptyOptional_thenIsEmptyBehavesAsExpected
BehavesAsExpected - универсальное вообще, любой тест можно так назвать
eyudkin
А если это можно писать везде, то можно везде и опустить, это ведь подразумевается в таком случае, нам зачем лишняя писанина?
Test_something_ok или даже просто test_something - вполне нормальная история
dopusteam
Не уловил, то ли согласны со мной, то ли нет
Не, не нормальная. Название теста должно отображать, что он тестирует. Если говорить предметно, то whenCreatesEmptyOptional_thenCorrect ни говорит мне ни о чём.
Давайте с примерами, test_something - это абстрактный пример) тем более в статье given-when-then используется, но then просто натягивается, потому что нужно