Pull to refresh

Comments 51

Вы меня, конечно, простите, но зачем здесь официальная и не очень интересная инструкция к уже очень сильно бородатой фиче (особенно, на фоне выхода уже девятой версии Java)?
Всё субъективно. Для Вас 3-летняя фича сильно бородатая, а гражданин в комментарии ниже указал, что он пользуется рецептами 20-летней давности. А на том же JavaRush на Java 8 перешли год назад, и сильно сомневаюсь, что они уже учат там пользоваться Optional.

Ну не знаю, мне не очень понятно и очевидно. Пишу на Java 1.6 всю жизнь ещё с 2010-ого.

На самом деле, имхо не стоит делать из Java JavaScript, это разные языки. К тому же, применение map и filter к сущности, где содержится всего один элемент (либо ни одного) выглядит максимально абсурдно. В том же методе findAll() никто не мешает всегда возвращать List<User>, который может быть пустым, а может не быть, но никогда не будет null-ом.

К тому же, из текста вообще не очень понятно, чем orElse отличается от orElseGet. Тем, что первый берёт на вход значение, а второй лямбду? Так а смысловое/семантическое отличие в чём, если лямбда всё равно что-то возвращает, и это что-то обычно достаточно короткое в плане написания?

Опять же, какой смысл писать длинную строчку, в конце которой кидать Exception (потому что нам реально нужно узнать об ошибке, да и не всегда есть возможность продолжить работу при отсутствии искомого объекта), если мы и при старом подходе получили бы Exception в том же самом месте на той же строке, только другого класса?

UFO just landed and posted this here
Вы — старпёр :)
На самом деле, если надо просто проверить User на null, Ваш метод прекрасно работает. Более того, он даже быстрее варианта с Optional, потому что не создаётся/уничтожается объект Optional.
Но представьте, что у User есть поле address, которое может быть null, в котором есть поле ZIP, которое тоже nullable. Вам надо отобразить это самое последнее поле. Без Optional.map() это будет жуткое количество проверок на null, а с Optional.map() — только одна финальная
UFO just landed and posted this here
isPresent() вообще не нужно:
System.out.println(user.map(User::getAddress).map(Address::getZip).orElse(""));
согласитесь, что код выглядит намного чище за счёт полного устранения проверок на null.
UFO just landed and posted this here
20 лет? Пишете на Java 1.1?
Спасибо за информацию, теперь я буду знать, что Вы так пишете.
я придерживаюсь стратегии ставить null в операции сравнения на первое место
if( user != null ) {} 

if( null != user ) {} 

поможет избежать дополнительный поиск опечатки в случае если напишете = вместо ==
if( null = user ) {} 
UFO just landed and posted this here
Язык развивается, старые рецепты заменяются новыми. Также, если Вы хотите работать с последними версиями популярных фреймворков, Вам будет необходимо изучить Optional, а если хотите успешно с ними работать, то выучить его нужно будет хорошо.
Также, как уже было указано в статье, Optional очень хорошо чистит код и делает его более читабельным. Ведь такого рода проверки на null являются служебными и только мешают видеть суть кода.

И, кстати, вместо
if (user == null) {}

давно уже принято использовать
if (Objects.isNull(user)) {}
Можно уточнить где принято так использовать?
javadoc для Objects.isNull() говорит:
This method exists to be used as a java.util.function.Predicate, filter(Objects::isNull)

И что плохого в том, чтобы писать как раньше?
if (user == null) {}
Вы сами ответили на свой вопрос. А плохого в том, что, к примеру,
поможет избежать дополнительный поиск опечатки в случае если напишете = вместо ==
UFO just landed and posted this here
Да, джава ожидает булевское значение, вот гражданин выше боится передать вместо булевского значения операцию присвоения, я ему и ответил его же опасением.
UFO just landed and posted this here

Вот видите, Вы сходу допустили синтаксическую ошибку, подтвердив мои слова :)

UFO just landed and posted this here
А не проще ли использовать какой-нибудь SonarLint? Заодно узнаете о себе много нового :)
if( null = user ) {}
К сожалению, такое я частенько видел в чужом коде. Это, как мне кажется, наследие от Си. Почему-то люди, забывают, что if в Java работает с Boolean.
Не делайте так, пожалуйста, пишите как в начале. Это несложно.
if в Java работает с Boolean

Нет. Оператор if в Java работает с boolean.

