Статьи

Дополнительные параметры Java

Когда вы разрабатываете метод в классе Java, некоторые параметры могут быть необязательными для его выполнения. Независимо от того, находится ли он внутри DTO, объекта домена с толстой моделью или простого класса обслуживания без сохранения состояния, дополнительные параметры метода являются общими.

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

Давайте начнем.

1. Необязательные параметры метода

Вы можете использовать необязательные параметры Java в методе несколькими различными способами. Я проведу вас от самого простого к более сложному.

1.1. @ Обнуляемая аннотация

Почему бы просто не пропустить ноль? Это простое решение, которое не требует дополнительной работы. У вас нет объекта, который требуется в качестве одного из параметров метода? Нет проблем. Просто передайте ноль, и компилятор счастлив.

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

Чтобы было ясно, что значение null является допустимым, вы можете использовать аннотацию @Nullable .

1
2
3
User createUser(String name, @Nullable Email email) {
   // ...
}

Разве вы не согласны, что это объявление метода самоочевидно?

необязательные параметры

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

Но есть и другие варианты.

1.2. Дополнительные списки

Вместо нуля мы можем иногда создать пустое представление класса. Подумайте о коллекциях Java. Если метод принимает список или карту, вы никогда не должны использовать пустые значения в качестве входных данных.

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

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

Ваши сомнения оправданы. К счастью, есть простое решение.

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

И знаешь, что? В Java уже есть пустые экземпляры всех коллекций, которые вы можете использовать. Вы найдете их в служебном классе Collections .

1
2
3
User createUser(String name, List<Rights> rights) {
   // ...
}
1
2
3
import java.util.Collections;
// ...
create("bob", Collections.emptyList());

1.3. Шаблон нулевого объекта

Концепция пустых коллекций также применима к другим классам. Пустая коллекция — это обычная коллекция с нулевыми элементами. Точно так же вы можете думать о других объектах в ваших приложениях.

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

необязательные параметры

Вы можете реализовать шаблон объекта Null двумя способами.

Для объектов простых значений достаточно экземпляра по умолчанию с предопределенными значениями, назначенными свойствам. Обычно этот объект Null выставляется как константа, поэтому вы можете использовать его несколько раз. Например:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class User {
 
   public static final User EMPTY = new User("", Collections.emptyList());
 
   private final String name;
   private final List<Rights> rights;
 
   public User(String name, List<Rights> rights) {
       Objects.requireNonNull(name);
       Objects.requireNonNull(rights);
       this.name = name;
       this.rights = rights;
   }
 
   // ...
 
}

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

Вот пример, который расширяет предыдущий:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public class AnonymousUser extends User {
 
   public static final AnonymousUser INSTANCE = new AnonymousUser();
 
   private AnonymousUser() {
      super("", Collections.emptyList());
   }
 
   @Override
   public void changeName(String newName) {
       throw new AuthenticationException("Only authenticated user can change the name");
   }
 
}

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

1.4. Перегрузка метода

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

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

1
2
3
4
5
6
7
8
9
User createUser(String name) {
   this.createUser(name, Email.EMPTY);
}
 
User createUser(String name, Email email) {
   Objects.requireNonNull(name);
   Objects.requireNonNull(rights);
   // ...
}

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

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

1,5. Параметр Шаблон объекта

Большинство разработчиков сходятся во мнении, что когда список параметров метода растет слишком долго, его становится трудно читать. Обычно вы решаете проблему с помощью шаблона Parameter Object . Объект параметров является именованным контейнерным классом, который группирует все параметры метода.

Решает ли это проблему необязательных параметров метода?

Нет.

Он просто перемещает задачу в конструктор объекта параметра.

Давайте посмотрим, как мы можем решить эту более общую проблему с …

2. Необязательные параметры конструктора

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

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

Если вы согласны, вы должны проверить шаблон Builder.

2.1. Образец строителя

Давайте рассмотрим класс с несколькими необязательными полями:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
class ProgrammerProfile {
 
