LINUX.ORG.RU

Java, философия использования null-ов и Optional-ов

 , , ,


0

5

Насколько я понял замысел создателей Java - для хранения данных нужно использовать nullable поля. В параметрах публичных функций нужно принимать not-null значения, null нельзя (в приватных можно). Если хочешь принять null, то тебе надо сделать перегрузку для этой функции без соотв. параметра или спроектировать API по-другому. В возвращаемых значениях публичных функций надо использовать Optional (вместо возврата null). В локальных переменных - я точно не понял, но полагаю, что в общем случае nullable, но если из функции данные получил в виде Optional, то можно так и оставить.

В целом это всё вызвано желанием убрать ненужное создание объектов. Optional это полновесный объект, и на ровном месте его создавать не нужно. Также доступ к значению получается через промежуточную ссылку, это тоже не очень хорошо. С локальными переменными и полями можно и без Optional разобраться, оно как бы всё рядом. С параметрами методов - тоже можно разобраться путем создания доп перегрузок. И только с возвращаемыми значениями так разобраться не получится, поэтому тут Optional это необходимое зло.

В Idea есть предупреждения на Optional в параметрах метода и в поле, так что это я как бы не сам придумал. Ну и в статьях про это писали.

Всё это выливается в подобный код:

class Person {
  private final String name;
  private final Integer age;
  
  private Person(String name, Integer age) {
    this.name = requireNonNull(name);
    this.age = age;
  }

  String name() { return name; }

  Optional<Integer> age() { return Optional.ofNullable(age); }

  class Builder {
    private String name;
    private Integer age;

    void name(String name) { this.name = requireNonNull(name); }

    void age(Integer age) { this.age = requireNonNull(age); }

    Person build() { return new Person(name, age); }
  }

}

Тут соблюдены все вышеперечисленные «каноны». Если хочешь создать персону без возраста, ты обязан не вызывать метод age в билдере.

С таким подходом получается, что создать копию персоны можно только так:

var personCopyBuilder = new Person.Builder();
personCopyBuilder.name(person.name());
person.age().ifPresent(age -> personCopyBuilder.age(age));
var personCopy = personCopyBuilder.build();

Если взять язык с nullable системой типов вроде Kotlin, то там как-то всё получается проще и естественней…

В частности мне не нравится вот эта сложная передача null, которая в том числе не даёт chain-ить методы билдера.

Как вы думаете - что тут можно улучшить? Или это хороший код и всё ок?

Скажем, один из вариантов это убрать проверку в Builder.age(Integer) на null, и тогда можно будет писать

var personCopy = new Person.Builder()
    .name(person.name())
    .age(person.age().orElse(null))
    .build()

и по сути тут вроде ничего страшного и нет, кроме нарушения правила не принимать nonnull значения в параметрах.

★★★★

Последнее исправление: vbr (всего исправлений: 4)
Ответ на: комментарий от ya-betmen

Да. И в жабе как раз почти все типы нуллабл

Это в Жабе, я же тебе про логику на уровне кода. В Жабе нету беззнаковых целых, но ты же не проверяешь int birthYear постоянно на отрицательные значения по всему коду приложения, когда достал профиль пользователя с БД. Логика приложения исходит из того, что там уже лежит значение принимающее определенные значения. Его проверили раз и на всегда при записи в БД.

urxvt ★★★★★
()
Ответ на: комментарий от urxvt

Все аргументы в ofNullable() заворачивать?

Это мы уже переходим к вопросу дизайна самого опшенала, по идее нужно было править оф.

И это ничем не будет отличаться от передачи опшеналов в метод.

ya-betmen ★★★★★
()