Конечно же вы правы, насчёт первой буквы. Я, признаюсь, некоторое время думал как лучше написать, чтобы человек обратил внимание, и на всякий случай указал ссылку на SO, где используется выражение a boolean expression. Да и ошибка в компиляторе выглядит так:
error: incompatible types: OtherClass cannot be converted to boolean
Согласитесь, не очень. Намного приятнее иметь дело с такой строчкой:
Optional<User> user = Optional.of(repository.findById(userId));

И поймать всё тот же NullPointerException, если пользователя с переданным идентификатором не найдено.

По-моему, правильным решением будет возвращение Optional-а из findById(), а никак не оборачивание его результата.
За возвращением null из метода, который должен вернуть Optional, Map, Collection, etc., нужно следить всякими анализаторами. А кто так делает, тому металлической линейкой по пальцам во избежание рецидивов.
Вы зря статью не дочитали. Методы обработки .orElse(), .oeElseThrow() и orElseget() как раз страхуют от NPE.
Не застрахуют потому что метод
Optional.of()

Вызовет конструктор

private Optional(T value) {
  this.value = Objects.requireNonNull(value);
}
Мы и используем, но в статье лучше тоже поправить.

Кстати, в исходниках нашего проекта 116 вхождений ofNullable и 46 вхождений of. В общем-то of тоже нужен частенько.

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

Можно и так делать (если дело с библиотекой которая уже есть), но тут точно нужно использовать Optional.ofNullable().
Также здесь
String name = repository.findById(userId).map(user -> user.getName()).orElseThrow(() -> new Exception());

в name вполне может оказатся null и ето для чего придуман Optional.flatMap()
.map()
Этот метод полностью повторяет аналогичный метод для stream()

Это не так. Optional.map не работает в случае null значения, а Stream.map работает
Увы, но обещание отсутствия NullPointerException не сбылось даже в управляемых языках. А ведь апологеты так хорошо пели о том, что мы больше их никогда не увидим.
Я вот, используя Optional, за последние 2 месяца увидел NPE только один раз.
Это как "опционалы в Swift", вы можете обложить свой код всякими ifPresent, isPresent, и не увидите NPE, а потом будете думать, почему у вас данные не возвращаются.

Я сам редко пользуюсь Optionals, потому что предпочитаю не допускать NPE. Для меня ожидаемо работающий код лучше, чем просто работающий код.
В Scala очень востребованным оказался метод fold, который эквивалентен map + getOrElse.
Может он и в Java есть.
Лично для меня большую часть функционала Optional выполняет элвис-оператор, который есть в kotlin, но до сих пор нет в Java и это печалит, тем более что запись object?.field легче чем optiona.ifPresent(() -> ...). Хотя, конечно, при большой вложенности объектов optional.map будет удобнее чем if (o1 != null && o1.o2 != null && ...)
.flatMap()

Этот метод делает ровно то же, что и стримовский, с той лишь разницей, что он работает только в том случае, если значение не null.

Не совсем так, он разворачивает Optional в отличии от Stream. Но суть да, аналогичная стримовской — избавиться от вложенных контейнеров, e.g. Optional<Optional<User>>.

Фишка Optional не только в том что он NPE-safe но и в том что Optional является монадой и реализует функции map и flatMap что позволяет вам писать код в функциональном стиле. На java это конечно не супер выглядит но в скале все гораздо приятнее.
Например у вас есть 3 имени проперти для подключения к БД (url, user, pass), поэтому вам нужно сходить в какой-то конфиг, взять значения переменных а потом из 3-х переменных сделать одну (ведь вам нужен коннекшн, а не сами логины пароли). В таком случае вы делаете примерно так:
maybe_url, maybe_user, maybe_password все Optional и потом:
Optional maybe_connection = maybe_url.flatMap(url -> maybe_user.flatMap(user -> maybe_pw.map(pw -> connectToDb(url, user, pw)))). Если любая проперти отстутсвтует вы получите Optional.empty на выходе без пробросов исключений

в скале это можно сделать как-то так:
for {
url <- maybe_url
user <- maybe_user
pw <- maybe_pw
connection = connectToDb(url, user, pw)
} yield connection


(разумеется можно навесить и больше, если хочется)

Уже третье ложное утверждение в комментариях к этой теме:


в том что Optional является монадой

Нет, java.util.Optional не является монадой, так как не соблюдает композицию байндинга (смотрите законы монад).

Почему не соблюдается? Вроде нормально же
Optional<String> m;
Function<String, Optional<Integer>> f;
Function<Integer, Optional<Boolean>> g;

