it-swarm.com.ru

Почему в аргументах 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
}

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

269
Neil Stevens

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

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

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

138
Joop Eggen

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

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

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

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

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

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

Когда метод может принимать необязательные параметры, предпочтительно использовать проверенный подход и спроектировать такой случай, используя метод перегрузка. В примере класса 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);
119
Gili

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

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

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

64
Mark Perry

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

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

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

Шаблон с Optional предназначен для избежания returningnull. Все еще вполне возможно передать null методу.

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

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

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

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

6
Steve B.

Я считаю, что Optional должен быть монадой, а в Java это невозможно представить.

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

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

3
Eddy

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

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

2
Kieran

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

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

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

1
Danil Gaponov

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

1
Macchiatow

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

Примечание API: Необязательный, в первую очередь, предназначен для использования в качестве возвращаемого типа метода, когда существует явная необходимость представлять «нет результата», и где использование нуля может привести к ошибкам.

1
Xiaolong

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

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

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's a 
  string >));

ссылка:

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

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

1
gpilotino

Прежде всего, если вы используете метод 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()

Вы не можете передать его в 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;
0
MiguelMunoz

Сначала я также предпочел передать Optionals в качестве параметра, но если вы переключитесь с перспективы API-Designer на перспективу 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();
0
Torsten

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

// 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 уродливым, если не сказать больше.

0
Swaraj Yadav

Использование 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, что будет бременем, особенно если количество аргументов возрастет.

0
Awan Biru

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

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

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

0
speedogoo

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

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

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

0
Blair