   // required field
   private final String name;
   // optional fields
   private final String blogUrl;
   private final String twitterHandler;
   private final String githubUrl;
 
   public ProgrammerProfile(String name) {
       Objects.requireNonNull(name);
       this.name = name;
       // other fields assignment...
   }
 
   // getters
 
}

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

Как избежать нескольких конструкторов? Используйте класс строителя.

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

Взгляните на конструктор класса из предыдущего примера:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class ProgrammerProfile {
 
   // fields, getters, ...
 
   private ProgrammerProfile(Builder builder) {
       Objects.requireNonNull(builder.name);
       name = builder.name;
       blogUrl = builder.blogUrl;
       twitterHandler = builder.twitterHandler;
       githubUrl = builder.githubUrl;
   }
 
   public static Builder newBuilder() {
       return new Builder();
   }
 
   static final class Builder {
       
       private String name;
       private String blogUrl;
       private String twitterHandler;
       private String githubUrl;
 
       private Builder() {}
 
       public Builder withName(String val) {
           name = val;
           return this;
       }
 
       public Builder withBlogUrl(String val) {
           blogUrl = val;
           return this;
       }
 
       public Builder withTwitterHandler(String val) {
           twitterHandler = val;
           return this;
       }
 
       public Builder withGithubUrl(String val) {
           githubUrl = val;
           return this;
       }
 
       public ProgrammerProfile build() {
           return new ProgrammerProfile(this);
       }
 
   }
 
}

Вместо общедоступного конструктора мы предоставляем только один статический метод фабрики для внутреннего класса компоновщика. Закрытый конструктор (который конструктор вызывает в методе build () ) использует экземпляр компоновщика для назначения всех полей и проверки наличия всех необходимых значений.

Это довольно простая техника, если подумать.

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

1
2
3
4
ProgrammerProfile.newBuilder()
       .withName("Daniel")
       .withBlogUrl("www.dolszewski.com/blog/")
       .build();

С помощью компоновщика вы можете создавать все возможные комбинации с необязательными параметрами объекта.

необязательные параметры

2.2. Компилятор безопасных классов времени компиляции

К сожалению, просто взглянув на методы компоновщика из предыдущего абзаца, вы не сможете точно определить, какие параметры являются необязательными, а какие — обязательными. Более того, не зная, вы можете опустить необходимые параметры случайно.

Посмотрите на следующий пример неправильно используемого компоновщика:

1
2
3
4
ProgrammerProfile.newBuilder()
       .withBlogUrl("www.dolszewski.com/blog/")
       .withTwitterHandler("daolszewski")
       .build();

Компилятор не сообщит ни об одной ошибке. Вы поймете проблему с отсутствующим обязательным параметром только во время выполнения.

Итак, как вы решаете проблему?

Вам нужно немного изменить метод фабрики компоновщика, чтобы его можно было вызывать только с необходимыми параметрами, а оставленные методы компоновщика — только для дополнительных параметров.

Вот все, что вам нужно изменить:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
class ProgrammerProfile {
    
   // ...
 
   public static Builder newBuilder(String name) {
      return new Builder(name);
   }
 
   public static final class Builder {
 
      private final String name;
      // ...
 
      private Builder(String name) {
          this.name = name;
      }
 
      // ...
 
   }
}

2,3. Создание класса Builder

Вы можете подумать, что сборщики требуют чертовски много кода.

Не беспокойся

Вам не нужно вводить весь этот код самостоятельно. Все популярные Java IDE имеют плагины, которые позволяют генерировать построители классов. Пользователи IntelliJ могут проверить плагин InnerBuilder , а поклонники Eclipse могут взглянуть на Spart Builder Generator . Вы также можете найти альтернативные плагины в официальных репозиториях.

Если вы используете проект Lombok , это также упростит работу со строителями классов. Вы можете проверить это краткое введение в Lombok строителей, если вам нужно место для начала.

3. Необязательные параметры Java-шаблонов

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

3.1. Карты

