Вопрос-ответ

Why should Java 8's Optional not be used in arguments

Почему необязательные параметры Java 8 не должны использоваться в аргументах

Я читал на многих веб-сайтах, что необязательный параметр следует использовать только как возвращаемый тип и не использовать в аргументах метода. Я изо всех сил пытаюсь найти логическую причину, почему. Например, у меня есть часть логики, которая имеет 2 необязательных параметра. Поэтому я думаю, что имело бы смысл написать сигнатуру моего метода следующим образом (решение 1):

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2) {
// my logic
}

На многих веб-страницах указано, что необязательные параметры не должны использоваться в качестве аргументов метода. Имея это в виду, я мог бы использовать следующую сигнатуру метода и добавить четкий комментарий Javadoc, чтобы указать, что аргументы могут быть нулевыми, в надежде, что будущие сопровождающие будут читать Javadoc и, следовательно, всегда выполнять проверки null перед использованием аргументов (решение 2):

public int calculateSomething(String p1, BigDecimal p2) {
// my logic
}

В качестве альтернативы я мог бы заменить свой метод четырьмя общедоступными методами, чтобы обеспечить более приятный интерфейс и сделать его более очевидным, что p1 и p2 необязательны (решение 3):

public int calculateSomething() {
calculateSomething(null, null);
}

public int calculateSomething(String p1) {
calculateSomething(p1, null);
}

public int calculateSomething(BigDecimal p2) {
calculateSomething(null, p2);
}

public int calculateSomething(String p1, BigDecimal p2) {
// my logic
}

Теперь я пытаюсь написать код класса, который вызывает этот фрагмент логики для каждого подхода. Сначала я извлекаю два входных параметра из другого объекта, который возвращает Optionals, а затем вызываю calculateSomething. Следовательно, если используется решение 1, вызывающий код будет выглядеть следующим образом:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1, p2);

если используется решение 2, вызывающий код будет выглядеть следующим образом:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));

если применяется решение 3, я мог бы использовать приведенный выше код или я мог бы использовать следующий (но это значительно больше кода):

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result;
if (p1.isPresent()) {
if (p2.isPresent()) {
result = myObject.calculateSomething(p1, p2);
} else {
result = myObject.calculateSomething(p1);
}
} else {
if (p2.isPresent()) {
result = myObject.calculateSomething(p2);
} else {
result = myObject.calculateSomething();
}
}

Итак, мой вопрос таков: почему использование Optionals в качестве аргументов метода считается плохой практикой (см. Решение 1)? Для меня это выглядит как наиболее удобочитаемое решение и делает наиболее очевидным, что параметры могут быть пустыми / null для будущих сопровождающих. (Я знаю, что разработчики Optional предполагали использовать его только как возвращаемый тип, но я не могу найти никаких логических причин не использовать его в этом сценарии).

Переведено автоматически
Ответ 1

О, к этим стилям кодирования следует относиться с некоторой долей осторожности.


  1. (+) Передача необязательного результата другому методу без какого-либо семантического анализа; оставляя это методу, вполне нормально.

  2. (-) Использование необязательных параметров, вызывающих условную логику внутри методов, буквально контрпродуктивно.

  3. (-) Необходимость упаковки аргумента в необязательный параметр неоптимальна для компилятора и приводит к ненужному переносу.

  4. (-) По сравнению с обнуляемыми параметрами Необязательные параметры являются более дорогостоящими.

  5. (-) Риск того, что кто-то передаст необязательный параметр как null в фактических параметрах.

В целом: Необязательный параметр объединяет два состояния, которые необходимо разгадать. Следовательно, он лучше подходит для результата, чем для ввода, из-за сложности потока данных.

Ответ 2

Лучший пост, который я видел по этой теме, был написан Дэниелом Ольшевски:


Хотя может возникнуть соблазн рассмотреть необязательные параметры для необязательных параметров метода, такое решение бледнеет по сравнению с другими возможными альтернативами. Чтобы проиллюстрировать проблему, изучите следующее объявление конструктора:


public SystemMessage(String title, String content, Optional<Attachment> attachment) {
// assigning field values
}

На первый взгляд это может показаться правильным дизайнерским решением. В конце концов, мы
явно отметили параметр attachment как необязательный. Однако, поскольку
для вызова конструктора клиентский код может стать немного
неуклюжим.


SystemMessage withoutAttachment = new SystemMessage("title", "content", Optional.empty());
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", Optional.ofNullable(attachment));

Вместо того, чтобы вносить ясность, фабричные методы необязательного класса
только отвлекают читателя. Обратите внимание, что есть только один необязательный параметр
, но представьте, что у него два или три. Дядя Боб определенно
не гордился бы таким кодом 😉


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


public SystemMessage(String title, String content) {
this(title, content, null);
}

public SystemMessage(String title, String content, Attachment attachment) {
// assigning field values
}

Это изменение значительно упрощает клиентский код и облегчает его чтение.


SystemMessage withoutAttachment = new SystemMessage("title", "content");
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", attachment);

Ответ 3

Почти нет веских причин не использовать Optional в качестве параметров. Аргументы против этого основаны на аргументах авторитетных источников (см. Брайана Гетца - его аргумент в том, что мы не можем применять ненулевые опции) или что Optional аргументы могут быть нулевыми (по сути, тот же аргумент). Конечно, любая ссылка в Java может быть нулевой, нам нужно поощрять применение правил компилятором, а не памятью программиста (что проблематично и не масштабируется).

Функциональные языки программирования поощряют Optional параметры. Один из лучших способов использовать это - иметь несколько необязательных параметров и использовать liftM2 для использования функции, предполагающей, что параметры не являются пустыми, и возвращающей необязательный параметр (см. http://www.functionaljava.org/javadoc/4.4/functionaljava/fj/data/Option.html#liftM2-fj.F-). В Java 8, к сожалению, реализована очень ограниченная библиотека, поддерживающая необязательный параметр.

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

Ответ 4

Давайте внесем полную ясность: в других языках нет общих рекомендаций против использования типа Maybe в качестве типа поля, типа параметра конструктора, типа параметра метода или типа параметра функции.

Итак, если вы "не должны" использовать Optional в качестве типа параметра в Java, причина специфична для Optional, для Java или для обоих.

Рассуждения, которые могут быть применимы к другим типам Maybe или другим языкам, вероятно, здесь недопустимы.

Согласно Брайану Гетцу,


У [W] e действительно было четкое намерение при добавлении [Необязательного], и это не должен был быть тип Maybe общего назначения, как бы многим людям ни хотелось, чтобы мы это сделали. Нашим намерением было предоставить ограниченный механизм для типов, возвращаемых библиотечным методом, где требовался четкий способ представления "нет результата", и использование null для такого было в подавляющем большинстве случаев причиной ошибок.


Например, вам, вероятно, никогда не следует использовать его для чего-то, что возвращает массив результатов или список результатов; вместо этого возвращайте пустой массив или список. Вам почти никогда не следует использовать его как поле чего-либо или параметр метода.


Итак, ответ специфичен для необязательного: это не "тип Maybe общего назначения"; как таковой, он ограничен, и он может быть ограничен способами, которые ограничивают его полезность в качестве типа поля или типа параметра.

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

java java-8