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

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

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

Многие параметры веб-страниц Необязательно не следует использовать в качестве аргументов метода. Имея это в виду, я мог бы использовать следующую сигнатуру метода и добавить ясный комментарий Javadoc, чтобы указать, что аргументы могут быть нулевыми, надеясь, что будущие сопровождающие будут читать Javadoc и поэтому всегда выполняют нулевые проверки до использования аргументов (решение 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
}

Теперь я попробую написать код класса, который вызывает этот кусок логики для каждого подхода. Сначала я извлекаю два входных параметра из другого объекта, который возвращает Optional, а затем вызываю 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();
    }
}

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

Ответ 1

О, эти стили кодирования должны быть взяты с небольшим количеством соли.

  1. (+) Передача необязательного результата другому методу без какого-либо семантического анализа; оставив это методу, вполне нормально.
  2. (-) Использование необязательных параметров, вызывающих условную логику внутри методов, в буквальном смысле неэффективно.
  3. (-) Необходимость упаковки аргумента в необязательном параметре является неоптимальной для компилятора и выполняет ненужное перенос.
  4. (-) По сравнению с обнуляемыми параметрами Optional является более дорогостоящим.

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

Ответ 2

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

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

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));

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

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

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

Этот совет представляет собой вариант "быть как можно более неопределенным в отношении входных данных и как можно более конкретное правило вывода".

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

  • вы хотите, чтобы ваша функция использовалась в сочетании с другим API, который возвращает Optional
  • Ваша функция должна возвращать нечто, отличное от пустого Optional, если заданное значение пуст
  • Вы думаете, что Optional настолько потрясающий, что тот, кто использует ваш API, должен будет узнать об этом; -)

Ответ 5

Образец с Optional предназначен для того, чтобы избежать возврата null. По-прежнему можно передать метод null в метод.

Пока они еще не являются официальными, вы можете использовать аннотации JSR-308 style, чтобы указать, принимаете ли вы значения null или нет. Обратите внимание, что вам нужно будет иметь правильную оснастку, чтобы ее идентифицировать, и она обеспечивала бы более статическую проверку, чем применяемая политика выполнения, но это помогло бы.

public int calculateSomething(@NotNull final String p1, @NotNull final String p2) {}

Ответ 6

Это кажется мне немного глупым, но единственная причина, по которой я могу думать, это то, что аргументы объекта в параметрах метода уже являются необязательными в некотором смысле - они могут быть нулевыми. Поэтому заставить кого-то взять существующий объект и обернуть его в необязательный вариант - это бесполезно.

Таким образом, методы объединения друг с другом, которые принимают/возвращают варианты, являются разумной вещью, например, Возможно, монада.

Ответ 7

Мое занятие заключается в том, что необязательный должен быть Monad, и это невозможно в Java.

В функциональном программировании вы имеете дело с функциями чистого и высшего порядка, которые принимают и составляют свои аргументы только на основе их типа "бизнес-домена". Составляющие функции, которые питаются или чьи вычисления должны сообщаться, в реальном мире (так называемые побочные эффекты), требуют применения функций, которые заботятся о автоматической распаковке значений из монадов, представляющих внешний мир (состояние, конфигурация, Фьючерсы, Может быть, Либо, Писатель и т.д.); это называется подъем. Вы можете рассматривать это как своего рода разделение проблем.

Смешивание этих двух уровней абстракции не облегчает удобочитаемость, поэтому вам лучше избегать этого.

Ответ 8

Еще одна причина быть осторожным при передаче параметра Optional в том, что метод должен делать одну вещь... Если вы передадите параметр Optional, который вы можете использовать, более чем одно, оно может быть похоже на прохождение булевский параметр.

public void method(Optional<MyClass> param) {
     if(param.isPresent()) {
         //do something
     } else {
         //do some other
     }
 }

Ответ 9

Я думаю, это потому, что вы обычно пишете свои функции для управления данными, а затем поднимите его на Optional с помощью map и подобных функций. Это добавляет к нему по умолчанию поведение Optional. Конечно, могут быть случаи, когда необходимо написать свою собственную вспомогательную функцию, которая работает на Optional.