Optional<Boolean> left = m.flatMap(f).flatMap(g);
Optional<Boolean> right = m.flatMap(x -> f.apply(x).flatMap(g));
Простой тест с перебором всех возможных значений показывает что разный результат будет только когда m == empty() && (f == null || g == null). Честно говоря, мне кажется что тестирование монадических законов на нулевых функциях — это читерство и так делать нельзя.

сам тест
public class MonadTest {

    public static void main(String[] args) {
        List<Optional<String>> ms = asList(ofNullable(null), ofNullable("123"), null);
        List<Function<String, Optional<Integer>>> fs = asList(s -> ofNullable(null), s -> ofNullable(s.length()), s -> null, null);
        List<Function<Integer, Optional<Boolean>>> gs = asList(s -> ofNullable(null), i -> ofNullable(i.intValue() == 0), i -> null, null);

        for (int i = 0; i < ms.size(); i++) {
            Optional<String> m = ms.get(i);
            for (int j = 0; j < fs.size(); j++) {
                Function<String, Optional<Integer>> f = fs.get(j);
                for (int k = 0; k < gs.size(); k++) {
                    Function<Integer, Optional<Boolean>> g = gs.get(k);
                    try {
                        test(m, f, g);
                    } catch (AssertionError e) {
                        System.out.println(i + " " + j + " " + k);
                    }
                }
            }
        }
    }

    private static void test(Optional<String> m, Function<String, Optional<Integer>> f, Function<Integer, Optional<Boolean>> g) {
        Optional<Boolean> left;
        boolean npeOnLeft;
        Optional<Boolean> right;
        boolean npeOnRight;

        try {
            left = m.flatMap(f).flatMap(g);
            npeOnLeft = false;
        } catch (NullPointerException e) {
            left = null;
            npeOnLeft = true;
        }

        try {
            right = m.flatMap(x -> f.apply(x).flatMap(g));
            npeOnRight = false;
        } catch (NullPointerException e) {
            right = null;
            npeOnRight = true;
        }

        assertEquals(npeOnLeft, npeOnRight);
        if (!npeOnLeft)
            assertEquals(left, right);
    }
}

Не совсем красиво я выразился, пожалуй. Нулевые функции — это читерство, а вот функции, принимающие или возвращающие null — вполне законны. Очевидно, что flatMap должно соответствовать операции bind. Вопрос: какая операция соответствует операции return? Предположим, что Optional.ofNullable. Тогда f = Optional::of нарушает закон (return v) >>= f ≡ f v для v = null. В терминах Java слева Optional.ofNullable(null).flatMap(f) => empty(), а вот f.apply(null) — NPE. Ладно, предположим, что return — это Optional.of. Тогда возьмём f = Optional::ofNullable и снова получим несоответствие.


Более интересные эффекты проявляются с операцией map (аналог хаскеловского fmap). Пусть есть две функции:


UnaryOperator<Object> f = x -> null;
UnaryOperator<Object> g = x -> "foo";

Возьмём произвольный непустой Optional opt. Композиция fmap подразумевает, что opt.map(f).map(g) эквивалентно opt.map(g.compose(f)), а это не так: первый — это всегда empty, а второй — это Optional.of("foo").

Я, конечно, тоже молодец, слишком доверчивый. Раз написано про ошибку в композиции, то проверил именно ее. А получилось что это как раз единственный закон из трех, который работает полностью.

Но вообще, мне кажется что все это некорректно. Если за базу мы берем haskell, то с нулами получается интересно. Ведь там их нет, именно за этим и нужен Maybe. Соответственно, логично предположить что если функция принимает какой-то тип, то она принимает именно этот тип, а не null. И тогда получается что (return v) >>= f ≡ f v для случая v = null просто не имеет смысла, т.к. конструкции (return null) и (f null) не существуют. На простой map, я думаю, данное ограничение тоже распространяется.

Так что еще большой вопрос является ли Optional монадой в строгом значении.

Но вообще, по поводу монад у меня есть другая мысль. Рискну быть заминусованным, но предположу что когда про монады говорит кто-нибудь, кто не является профессиональным фп-программистом (что-бы это не значило), он имеет в виду не строгий математический объект, а некую monad-like «контейнерную» абстракцию, позволяющую легко работать напрямую с содержимым в «нормальных» сценариях. А что при этом оно почти всегда разваливается на границах — да кому какое дело?! В конце концов, не надо мешать нулы и опшионалы.
Sign up to leave a comment.

Articles