Как переписать код в опции?
В моей текущей работе мы переписываем код на Java 8.
Если у вас есть такой код:
if(getApi() != null && getApi().getUser() != null
&& getApi().getUser().getCurrentTask() != null)
{
getApi().getUser().getCurrentTask().pause();
}
вы можете просто переписать его на
Optional.ofNullable(this.getApi())
.map(Api::getUser)
.map(User::getCurrentTask)
.ifPresent(Task::pause);
без изменения поведения кода.
но что, если что-то посередине может выбросить NPE, потому что оно не проверено на null?
например:
if(getApi() != null && getApi().getUser() != null
&& getApi().hasTasks())
{
getApi().getMasterUser(getApi().getUser()) //<- npe can be here
.getCurrentTask().pause();
}
Каков наилучший способ переписать такой код с помощью опций? (он должен работать точно так же и вызывать npe, когда getMasterUser(...)
возвращает null)
UPD
второй пример:
if(getApi()!=null && getApi.getUser() != null)
{
if(getApi().getUser().getDepartment().getBoss() != null)// <- nre if department is null
{
getApi().getUser().getDepartment().getBoss().somefunc();
}
}
у него есть nullchecks для api, пользователя, босса, но не отдела. как это можно сделать с помощью опций?
Ответы
Ответ 1
if(getApi() != null && getApi().getUser() != null) {
if(getApi().getUser().getDepartment().getBoss() != null) {
getApi().getUser().getDepartment().getBoss().somefunc();
}
}
Одним из способов написания этого с помощью опций является:
Optional.ofNullable(this.getApi())
.map(Api::getUser)
.map(user -> Objects.requireNonNull(user.getDepartment()))
.map(Department::getBoss)
.ifPresent(Boss::somefunc);
Но это подвержено ошибкам, потому что требуется, чтобы клиент отслеживал, что есть и не является необязательным. Лучшим способом было бы сделать api самостоятельно возвращать необязательный вместо значения NULL. Тогда код клиента:
this.getApi()
.flatMap(Api::getUser)
.map(user -> user.getDepartment().getBoss())
.ifPresent(Boss::somefunc));
Это сделало бы более ясным в api, какие значения должны быть необязательными и сделать ошибку компиляции, чтобы не обрабатывать их.
if(getApi() != null && getApi().getUser() != null && getApi().hasTasks()) {
getApi().getMasterUser(getApi().getUser()).getCurrentTask().pause();
}
Здесь вам нужно получить доступ к api
и user
в то же время, чтобы вам, вероятно, нужно было вложить lambdas:
getApi().filter(Api::hasTasks).ifPresent(api -> {
api.getUser().ifPresent(user -> {
api.getMasterUser(user).getCurrentTask().ifPresent(Task::pause);
});
});
Ответ 2
Для второго примера (применимо также и для первого) это короче и примерно столь же очевидно, как и более длинная версия:
Optional.ofNullable(getApi())
.map(Api::getUser)
.flatMap(u -> Optional.ofNullable(u.getDepartment().getBoss()))
.ifPresent(Boss::somefunc);
Он также использует меньше API.
Я хотел бы также прокомментировать ваше "это нарушает шаблон монады". Ничто здесь (включая ваши решения) не разрушает шаблон монады. Он полностью выражен в терминах return
и >>=
. Во всяком случае, это вызов ifPresent
, который разбивает его, потому что он подразумевает побочные эффекты.
Ответ 3
Итак, ответ для первого примера -
Optional.ofNullable(getApi())
.filter(Api::hasTasks)
.map(Api::getUser)
.map(u -> Objects.requireNonNull(getApi().getMasterUser(u)))//api won't be null here so no need to check it
.map(MasterUser::getCurrentTask)
.ifPresent(Task::pause);
и для второго примера:
Optional.ofNullable(getApi())
.map(Api::getUser)
.map(u -> Objects.requireNonNull(u.getDepartment()))
.map(Department::getBoss)
.ifPresent(Boss::somefunc);
Таким образом, вам нужно изменить .map(class::func)
на .map(o -> Objects.requireNonNull(o.func()))
, чтобы заставить его генерировать NRE при необходимости.
Это, конечно, ломает шаблон монады, но он все же лучше, чем никакое решение
Исправьте меня, если я ошибаюсь.