Ответ 10

Я считаю, что резон бытия заключается в том, что вы должны сначала проверить, является ли Факультативный нуль сам, а затем попытаться оценить значение, которое оно обертывает. Слишком много ненужных проверок.

Ответ 11

Опционы не предназначены для этой цели, как приятно объясняет Брайан Гетц.

Вы всегда можете использовать @Nullable, чтобы указать, что аргумент метода может быть нулевым. Использование опции не позволяет вам более подробно писать логику метода.

Ответ 12

Принятие Необязательный, поскольку параметры вызывают ненужное перенос на уровне вызывающего.

Например, в случае:

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {}

Предположим, у вас есть две ненулевые строки (то есть возвращенные из другого метода):

String p1 = "p1"; 
String p2 = "p2";

Вы вынуждены обернуть их в Optional, даже если вы знаете, что они не пустые.

Это становится еще хуже, когда вам приходится сочинять с другими "отображаемыми" структурами, т.е. Либо:

Either<Error, String> value = compute().right().map((s) -> calculateSomething(
< here you have to wrap the parameter in a Optional even if you know it a 
  string >));

ссылка:

методы не должны ожидать Option как параметры, это почти всегда запах кода, который указывает на утечку потока управления от вызывающего к вызываемому, вызывающий должен проверять содержимое Option

ссылка https://github.com/teamdigitale/digital-citizenship-functions/pull/148#discussion_r170862749

Ответ 13

Использование Optional качестве аргументов метода также требует проверки null на себя. Ненужная дублирующая логика. Например:-

void doSomething(Optional<ISOCode> isoOpt, Optional<Product> prodOpt){

    if(isoOpt != null){
       isoOpt.orElse(ISOCode.UNKNOWN);
    }

    if(prodOpt != null){
       prodOpt.orElseThrow(NullPointerException::new);
    }

    //more instructions
}

Представьте себе, если число Optional параметра растет. Если забыли проверку на ноль, возможно, NPE будет выброшен во время выполнения.

Более того, если ваш метод общедоступен, клиент будет вынужден обернуть его параметры в Optionals, что будет бременем, особенно если количество аргументов возрастет.

Ответ 14

Проверьте JavaDoc в JDK10, https://docs.oracle.com/javase/10/docs/api/java/util/Optional.html, добавлена заметка API:

Замечание по API: Optional в первую очередь предназначен для использования в качестве типа возврата метода, где существует явная необходимость представлять "нет результата", и где использование null может привести к ошибкам.

Ответ 15

Еще один подход, что вы можете сделать, это

// get your optionals first
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

// bind values to a function
Supplier<Integer> calculatedValueSupplier = () -> { // your logic here using both optional as state}

После того, как вы создали функцию (поставщик в этом случае), вы сможете передать это как любую другую переменную и сможете ее вызвать, используя

calculatedValueSupplier.apply();

Идея в том, что у вас есть необязательное значение или нет, будет внутренняя деталь вашей функции и не будет в параметре. Мышление функций, когда мы думаем о необязательном как параметр, на самом деле очень полезный метод, который я нашел.

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

Ответ 16

Сначала я также предпочел передать параметр Optionals в качестве параметра, но если вы переключитесь с точки зрения API-дизайнера на перспективу API-User, вы увидите недостатки.

В вашем примере, где каждый параметр является необязательным, я бы предложил изменить метод расчета на собственный класс, например:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

MyCalculator mc = new MyCalculator();
p1.map(mc::setP1);
p2.map(mc::setP2);
int result = mc.calculate();

Ответ 17

Я знаю, что этот вопрос скорее связан скорее с мнением, чем с жесткими фактами. Но недавно я перешел от разработчика .net к java, поэтому недавно присоединился к факультативной стороне. Кроме того, я бы предпочел указать это как комментарий, но поскольку мой точечный уровень не позволяет мне комментировать, я вынужден вместо этого поставить это как ответ.

То, что я делал, что послужило мне хорошим правилом. Является ли использование опций для типов возвращаемых данных и использовать только опции в качестве параметров, если мне требуется как значение параметра "Необязательно", так и "погода" или "нет", значение "Значение" в этом методе.

Если мне нужно только значение, я проверяю isPresent перед вызовом метода, если у меня есть какой-то журнал или другая логика в методе, который зависит от того, существует ли это значение, тогда я с радостью передам необязательный параметр.

Ответ 18

Это потому, что у нас разные требования к пользователю API и разработчику API.

Разработчик несет ответственность за предоставление точной спецификации и правильной реализации. Поэтому, если разработчик уже знает, что аргумент является необязательным, реализация должна правильно с ним работать, независимо от того, является ли он пустым или необязательным. API должен быть максимально простым для пользователя, а null - самым простым.

С другой стороны, результат передается от разработчика API пользователю. Несмотря на то, что спецификация является полной и многословной, существует вероятность того, что пользователь либо не знает об этом, либо просто ленится иметь дело с ней. В этом случае необязательный результат заставляет пользователя написать дополнительный код для обработки возможного пустого результата.

Ответ 19

Прежде всего, если вы используете метод 3, вы можете заменить эти последние 14 строк кода следующим:

int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));

