Почему в аргументах 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
s как аргументы метода (см. решение 1)? Это выглядит как наиболее читаемое решение для меня и делает наиболее очевидным, что параметры могут быть пустыми / нулевыми для будущих сопровождающих. (Я знаю, дизайнеры Optional
намеревался использовать его только как тип возвращаемого значения, но я не могу найти никаких логических причин не использовать его в этом сценарии).
29 ответов
О, эти стили кодирования должны быть взяты с небольшим количеством соли.
- (+) Передача необязательного результата другому методу без какого-либо семантического анализа; оставив это методу, вполне нормально.
- (-) Использование необязательных параметров, вызывающих условную логику внутри методов, в буквальном смысле неэффективно.
- (-) Необходимость упаковки аргумента в необязательном параметре является неоптимальной для компилятора и выполняет ненужное перенос.
- (-) По сравнению с обнуляемыми параметрами Optional является более дорогостоящим.
В общем: необязательно объединяет два состояния, которые должны быть распутаны. Следовательно, лучше подходит для результата, чем для ввода, для сложности потока данных.
Лучший пост, который я видел по этой теме, был написан Daniel Olszewski:
Хотя может показаться заманчивым рассмотреть 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);
Почти нет веских причин не использовать Optional
в качестве параметров. Аргументы против этого опираются на аргументы авторитета (см. Брайан Гетц - его аргумент в том, что мы не можем применять ненулевые необязательные параметры) или что Optional
аргументы могут быть нулевыми (по сути, один и тот же аргумент). Конечно, любая ссылка в Java может быть нулевой, мы должны поощрять соблюдение правил компилятором, а не памятью программистов (что проблематично и не масштабируется).
Функциональные языки программирования поощряют Optional
параметры. Одним из лучших способов использования этого является наличие нескольких необязательных параметров и использование liftM2
использовать функцию, предполагая, что параметры не пусты, и возвращая необязательный параметр (см. http://www.functionaljava.org/javadoc/4.4/functionaljava/fj/data/Option.html). К сожалению, в Java 8 реализована очень ограниченная поддержка библиотек.
Как программисты на Java, мы должны использовать только null для взаимодействия с устаревшими библиотеками.
Давайте проясним кое-что: в других языках нет общих рекомендаций против использования типа Maybe в качестве типа поля, типа параметра конструктора, типа параметра метода или типа параметра функции.
Поэтому, если вам "не следует" использовать Optional в качестве типа параметра в Java, причина специфична для Optional, для Java или для обоих.
Рассуждения, которые могут относиться к другим типам Maybe или другим языкам, здесь, вероятно, неверны.
Пер Брайан Гетц,
[Мы] имели четкое намерение при добавлении [Optional], и это не должно было быть типом Maybe общего назначения, как бы многие люди ни хотели, чтобы мы это сделали. Наше намерение состояло в том, чтобы предоставить ограниченный механизм для типов, возвращаемых библиотечными методами, где должен был быть четкий способ представления "нет результата", а использование null для таких типов с большой вероятностью могло вызвать ошибки.
Например, вам, вероятно, никогда не следует использовать его для чего-то, что возвращает массив результатов или список результатов; вместо этого верните пустой массив или список. Вы почти никогда не должны использовать его как поле чего-либо или параметр метода.
Так что ответ специфичен для Optional: это не "тип Maybe общего назначения"; как таковой, он ограничен, и он может быть ограничен способами, ограничивающими его полезность в качестве типа поля или типа параметра.
Тем не менее, на практике я редко сталкивался с проблемой использования Optional в качестве типа поля или типа параметра. Если Optional, несмотря на свои ограничения, работает как тип параметра или тип поля для вашего варианта использования, используйте его.
Может быть, я спровоцирую кучу голосов против и отрицательных комментариев, но... Я терпеть не могу.
Отказ от ответственности: то, что я пишу ниже, на самом деле не является ответом на исходный вопрос, а, скорее, моими мыслями по теме. И единственный источник для этого - мои мысли и мой опыт (с Java и другими языками).
Сначала давайте проверим, зачем вообще кому-то нужно использовать Optional?
Для меня причина проста: в отличие от других языков Java не имеет встроенной возможности определять переменную (или тип) как допускающую значение NULL или нет. Все "объектные"-переменные допускают значение NULL, а все примитивные типы - нет. Для простоты давайте не будем рассматривать примитивные типы в дальнейшем обсуждении, поэтому я просто буду утверждать, что все переменные допускают значение NULL.
Зачем нужно объявлять переменные как допускающие / не допускающие значение null? Что ж, причина для меня в том, что явное всегда лучше, чем неявное. Помимо явного оформления (например, аннотации или типа), может помочь статическому анализатору (или компилятору) выявить некоторые проблемы, связанные с нулевым указателем.
Многие люди утверждают в комментариях выше, что функциям не обязательно иметь аргументы, допускающие значение NULL. Вместо этого следует использовать перегрузки. Но такое утверждение годится только в школьной тетради. В реальной жизни бывают разные ситуации. Рассмотрим класс, который представляет настройки какой-либо системы или личные данные какого-либо пользователя, или фактически любую составную структуру данных, которая содержит множество полей - многие из них имеют повторяющиеся типы, а некоторые поля являются обязательными, а другие - необязательными.. В таких случаях перегрузка наследования / конструктора не помогает.
Случайный пример: допустим, нам нужно собрать данные о людях. Но некоторые люди не хотят предоставлять все данные. И, конечно, это POD, так что в основном тип с семантикой значений, поэтому я хочу, чтобы он был более или менее неизменным (без сеттеров).
class PersonalData {
private final String name; // mandatory
private final int age; // mandatory
private final Address homeAddress; // optional
private final PhoneNumber phoneNumber; // optional. Dedicated class to handle constraints
private final BigDecimal income; // optional.
// ... further fields
// How many constructor- (or factory-) overloads do we need to handle all cases
// without nullable arguments? If I am not mistaken, 8. And what if we have more optional
// fields?
// ...
}
Итак, обсуждение IMO выше показывает, что хотя в большинстве случаев мы можем выжить без аргументов, допускающих значение NULL, но иногда это практически невозможно.
Теперь мы подходим к проблеме: если некоторые аргументы допускают значение NULL, а другие нет, как мы узнаем, какой из них?
Подход 1: все аргументы допускают значение NULL (в соответствии со стандартом Java, кроме примитивных типов). Итак, мы проверяем их все.
Результат: код взрывается проверками, которые в большинстве случаев не нужны, потому что, как мы обсуждали выше, почти всегда мы можем использовать переменные, допускающие значение NULL, и только в некоторых редких случаях необходимы "значения NULL".
Подход 2. Используйте документацию и / или комментарии, чтобы описать, какие аргументы / поля допускают значение NULL, а какие нет.
Результат: не работает. Люди лень писать и читать документы. Кроме того, в последнее время наблюдается тенденция избегать написания документации в пользу того, чтобы код сам по себе описывал себя. Кроме того, все рассуждения об изменении кода и забывании модификации документации остаются в силе.
Подход 3: @Nullable @NonNull и т. Д. Я лично считаю их хорошими. Но есть определенные недостатки: (например, они соблюдаются только внешними инструментами, а не компилятором), худшим из которых является то, что они не являются стандартными, что означает, что 1. Мне нужно было бы добавить внешнюю зависимость в мой проект, чтобы получить выгоду. от них, и 2. То, как они обрабатываются в разных системах, неодинаково. Насколько мне известно, они были отклонены от официального стандарта Java (и я не знаю, есть ли какие-либо планы попробовать еще раз).
Подход 4: Необязательный<>. Недостатки уже упомянуты в других комментариях, худшим из которых является снижение производительности (IMO). Кроме того, он добавляет немного шаблона, хотя я лично считаю, что использование Optional.empty() и Optional.of() не так уж и плохо. Преимущества очевидны:
- Это часть стандарта Java.
- Читателю кода (или пользователю API) становится очевидным, что эти аргументы могут быть нулевыми. Более того, он вынуждает как пользователя API, так и разработчика метода узнать об этом факте, явно упаковывая / разворачивая значения (что не так, когда используются аннотации, такие как @Nullable и т. Д.).
Так что, на мой взгляд, нет однозначного изображения любой методологии, включая эту. Я лично пришел к следующим рекомендациям и соглашениям (которые все еще не являются строгими правилами):
- Внутри моего собственного кода все переменные не должны быть нулевыми (но, вероятно, Optional<>).
- Если у меня есть метод с одним или двумя необязательными аргументами, я пытаюсь переделать его, используя перегрузки, наследование и т. Д.
- Если я не могу найти решение в разумные сроки, я начинаю думать, а критична ли производительность (т.е. есть ли миллионы объектов, которые нужно обработать). Обычно это не так.
- Если нет, я использую Optional в качестве типов аргументов и / или типов полей.
Есть еще серые области, в которых эти условные обозначения не работают:
- Нам нужна высокая производительность (например, обработка огромных объемов данных, так что общее время выполнения очень велико, или ситуации, когда пропускная способность критична). В этом случае снижение производительности, вносимое Optional, может быть действительно нежелательным.
- Мы находимся на границе кода, который пишем сами, например: мы читаем из БД, конечную точку отдыха, файл синтаксического анализа и т. Д.
- Или мы просто используем какие-то внешние библиотеки, которые не соответствуют нашим соглашениям, поэтому, опять же, мы должны быть осторожны...
Кстати, последние два случая тоже могут быть источником необходимости в необязательных полях / аргументах. Т.е. когда структура данных разрабатывается не нами, а навязывается какими-то внешними интерфейсами, db-схемами и т.д.
В конце, я думаю, нужно подумать над проблемой, которая решается, и попытаться найти соответствующие инструменты. Если вариант Optional<> подходит, я не вижу причин не использовать его.
Проверьте JavaDoc в JDK10, https://docs.oracle.com/javase/10/docs/api/java/util/Optional.html, добавлена заметка API:
Замечание по API: Optional в первую очередь предназначен для использования в качестве типа возврата метода, где существует явная необходимость представлять "нет результата", и где использование null может привести к ошибкам.
Шаблон с Optional
для одного, чтобы избежать возвращения null
, Это все еще вполне возможно пройти в null
к методу.
Хотя они пока не являются официальными, вы можете использовать аннотации в стиле JSR-308, чтобы указать, принимаете ли вы null
значения в функцию. Обратите внимание, что вам понадобится правильный инструмент для его идентификации, и он обеспечит больше статической проверки, чем принудительная политика времени выполнения, но это поможет.
public int calculateSomething(@NotNull final String p1, @NotNull final String p2) {}
Этот совет является вариантом правила "быть как можно более неопределенным в отношении входных данных и максимально конкретным в отношении выходных данных".
Обычно, если у вас есть метод, который принимает простое ненулевое значение, вы можете отобразить его поверх Optional
Таким образом, простая версия является строго более неопределенной в отношении входных данных. Однако есть множество возможных причин, почему вы хотели бы потребовать Optional
аргумент тем не менее:
- Вы хотите, чтобы ваша функция использовалась вместе с другим API, который возвращает
Optional
- Ваша функция должна возвращать что-то кроме пустого
Optional
если данное значение пусто -
Ты думаешьOptional
это так здорово, что тот, кто использует ваш API, должен узнать об этом;-)
Итак, если вы позволите каламбур, Oracle выдал оракул:
Вы не должны использовать но для возвращаемых значений функции.
Мне нравится, что большинство ответов до сих пор согласуются с повествованием об оракуле Oracle, которое беспрекословно повторяется по всему Интернету, на «многих веб-сайтах», упомянутых в вопросе. Это очень типично для переполнения стека: если предполагается, что что-то должно быть определенным образом, а вы спрашиваете, почему это должно быть именно так, почти все приведут доводы, почему; почти никто не будет сомневаться, так ли это на самом деле.
Итак, особое мнение:
Вы можете использовать, чтобы полностью исключить из своей кодовой базы.
Я сделал это в проекте из 100 тысяч строк кода. Это сработало.
Если вы решите пойти по этому пути, то вам потребуется основательность, так что работы у вас будет много. Пример, упомянутый в принятом ответе сOptional.ofNulable()
никогда не должно происходить, потому что если вы тщательно, то у вас никогда не должно ничего возвращатьсяnull
, а значит и не надо. В том проекте из 100 тысяч строк кода, о котором я упоминал выше, я использовал толькоOptional.ofNullable()
пару раз при получении результатов от внешних методов, которые я не могу контролировать.
Кроме того, если вы решите пойти по этому пути, ваше решение не будет самым производительным из возможных, потому что вы будете выделять множество опций. Однако:
- Это не что иное, как недостаток производительности во время выполнения.
- Это не серьезный недостаток.
- Это проблема Java, а не ваша проблема.
Позвольте мне объяснить последнюю часть.
Java не предлагает явного обнуления ссылочных типов, как это делает C# (начиная с версии 8.0), поэтому в этом отношении он уступает. (Я сказал «в этом отношении», в остальном Java лучше, но сейчас это не по теме.)
Единственной правильной альтернативой явной возможности обнуления ссылочных типов является тип.
(И, возможно, это даже немного лучше, потому что с вами можно указать необязательный-из-необязательный, если необходимо, тогда как с явной обнуляемостью вы не можете иметьReferenceType??
, или, по крайней мере, вы не можете в C#, как это реализовано в настоящее время.)
не нужно добавлять накладные расходы, это происходит только в Java. Это связано с тем, что Java также не поддерживает истинные типы значений, как это делают C# и Scala. В этом отношении Java сильно уступает этим языкам. (Опять же, я сказал «в этом отношении»; в других отношениях Java лучше, но сейчас это не по теме.) Если бы Java действительно поддерживала истинные типы значений, тоOptional
было бы реализовано как одно машинное слово, что означало бы, что накладные расходы во время выполнения на его использование были бы нулевыми.
Итак, вопрос, который сводится к следующему: хотите ли вы идеальной ясности и безопасности типов в своем коде или предпочитаете максимальную производительность? Я полагаю, что для языков высокого уровня (одним из которых явно стремится стать Java) этот вопрос давно решен.
Это кажется мне немного глупым, но единственная причина, по которой я могу думать, состоит в том, что аргументы объекта в параметрах метода уже являются необязательными - они могут быть нулевыми. Поэтому заставлять кого-то брать существующий объект и оборачивать его в необязательный объект бессмысленно.
Это, как говорится, разумно делать цепочку методов, которые принимают / возвращают необязательные параметры, например, возможно, монаду.
Принятие Необязательный, поскольку параметры вызывают ненужное перенос на уровне вызывающего.
Например, в случае:
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 как параметры, это почти всегда запах кода, указывающий на утечку потока управления от вызывающего к вызываемому, вызывающий должен проверять содержимое Option
ссылка https://github.com/teamdigitale/digital-citizenship-functions/pull/148
Я считаю, что Optional должен быть монадой, а в Java это невозможно представить.
В функциональном программировании вы имеете дело с функциями чистого и более высокого порядка, которые принимают и формируют свои аргументы только на основе своего "типа бизнес-домена". Составление функций, которые питаются или о чьих вычислениях следует сообщать в реальном мире (так называемые побочные эффекты), требует применения функций, которые обеспечивают автоматическую распаковку значений из монад, представляющих внешний мир (State, Configuration, Фьючерсы, Возможно, Либо, Писатель, и т.д...); это называется подъемом. Вы можете думать об этом как о некоем разделении интересов.
Смешение этих двух уровней абстракции не способствует удобочитаемости, поэтому лучше избегать этого.
Еще одна причина, чтобы быть осторожным, когда передать Optional
как параметр, что метод должен делать одну вещь... Если вы передаете Optional
param, который вы можете предпочесть сделать больше, чем одну вещь, это может быть похоже на передачу логического параметра.
public void method(Optional<MyClass> param) {
if(param.isPresent()) {
//do something
} else {
//do some other
}
}
Использование Optional в качестве параметров может быть полезно в некоторых случаях использования, которые связаны с protobufs или настройками полей в объекте конфигурации.
public void setParameters(Optional<A> op1, Optional<B> op2) {
ProtoRequest.Builder builder = ProtoRequest.newBuilder();
op1.ifPresent(builder::setOp1);
op2.ifPresent(builder::setOp2);
...
}
Я думаю, что в таких случаях было бы полезно иметь необязательные параметры в качестве параметров. API, получающий прототип запроса, будет обрабатывать разные поля. Если функция не выполняет дополнительных вычислений с этими параметрами, то использование Optional может быть проще.
public void setParameters(A op1, B op2) {
ProtoRequest.Builder builder = ProtoRequest.newBuilder();
if (op1 != null) {
builder.setOp1(op1);
}
if (op2 != null) {
builder.setOp2(op2);
}
...
}
Я думаю, это потому, что вы обычно пишете свои функции для манипулирования данными, а затем поднимаете их до Optional
с помощью map
и аналогичные функции. Это добавляет по умолчанию Optional
Поведение к этому. Конечно, могут быть случаи, когда необходимо написать собственную вспомогательную функцию, которая работает на Optional
,
Я полагаю, что резонанс бытия заключается в том, что вы должны сначала проверить, является ли Optional нулевым, а затем попытаться оценить значение, которое он оборачивает. Слишком много ненужных проверок.
Дополнительные опции не предназначены для этой цели, как хорошо объяснил Брайан Гетц.
Вы всегда можете использовать @Nullable, чтобы указать, что аргумент метода может быть нулевым. Использование опционально не позволяет вам писать логику метода более аккуратно.
С помощью 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, что будет бременем, особенно если количество аргументов возрастет.
Я знаю, что этот вопрос больше касается мнения, а не убедительных фактов. Но недавно я перешел от разработки.net к разработке на Java, поэтому я только недавно присоединился к факультативной стороне. Кроме того, я бы предпочел указать это как комментарий, но, поскольку мой уровень баллов не позволяет мне комментировать, я вынужден вместо этого поставить это в качестве ответа.
То, что я делаю, послужило мне хорошим эмпирическим правилом. Использовать Optional для возвращаемых типов и использовать Optional только в качестве параметров, если мне требуется как значение Optional, так и погода или нет. Optional имел значение в методе.
Если меня интересует только значение, я проверяю isPresent перед вызовом метода, если у меня есть какая-то логика или другая логика в методе, которая зависит от того, существует ли значение, тогда я с радостью передам в Optional.
Мне бы хотелось получить отзывы о том, почему я использую Optional в качестве параметра, и сейчас я делаю это для некоторых случаев использования. Итак, причина, по которой я его использую, - это когда я хочу сказать, что этот метод принимает аргумент, но может прекрасно обходиться и без него.
Например:
createFace(глаза, нос, рот, Необязательно <Борода>)
Я считаю, что это дает понять вызываемому, что метод будет работать нормально и без бороды (как параметра). Я не хочу надеяться, что вызываемый прочитает документацию, которую я написал, и поймет, что метод отлично справится и без бороды. Я знаю, что существует переопределение методов, и я использую его в большинстве случаев (особенно в приведенном мною примере), но иногда я считаю, что параметр Optional как параметр работает как документация для моих методов, когда переопределение методов кажется излишним.
Хорошим примером были бы необязательные аргументы, это репозитории JPA. Я хотел бы сделать что-то вроде findByNameAndSurname(необязательно, необязательно). Таким образом, если необязательный параметр пуст, WHERE param=y не выполняется.
Независимо от Java 8, используйте метод перегрузки методов старой школы, чтобы внести ясность и гибкость, предположим, что у вас есть следующий метод с двумя аргументами
public void doSomething(arg1,arg2);
если вы хотите добавить дополнительный необязательный параметр, перегрузите метод
public void doSomething(arg1,arg2,arg3) {
Result result = doSomething(arg1,arg2);
// do additional working
}
Хотя эта тема старая, я заинтересован в ней прямо сейчас.
Лично я предпочитаю решение 3, в то время как предоставление аргумента методу всегда считается ненулевым (мне нравится добавлять lomboks @NonNull). Так что, если вы действительно хотите принять все ваши 4 случая, почему бы не использовать объект параметра? Может быть, ваш другой объект может удовлетворить? В общем, я хотел бы спросить, действительно ли я хочу предоставить API, который принимает эти (необязательные) значения, или я могу изменить архитектуру, чтобы избежать необязательных параметров? Если вы думаете, что вам действительно нужны дополнительные параметры, я предполагаю, что вы нарушаете SRP.
Это потому, что у нас разные требования к пользователю API и разработчику API.
Разработчик несет ответственность за предоставление точной спецификации и правильной реализации. Поэтому, если разработчик уже знает, что аргумент является необязательным, реализация должна правильно с ним работать, независимо от того, является ли он пустым или необязательным. API должен быть максимально простым для пользователя, а null - самым простым.
С другой стороны, результат передается от разработчика API пользователю. Несмотря на то, что спецификация является полной и многословной, существует вероятность того, что пользователь либо не знает об этом, либо просто ленится иметь с ней дело. В этом случае необязательный результат заставляет пользователя написать дополнительный код для обработки возможного пустого результата.
Прежде всего, если вы используете метод 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()
метод, который требует ссылки на функцию, которая возвращает Необязательный. Вот его подпись:
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;
Если вам нужен необязательный тип примитива, просто используйте класс примитива.
Для тех, кто имеет опыт работы с C++ и запутался.
Например, для опциональногоboolean
, использовать .
Самая большая проблема заключается в том, что самому параметру может быть присвоено значение null, поэтому теперь у вас есть три состояния вместо двух.
НапримерOptional<Boolean>
может быть (нулевым, пустым или непустым), ноBoolean
может быть только (нулевым или непустым).
Я заметил, что вы упомянули несколько подходов, и за годы, прошедшие с тех пор, как был задан вопрос, одна альтернатива по-настоящему не изучалась -
Идея в том, что с помощью компоновщика вы задаете только те параметры, которые вам нужны, и он очень хорошо масштабируется по мере роста количества аргументов (в отличие от перегрузки).
Вот пример класса, который вы можете использовать:
public class Something {
private String p1;
private Integer p2;
private Something(String p1, Integer p2) {
this.p1 = p1;
this.p2 = p2;
}
public static class Builder {
private String p1;
private Integer p2;
public Builder setP1(String value) {
this.p1 = value;
return this;
}
public Builder setP2(Integer value) {
this.p2 = value;
return this;
}
public Something build() {
// Validate input and set defaults here
return new Something(p1, p2);
}
}
}
Вот как вы можете его использовать:
var something = Something.Builder().setP1("blah").setP2(12345).build();
calculate(something); // or something.calculate()
Теперь вы можете заметить, что приведенная выше реализация содержит изрядное количество шаблонного кода, что является ее основным недостатком. Однако есть библиотеки, которые устраняют это за вас, например FreeBuilder , делая код коротким и аккуратным (условно говоря, Java не славится своей компактностью кода).
Еще один подход, что вы можете сделать, это
// 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 уродливым, если не сказать больше.
Сначала я также предпочел передать 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();