0,0
рейтинг
9 июня 2014 в 18:01

Разработка → Новый класс Optional в Java 8, не панацея от NullPointerException из песочницы tutorial

Java*
В релизе Java 8 появился новый класс Optional призванный помочь разработчикам в обработке NullPointerException.

С NullPointerException встречались многие и во многих случаях, это очень неприятное исключение заставляет дебажить код, дабы понять, в каком месте, кто-то из твоих предшественников(а возможно и ты), не поставили пресловутую проверку на null.

А что если вообще запретить назначать тем или иным полям класса значения равные null? Java естественно не запрещает нам делать этого, но с Optional это становится немного удобнее и нагляднее.

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

Создание объектов Optional

Для начала приведу пример класса с использованием Optional:

import java.util.Date;
import java.util.Optional;

public class Person {

	private Optional<String> firstName;
	
	private Optional<String> secondName;
	
	private Optional<Integer> age;
	
	private Optional<PersonAddress> address;
	
	public Optional<String> getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = Optional.ofNullable(firstName);
	}

	public Optional<String> getSecondName() {
		return secondName;
	}

	public void setSecondName(String secondName) {
		this.secondName = Optional.of(secondName);
	}

	public Optional<Integer> getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = Optional.ofNullable(age);
	}
	
	public Optional<PersonAddress> getAddress() {
		return address;
	}

	public void setAddress(PersonAddress address) {
		this.address = Optional.of(address);
	}
}

Как видите при установке полям класса значений, через «set» методы, мы используем статические методы класса Optional - of(), ofNullable()).

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

/** Создание Optional объектов */
		
//Пустой Optional объект
Optional<Person> optionalPerson = Optional.empty();
	
//Optional объект с ненулевым значением
Optional<Person> optionalNonNull = Optional.of(somePerson);
		
//Optional объект с возможностью нулевого значения
Optional<Person> optionalNullable = Optional.ofNullable(somePerson);

Использование Optional для устранения избыточного кода

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

При попытке обратиться напрямую к этому полю через цепочку объектов и при условии, что переданный объект по каким-то причинам пришел равный null мы получим NullPointerException, поэтому для начала нам необходимо проверить каждый объект на null и уже потом взять необходимое нам текстовое поле:

Person person = getDefaultPerson();
	if (person != null) {
		PersonAddress personAddress = person.getAddress();
			if (personAddress != null) {
				PersonAddressStreet street = personAddress.getStreet();
				if(street != null) {
					streetName = street.getStreetName();
				} else {
					streetName = "EMPTY";
				}
			}
	}

А теперь все то же самое, но с использованием Optional:

String streetName = person.flatMap(Person::getAddress)
               .flatMap(PersonAddress::getStreet)
               .map(PersonAddressStreet::getStreetName)
               .orElse("EMPTY");

Намного лаконичнее, не правда ли?

Действия с объектом, с использованием метода ifPresent()

Метод ifPresent() позволяет также устранить некоторую избыточность кода, следующего вида:

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

Те же действия, но с использованием Optional:

person.ifPresent(System.out::println);

Действия с объектом с использованием isPresent()

isPresent() не дает нам большой выгоды по устранению избыточности кода, но зато придает немного большую информативность написанному коду.

Как было раньше:

if(person != null) {
	System.out.println(person)
} else {
	System.out.println("Person not found!");
 }

То же самое, но с использованием Optional:

if (person.isPresent()) {
	System.out.println(person.get());
} else {
	System.out.println("Person not found!");
}

Действия с объектом с использованием orElse(), orElseThrow()

И напоследок еще несколько методов для наведения «красоты» в коде.

Как было раньше:

Person personNew = person != null ? person : new Person();

То же самое, но с использованием Optional:

Person personNew = person.orElse(new Person());

Или, если не хотим создавать объект, можно выбросить исключение:

Person personNewThrow = person.orElseThrow(Exception::new);

Заключение

Естественно Optional не дает никакой гарантии избавления от NullPointerException и все проверки на null можно было описывать и раньше, но с Optional это действия становятся быстрее и проще, так как дополнительные методы для проверки объекта или проверки и каких-то дальнейших действий с объектом уже описаны и нам осталось только воспользоваться ими в своем коде.

А также несомненно, Optional помогает придать большую информативность коду, повысить его читабельность.

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

Информация для дальнейшего изучения:

Александр Подкутин @a_podkutin
карма
5,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (38)

  • +4
    На самом деле Optional в Java 8 был введён исключительно для поддержки коллекций. Его опасно использовать, например в JavaEE, в частности, потому что он не сериализуемый. С этим связана довольно напряжённая дискуссия (см. дальше в треде ответ Brian Goetz, блин, не знаю как его фамилию по-русски написать) в списке рассылки пару лет назад. Optional в Java 8 предназначается самими авторами языка исключительно для реализации идиомы «опциональное возвращаемое значение». Поэтому, как ни прискорбно, пользоваться Optional'ом из JDK8 далеко не всегда возможно. Одна надежда на авторов гуавы что они свой Optional улучшат, добавят наконец flatMap, например.
  • +4
    Верно ли, что метод, возвращающий Optional, может все так же вернуть null вместо Optional.empty()?
    Жду следующего творчества индусов:
    if(person != null) {
        person.ifPresent(System.out::println);
    }
    
    • –1
      Если для возвращаемого объекта указано, что он Optional.ofNullable(), и в методе он равен null, то вернется Optional.empty(), но при попытке использовать этот объект методом person.get() выпадет исключение java.util.NoSuchElementException. Если же указать Optional.of() возвращаемому объекту, то изначально, при попытке передать в него null, мы получим NullPointerException.
      • 0
        А если просто написать return null?
        • –1
          Получим null, так как в этом случае мы не оборачиваем значение в описываемый класс Optional, но мы можем также обернуть его и уже по возвращении из метода.
          • +3
            Тогда достаточно было ответить одним словом — «верно». А к чему это приведет — смотри приведенный код… ладно если еще семантика null и Optional.empty() отличаться не будет. А то я же знаю человека, который сочтет Optional<Boolean> удобным способом вернуть из метода одно из четырех значений.
            • +2
              Этот человек нуждается в помощи. Если можете, побейте снабдите его книгой Роберта Мартина «Чистый код».
  • +3
    Как по мне лучше привыкнуть использовать @ NotNull & @ Nullable
    • 0
      Угу и a.getB().getC().get(D) превратиться в в жутчайший if.

      Nullable хорошо. Но Optional гораздо удобнее.
      • 0
        Я понимаю _зачем_ это было сделано, но наличие такой «фичи» не освобождает от необходимости помечать методы и аргументы.
      • +1
        Если знать, что A, B и C никогда не бывают null, то можно и обойтись и простой записью a.getB().getC().getD().

        Другое дело, что конкретно такая запись формально нарушает Закон Деметры.
    • 0
      optional и аннотации дополняют друг друга, так как optional почти бесполезен без поддержки системы типов
    • 0
      Лучше запретить null'ы на уровне языка :-)
      Ясно-понятно, что уже не Java.

      А еще лучше запрещать не только null'ы, но и вообще целые классы значений, чтобы избежать большинства runtime-проверок как таковых. Runtime-проверки должны быть только там, где есть внешние данные. Согласованность внутренней логики должна проверяться на уровне компиляции.
      Хороший пример этой идиомы — enum'ы вместо именованных констант, generic'и.
      • –1
        А еще было бы клево различать readonly и writeonly типы переменных.
        • 0
          read-only:
          class MyClass {
          	private final int roField;
          
          	public MyClass(int value) {
          		this.roField = value;
          	}
          
          	public int getRoField() {
          		return roField;
          	}
          }


          А для чего нужные переменные уровня write-only?
          • 0
            Для маппинга на соответствующие регистры устройств (микроконтроллеры и т.п.). Это я не про Java, разумеется.
          • 0
            Для того, чтобы можно было отразить в коде то, что обычно пишут в доках.
            Код, как известно, лучшая документация.
            И желательно чтобы это было так даже в случае отсутствия исходников.
          • 0
            Read-only getter работает только для примитивных типов.
            Для объектов будет константная ссылка.
            А я говорю про аналог const в Си.
            Причем read-only должно быть поведение по-умолчанию, а read/write — только там, где действительно нужно.
            Ведь const в Си не пишут там, где следует, именно по причине лени или забывчивости.
  • +1
    А рабочий пример есть? А то я вижу какие-то person.flatMap, person.orElse, но в классе такого объявления нет.
    • +1
      Методы flatMap() и orElse() относятся к классу Optional, а не Person. Демо-проект для данной статьи Вы можете посмотреть здесь.
      • +1
        А зачем вы код комментируете на русском языке? Гитхаб эти комментарии пожевал и теперь ничего не понятно.
        • +1
          Извините, исправил кодировку на UTF-8.
  • +6
    Что касается проверки на null — я все еще надеюсь, что когда-нибудь будет добавлен Elvis Operator. Жаль, что его отложили на дальнейшие релизы :(

    String streetName = person.?getAddress().?getStreetName() ?: "EMPTY";

    По мне такой вариант лучше, чем с Option
    • –5
      Хватит уже в язык пихать всякие *#~:/-)(&@$ символы.
      • +1
        Да? И какой же из последних «впихнутых» символов в Java вас так расстраивает? :)
  • +2
    Optional не решает общей проблемы NullPointer-ов, так как само поле Optional может быть null. Кроме того Optional вводит косвенные проблемы совместимости такие как сериализация, распознавание типов полей многими фреймворками, etc… И совершенно не согласен, что код становится читаемей, хоть и чуть короче.

    Глобально, null reference — это глобальная ошибка дизайна языков, которая усугубилась в Java (и еще больше в Scala), когда решили работать исключительно только со ссылками и фактически исключили value-типы.
    qconlondon.com/london-2009/presentation/Null+References:+The+Billion+Dollar+Mistake

    Сейчас идет обратная тенденция включить в язык структуры (value types). Есть попытки исправить ситуацию, напр. в Kotlin явно указывается, что тип nullable, что вносит определенные сложности с использованием api джавы. Даже в саму джаву когда-нибудь планируется внести value types, но маловероятно: habrahabr.ru/post/222113/

    Так что пока остается только четко документировать код и использовать @Nullable/NotNull
    • 0
      и еще больше в Scala

      Поясните пожалуйста. Имхо, в скале получить NPE сложнее.
      • +1
        Согласен, сложнее, если все делать правильно. Однако Option был введен искуственно и само его использование опционально, хотя и рекомендуется. Язык не избавился от парадигмы null-ов, даже несмотря на то, что там появились case классы. Мы имеем любой класс MyClass от AnyRef, который сам по себе уже nullable. Плюс еще добавляем Option для явного указания, что его значение опционально. То есть если например метод возвращает MyClass мы не можем сказать, возвращает ли он null или нет. Кто-то пользует Option, кто-то нет, это добавляет неразберихи.

        Помимо этого проблема Option-а в том, что Option[Int] не имеет никакого отношения к Int. То есть обычный Int просто так не передать в функцию func( x: Option[Int] ), хотя по идее Option[Int] должен расширять Int. Поэтому последняя тенденция (Kotlin, Ceylon) — встроить в сам язык nullable типы, которые указываются въявную и расширяют содержимые типы.
  • 0
    Optional optionalNullable = Optional.ofNullable(new Person());
    Не удачный пример. Конструктор не межет вернуть null. Болле хороший:

    Map m = ...;
    Optional optionalNullable = Optional.ofNullable(m.get(...));

    • 0
      Фигня, мне гораздо больше эта пара понравилась:

      public void setSecondName(String secondName) {
      this.secondName = Optional.of(secondName);
      }

      public void setAge(int age) {
      this.age = Optional.ofNullable(age);
      }
    • 0
      tmk826 Спасибо, исправил данный пример.
    • 0
      dougrinch А что в этой паре стоит исправить?
      • 0
        Да, в целом, все норм. Просто странно выглядит рядом «Optional.of(String)» который может быть нулем и «Optional.ofNullable(int)» который как раз нулем не может быть вообще никогда и никак.
        • 0
          Да, это моя ошибка в сигнатуре метода, переменная age имеет тип Integer и передавать здесь мы должны Integer, который может быть null. Исправил данный момент, спасибо.
  • +1
    Спасибо за статью, однако общий посыл статьи на мой взгляд неверный. В принципе об этом уже сказал Googolplex в самом первом комментарии к статье — Optional предназначен не для борьбы с NPE, для более лаконичной реализации идиомы «опциональное возвращаемое значение».

    Раздел "Использование Optional для устранения избыточного кода" странный. Вы предлагаете вместо этого
    Person person = getDefaultPerson();
    if (person != null) {
        PersonAddress personAddress = person.getAddress();
        if (personAddress != null) {
            PersonAddressStreet street = personAddress.getStreet();
            if(street != null) {
                streetName = street.getStreetName();
            } else {
                streetName = "EMPTY";
            }
        }
    }

    Написать так
    Person person = getDefaultPerson();
    String streetName = person.flatMap(Person::getAddress)
                   .flatMap(PersonAddress::getStreet)
                   .map(PersonAddressStreet::getStreetName)
                   .orElse("EMPTY");

    Хммм, 5 строк вместо 12, здорово. Но ведь можно написать и так
    Person person = getDefaultPerson();
    PersonAddress personAddress = person != null ? person.getAddress() : null;
    PersonAddressStreet street = personAddress != null ? personAddress.getStreet() : null;
    String streetName = street != null ? street.getStreetName() : "EMPTY";

    и получить всего 4 строки. Если хочется сократить код максимально, то можно вообще объявить где-то парочку статических функций
    public static <T> T consumeNpe(@NonNull Supplier<T> potentiallyNpeCall)
        {
            return noNpe(potentiallyNpeCall, null);
        }
    
    public static <T> T consumeNpe(@NonNull Supplier<T> potentiallyNpeCall, T defaultValue)
    {
        try
        {
            return potentiallyNpeCall.get();
        }
        catch (NullPointerException npe)
        {
            return defaultValue;
        }
    }

    и разруливать эту ситуацию в 1 строчку в любом месте в приложении
    String streetName = consumeNpe(() -> getDefaultPerson().getAddress().getStreet().getStreetName(), "EMPTY");

    Во втором примере вы предлагаете заменить
    if(person != null) {
        System.out.println(person);
     }

    на
    person.ifPresent(System.out::println);

    я бы назвал это "шило на мыло", тем более что первый кусок кода можно записать без скобок
    if(person != null) System.out.println(person);

    ну и далее по списку.

    Это никакое не "устранение избыточного кода", это всего лишь замена декларативного стиля на функциональный, не более того.

    Повторю, Optional предназначен лишь для лучшей читаемости ситуаций, когда возвращаемое методом значение опционально. Если взять ваш пример, то метод getAddress() в классе Person будет возвращать как раз Optional. Программист сразу поймёт что у персоны может не быть адреса едва взглянув на заголовок функции и вместо проверки на null вызовет метод isPresent().

    Кстати рекомендую к прочтению ещё одну статью на эту тему.
    • +1
      Ваши варианты по сравнению с Optional выглядят не очень-то читабельно, а за тело if на строке с условием я бы заставлял читать нараспев мой код студенческих времён. Optional не только позволяет писать меньше кода, он ещё и делает код чище, не зацикливайтесь вы на количестве строк. Ну серьёзно:
      .flatMap(PersonAddress::getStreet)
      

      гораздо понятнее, чем
      PersonAddressStreet street = personAddress != null ? personAddress.getStreet() : null;
      

      В первом варианте — самая суть, во втором — куча мусора с сутью где-то в конце. Сэкономили 1 строку, сделав все 4 нечитаемыми. Строки кода вообще нужно считать с умом; так-то и паттерн Builder можно писать в одну строку, если жить недолго осталось.

      consumeNpe() — вообще адский костыль, т.к. NPE может вылететь и внутри вызываемых через точку методов, но вы никогда не узнаете о том, что у вас в методе баг, т.к. не вызываете printStackTrace() у исключения и наивно полагаете, что NPE у вас может вылететь только в самой цепочке.
      • +2
        Вы неправильно поняли, я вовсе не извращенец-однострочник, автор статьи первым написал намеренно раздутый "обычный" код, а потом 5 строчек в функциональном стиле, типа смотрите как теперь все здорово и читаемо. Вот только для кого, для адептов этого самого функционального стиля? Если я сейчас покажу аналогичный код из статьи и из своего комментария коллеге, не знакомому с RxJava, мой код он поймет, а вот увидев код из статьи произнесет "wtf is this". Серьезно, хватит считать, что функциональный стиль кода заведомо читаемее, с каких это пор базовые элементы языка стали считаться " непонятным мусором ", а flatMap — сутью? Я сам люблю функциональное программирование, но пихать его направо и налево без разбора и с пеной у рта доказывать всем вокруг, что теперь то код стал читаемее раз в 5 минимум похоже на помешательство, вы уж извините.
  • 0
    <удалено>

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.