Comments 51
Ну не знаю, мне не очень понятно и очевидно. Пишу на Java 1.6 всю жизнь ещё с 2010-ого.
На самом деле, имхо не стоит делать из Java JavaScript, это разные языки. К тому же, применение map и filter к сущности, где содержится всего один элемент (либо ни одного) выглядит максимально абсурдно. В том же методе findAll()
никто не мешает всегда возвращать List<User>
, который может быть пустым, а может не быть, но никогда не будет null-ом.
К тому же, из текста вообще не очень понятно, чем orElse
отличается от orElseGet
. Тем, что первый берёт на вход значение, а второй лямбду? Так а смысловое/семантическое отличие в чём, если лямбда всё равно что-то возвращает, и это что-то обычно достаточно короткое в плане написания?
Опять же, какой смысл писать длинную строчку, в конце которой кидать Exception (потому что нам реально нужно узнать об ошибке, да и не всегда есть возможность продолжить работу при отсутствии искомого объекта), если мы и при старом подходе получили бы Exception в том же самом месте на той же строке, только другого класса?
На самом деле, если надо просто проверить User на null, Ваш метод прекрасно работает. Более того, он даже быстрее варианта с Optional, потому что не создаётся/уничтожается объект Optional.
Но представьте, что у User есть поле address, которое может быть null, в котором есть поле ZIP, которое тоже nullable. Вам надо отобразить это самое последнее поле. Без Optional.map() это будет жуткое количество проверок на null, а с Optional.map() — только одна финальная
Спасибо за информацию, теперь я буду знать, что Вы так пишете.
if( user != null ) {}
if( null != user ) {}
поможет избежать дополнительный поиск опечатки в случае если напишете = вместо ==
if( null = user ) {}
Также, как уже было указано в статье, 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) {}
поможет избежать дополнительный поиск опечатки в случае если напишете = вместо ==
Нет, не принято.
if( null = user ) {}
К сожалению, такое я частенько видел в чужом коде. Это, как мне кажется, наследие от Си. Почему-то люди, забывают, что if в Java работает с Boolean.Не делайте так, пожалуйста, пишите как в начале. Это несложно.
if в Java работает с Boolean
Нет. Оператор if в Java работает с boolean.
error: incompatible types: OtherClass cannot be converted to boolean
Согласитесь, не очень. Намного приятнее иметь дело с такой строчкой:
Optional<User> user = Optional.of(repository.findById(userId));
И поймать всё тот же NullPointerException, если пользователя с переданным идентификатором не найдено.
По-моему, правильным решением будет возвращение Optional-а из findById(), а никак не оборачивание его результата.
И про этот случай в статье ничего не написано.
Optional.of()
Вызовет конструктор
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
Также здесь
String name = repository.findById(userId).map(user -> user.getName()).orElseThrow(() -> new Exception());
в name вполне может оказатся null и ето для чего придуман Optional.flatMap()
.map()
Этот метод полностью повторяет аналогичный метод для stream()
Это не так. Optional.map не работает в случае null значения, а Stream.map работает
Я сам редко пользуюсь Optionals, потому что предпочитаю не допускать NPE. Для меня ожидаемо работающий код лучше, чем просто работающий код.
Может он и в Java есть.
.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));
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 «контейнерную» абстракцию, позволяющую легко работать напрямую с содержимым в «нормальных» сценариях. А что при этом оно почти всегда разваливается на границах — да кому какое дело?! В конце концов, не надо мешать нулы и опшионалы.
Optional: Кот Шрёдингера в Java 8