Pull to refresh

Comments 38

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

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

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

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

	public MyClass(int value) {
		this.roField = value;
	}

	public int getRoField() {
		return roField;
	}
}


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

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

По мне такой вариант лучше, чем с Option
Хватит уже в язык пихать всякие *#~:/-)(&@$ символы.
Да? И какой же из последних «впихнутых» символов в Java вас так расстраивает? :)
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
и еще больше в Scala

Поясните пожалуйста. Имхо, в скале получить NPE сложнее.
Согласен, сложнее, если все делать правильно. Однако 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 типы, которые указываются въявную и расширяют содержимые типы.
Optional optionalNullable = Optional.ofNullable(new Person());
Не удачный пример. Конструктор не межет вернуть null. Болле хороший:

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

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

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

public void setAge(int age) {
this.age = Optional.ofNullable(age);
}
Да, в целом, все норм. Просто странно выглядит рядом «Optional.of(String)» который может быть нулем и «Optional.ofNullable(int)» который как раз нулем не может быть вообще никогда и никак.
Да, это моя ошибка в сигнатуре метода, переменная age имеет тип Integer и передавать здесь мы должны Integer, который может быть null. Исправил данный момент, спасибо.
Спасибо за статью, однако общий посыл статьи на мой взгляд неверный. В принципе об этом уже сказал 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().

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

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

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

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

Articles

Change theme settings