Технически говоря, вход метода — это набор пар ключ-значение. В Java у нас есть стандартная встроенная структура данных, которая соответствует этому описанию — Карта.

Компиляция не помешает вам использовать HashMap <String, Object> в качестве контейнера для всех необязательных параметров метода, но ваш здравый смысл должен.

Хотя вы можете поместить что-либо в такой HashMap, это неправильная идея. Этот подход труден для понимания, нечитаем и быстро станет вашим кошмаром обслуживания.

Все еще не убежден?

Вам определенно стоит подумать о том, чтобы сменить карьеру на разработчика JavaScript. В компании гораздо проще плакать.

3.2. Java Varargs

Просто чтобы быть ясно, нет ничего плохого в использовании Java Varargs. Если вы не знаете, сколько аргументов будет вызывать ваш метод, varargs идеально подойдет.

Но использование varargs в качестве контейнера для одного значения, которое может присутствовать или нет, является неправильным использованием. Такое объявление позволяет вызывать метод с более необязательными значениями, чем ожидалось. Мы обсудили гораздо более описательные подходы для обработки отдельных необязательных параметров.

3.3. Почему не опционально в качестве аргумента метода?

Наконец, самый спорный подход — Java 8 Необязательный метод ввода. Я уже написал пост о дополнительных вариантах использования, в которых я также рассмотрел параметры метода. Позвольте мне расширить то, что вы можете найти там.

Использование памяти

Когда вы создаете экземпляр класса Optional, вы должны выделить для него память. Хотя пустой необязательный экземпляр, доступ к которому осуществляется с помощью Optional.empty (), является одноразовым одноразовым (точно так же, как пустые коллекции, которые мы уже обсуждали), непустые экземпляры будут занимать операционную память.

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

Тем не менее, в настоящее время сборщики мусора хорошо справляются с недолговечными объектами. Распределение памяти не имеет большого значения. Есть ли у нас другие минусы?

необязательные параметры

Кодирование с учетом читателя

Как насчет читаемости кода?

1
2
createProfile("Daniel", Optional.of("www.dolszewski.com/blog/"),
       Optional.of("daolszewski"), Optional.of("https://github.com/danielolszewski"));

Возможно, это просто вопрос личных предпочтений, но для многих разработчиков множественные необязательные вызовы фабрики отвлекают. Шум в коде для читателя. Но опять же, это просто вопрос вкуса. Давайте найдем что-то более убедительное.

необязательные параметры

Мнение архитектора Java языка

Брайан Гетц, архитектор языка Java в Oracle, однажды заявил, что Optional был добавлен в стандартную библиотеку с учетом результатов методов, а не их входных данных.

Но разработчики программного обеспечения являются мятежниками и не любят прислушиваться к авторитетам. Этот аргумент также может показаться слабым. Мы должны идти глубже.

необязательные параметры

Решает ли Optional проблему с необязательными параметрами?

Если у вас есть объявление метода, подобное этому:

1
doSomethingWith(Optional<Object> parameter);

Сколько возможных входных данных вы должны ожидать?

Параметр может быть либо упакованным значением, либо пустым необязательным экземпляром. Так что ответ 2, верно?

Неправильно.

Реальный ответ — 3, потому что вы также можете передать значение NULL в качестве аргумента. Вы должны иметь ограниченное доверие к входным данным, если вы не знаете, кто будет вызывать ваш API.

В этом случае, перед обработкой параметра вы должны проверить, не является ли Optional равным NULL, а затем, если значение присутствует. Довольно сложно, ты не согласен?

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

Вывод

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

Эти альтернативы включают аннотацию @Nullable , нулевые объекты, перегрузку методов и шаблон объекта параметров. Мы также знакомимся со строителями классов для объектов с несколькими необязательными полями. Наконец, мы рассмотрели распространенную плохую практику и возможные злоупотребления.

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

Смотрите оригинальную статью здесь: дополнительные параметры Java

Мнения, высказанные участниками Java Code Geeks, являются их собственными.