Стиль конструктора Java: параметры проверки не равны нулю
Каковы наилучшие методы, если у вас есть класс, который принимает некоторые параметры, но никому из них не разрешено быть null
?
Ниже очевидно, но исключение немного неспецифично:
public class SomeClass
{
public SomeClass(Object one, Object two)
{
if (one == null || two == null)
{
throw new IllegalArgumentException("Parameters can't be null");
}
//...
}
}
Здесь исключения позволяют узнать, какой параметр имеет значение null, но конструктор теперь довольно уродлив:
public class SomeClass
{
public SomeClass(Object one, Object two)
{
if (one == null)
{
throw new IllegalArgumentException("one can't be null");
}
if (two == null)
{
throw new IllegalArgumentException("two can't be null");
}
//...
}
Здесь конструктор более аккуратный, но теперь код конструктора на самом деле не находится в конструкторе:
public class SomeClass
{
public SomeClass(Object one, Object two)
{
setOne(one);
setTwo(two);
}
public void setOne(Object one)
{
if (one == null)
{
throw new IllegalArgumentException("one can't be null");
}
//...
}
public void setTwo(Object two)
{
if (two == null)
{
throw new IllegalArgumentException("two can't be null");
}
//...
}
}
Какой из этих стилей лучше?
Или есть альтернатива, которая более широко принята?
Ответы
Ответ 1
Второй или третий.
Потому что он сообщает пользователю вашего API, что именно пошло не так.
За меньшее количество слов используйте Validate.notNull(obj, message)
от commons-lang. Таким образом, ваш конструктор будет выглядеть так:
public SomeClass(Object one, Object two) {
Validate.notNull(one, "one can't be null");
Validate.notNull(two, "two can't be null");
...
}
Размещение проверки в сеттере также приемлемо, с тем же комментарием комментария. Если ваши сеттеры также играют роль сохранения согласованности объектов, вы также можете выбрать третью.
Ответ 2
Вы можете использовать одну из многих библиотек, предназначенную для облегчения проверки предварительных условий. Много кода в Google Guava использует com.google.common.base.Preconditions
Простые статические методы, которые нужно вызывать в начале ваших собственных методов, чтобы проверить правильные аргументы и состояние. Это позволяет создавать такие конструкции, как
if (count <= 0) {
throw new IllegalArgumentException("must be positive: " + count);
}
чтобы заменить его более компактным
checkArgument(count > 0, "must be positive: %s", count);
Он имеет checkNotNull
, который широко используется в Guava. Затем вы можете написать:
import static com.google.common.base.Preconditions.checkNotNull;
//...
public SomeClass(Object one, Object two) {
this.one = checkNotNull(one);
this.two = checkNotNull(two, "two can't be null!");
//...
}
Большинство методов перегружены, чтобы либо не принимать сообщение об ошибке, ни сообщение об ошибке, ни шаблонное сообщение об ошибке с varargs.
Вкл IllegalArgumentException
vs NullPointerException
В то время как ваш исходный код вызывает IllegalArgumentException
on null
аргументы, вместо этого Guava Preconditions.checkNotNull
выбрасывает NullPointerException
.
Здесь цитата из Effective Java 2nd Edition: Пункт 60: Благодарите за использование стандартных исключений:
Возможно, все ошибочные вызовы метода сводятся к незаконному аргументу или незаконному состоянию, но другие исключения стандартно используются для определенных видов незаконных аргументов и состояний. Если вызывающий абонент пропускает null
в каком-то параметре, для которого запрещены значения NULL, соглашение диктует NullPointerException
, а не IllegalArgumentException
.
A NullPointerException
не резервируется только при доступе к элементам ссылки null
; довольно стандартно бросать их, когда аргумент null
, когда это недопустимое значение.
System.out.println("some string".split(null));
// throws NullPointerException
Ответ 3
Старый вопрос; еще один новый ответ (уже упомянутый другим комментарием, но я считаю, что стоит его собственный ответ).
Java 7 добавила java.lang.Objects.requireNonNull()
к API, которые все могут использовать. Таким образом, проверка всех аргументов для null сводится к короткому списку, например:
this.arg1 = Objects.requireNonNull(arg1, "arg1 must not be null");
this.arg2 = Objects.requireNonNull(arg2, "arg2 must not be null");
Боковые заметки:
- обязательно не переверните два аргумента - второй - это сообщение, которое будет использоваться для NPE, который вызывается, если первый аргумент равен null (если вы меняете их, ну, тогда ваш чек никогда не потерпит неудачу)
- еще одна лучшая практика: если возможно, сделайте все ваши члены класса окончательными (так что вы можете быть уверены: когда какой-то объект был успешно создан, все его члены не являются нулевыми, и они не будут меняться со временем)
Ответ 4
У меня был бы метод утилиты:
public static <T> T checkNull(String message, T object) {
if(object == null) {
throw new NullPointerException(message);
}
return object;
}
Я хотел бы, чтобы он возвращал объект, чтобы вы могли использовать его в назначениях следующим образом:
public Constructor(Object param) {
this.param = checkNull("Param not allowed to be null", param);
}
РЕДАКТОР. Что касается предложений об использовании сторонней библиотеки, предпосылки Google, в частности, делают это даже лучше, чем мой код. Однако, если это единственные причины для включения библиотеки в ваш проект, я бы колебался. Метод слишком прост.
Ответ 5
Помимо приведенных выше ответов, которые являются действительными и разумными, я считаю, что полезно отметить, что, возможно, проверка нулевого значения не является обязательной "хорошей практикой". (Предполагая, что читатели, кроме ОП, могут поставить вопрос как догматический)
Из блога Misko Hevery по тестируемости:
Утверждать или не утверждать
Ответ 6
Сравнение способов проверки предусловий в Java - Guava vs. Apache Commons vs. Spring Framework vs. Plain Java Asserts
public static void fooSpringFrameworkAssert(String name, int start, int end) {
// Preconditions
Assert.notNull(name, "Name must not be null");
Assert.isTrue(start < end, "Start (" + start + ") must be smaller than end (" + end + ")");
// Do something here ...
}
public static void fooApacheCommonsValidate(String name, int start, int end) {
// Preconditions
Validate.notNull(name, "Name must not be null");
Validate.isTrue(start < end, "Start (%s) must be smaller than end (%s)", start, end);
// Do something here ...
}
public static void fooGuavaPreconditions(String name, int start, int end) {
// Preconditions
Preconditions.checkNotNull(name, "Name must not be null");
Preconditions.checkArgument(start < end, "Start (%s) must be smaller than end (%s)", start, end);
// Do something here ...
}
public static void fooPlainJavaAsserts(String name, int start, int end) {
// Preconditions
assert null != name : "Name must not be null";
assert start < end : "Start (" + start + ") must be smaller than end (" + end + ")";
// Do something here ...
}
Это резюме этой статьи:
http://www.sw-engineering-candies.com/blog-1/comparison-of-ways-to-check-preconditions-in-java
Ответ 7
Альтернативой отбрасыванию неконтролируемого исключения будет использование assert
. В противном случае я бы выбрал проверенные исключения, чтобы уведомить об этом факта, что конструктор не будет работать с недопустимыми значениями.
Разница между вашими первыми двумя решениями - вам нужно подробное сообщение об ошибке, вам нужно знать, какой параметр не удалось или ему достаточно знать, что экземпляр не мог быть создан из-за незаконных аргументов?
Обратите внимание, что второй и третий пример не могут правильно сообщать, что оба параметра имеют значение null.
Кстати - я голосую за вариант (1):
if (one == null || two == null) {
throw new IllegalArgumentException(
String.format("Parameters can't be null: one=%s, two=%s", one, two));
}
Ответ 8
Аннотации для статического анализа также полезны либо в дополнение, либо на месте проверок времени выполнения.
FindBugs, например, предоставляет аннотацию @NonNull.
public SomeClass (@NonNull Object one, @NonNull Object two) {
Ответ 9
Вы можете просто иметь метод, который принимает все аргументы конструктора, которые необходимо проверить. Этот метод генерирует исключение с определенным сообщением в зависимости от того, какой аргумент недействителен.
Ваш конструктор вызывает этот метод, и если он пройдет, он инициализирует значения.
Ответ 10
Я предполагаю, что вы говорите о встроенном assert
в Java. По-моему, это не очень хорошая идея. Поскольку он может быть включен/выключен с использованием параметров командной строки. Поэтому некоторые говорят, что это приемлемо только для частных методов.
Мои наставники говорят мне не изобретать колесо! Их советы - использовать библиотеки. Они (вероятно) хорошо спроектированы и протестированы. Конечно, вы несете ответственность за то, чтобы вы захватили библиотеку хорошего качества.
Другие говорят мне, что Enterprise ppl - в некоторых терминах - ошибается, и вы вводите большую зависимость - для простых задач - чем требуется. Я тоже могу принять этот момент... Но вот мой последний опыт:
Сначала я написал свой собственный частный метод для проверки нулевых параметров. Это скучно и скучно. Я знаю, что я должен поместить его в класс Utility. Но почему я должен писать это на первом месте, когда кто-то уже это сделал? Я могу сэкономить время, не записывая unit test и не создавая существующий материал. Если вы не хотите тренироваться или учиться, я бы не рекомендовал это делать.
Недавно я начал использовать google guava, и я обнаружил, что вместе с apache commons - как только вы начнете их использовать, вы не будете использовать только один единственный метод. Вы будете открывать и использовать его все больше и больше. В конце это сделает ваш код более коротким, читабельным, более последовательным и более удобным.
Кстати: В зависимости от ваших целей я бы пошел с 2 или 3, используя одну из вышеупомянутых библиотек выше...