Четыре варианта, которые вы написали, являются удобными методами. Вы должны использовать их только тогда, когда они более удобны. Это также лучший подход. Таким образом, API очень ясно, какие члены необходимы, а какие нет. Если вы не хотите писать четыре метода, вы можете уточнить, как вы называете ваши параметры:

public int calculateSomething(String p1OrNull, BigDecimal p2OrNull)

Таким образом, ясно, что нулевые значения разрешены.

Использование p1.orElse(null) иллюстрирует, насколько многословен наш код при использовании Optional, поэтому я избегаю этого. Необязательно был написан для функционального программирования. Потоки это нужно. Ваши методы, вероятно, никогда не должны возвращать Optional, если нет необходимости использовать их в функциональном программировании. Существуют методы, такие как метод Optional.flatMap(), для которых требуется ссылка на функцию, которая возвращает Optional. Вот его подпись:

public <U> Optional<U> flatMap(Function<? super T,? extends Optional<? extends U>> mapper)

Так что это обычно единственная причина написать метод, который возвращает Optional. Но даже там этого можно избежать. Вы можете передать метод получения, который не возвращает Optional, методу, подобному flatMap(), обернув его в другой метод, который преобразует функцию в правильный тип. Метод обертки выглядит так:

public static <T, U> Function<? super T, Optional<U>> optFun(Function<T, U> function) {
    return t -> Optional.ofNullable(function.apply(t));
}

Предположим, у вас есть такой String getName(): String getName()

Вы не можете передать его в flatMap следующим образом:

opt.flatMap(Widget::getName)//Won't work!

Но вы можете передать это так:

opt.flatMap(optFun(Widget::getName))//Works great!

Вне функционального программирования следует избегать опций.

Брайан Гетц сказал это лучше всего, когда он сказал это:

Причина, по которой Optional был добавлен в Java, заключается в следующем:

return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .findFirst()
    .getOrThrow(() -> new InternalError(...));

чище, чем это:

Method matching =
    Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .getFirst();
if (matching == null)
  throw new InternalError("Enclosing method not found");
return matching;

Ответ 20

Хотя эта тема старая, я заинтересован в ней прямо сейчас.

Лично я предпочитаю решение 3, в то время как предоставление аргумента методу всегда считается ненулевым (мне нравится добавлять lomboks @NonNull). Так что, если вы действительно хотите принять все ваши 4 случая, почему бы не использовать объект параметра? Может быть, ваш другой объект может удовлетворить? В общем, я хотел бы спросить, действительно ли я хочу предоставить API, который принимает эти (необязательные) значения, или я могу изменить архитектуру, чтобы избежать необязательных параметров? Если вы думаете, что вам действительно нужны дополнительные параметры, я предполагаю, что вы нарушаете SRP.