Подтвердить что ты не робот

Как переписать код в опции?

В моей текущей работе мы переписываем код на 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, пользователя, босса, но не отдела. как это можно сделать с помощью опций?

4b9b3361

Ответ 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 при необходимости.

Это, конечно, ломает шаблон монады, но он все же лучше, чем никакое решение

Исправьте меня, если я ошибаюсь.