Какой смысл гуавы checkNotNull
Я довольно новичок в Гуаве (давайте будем честны, я не "довольно новичок", я полный новичок в этом вопросе), и поэтому я решил просмотреть некоторую документацию и был очень удивлен, читая это:
com.google.common.base.Preconditions.checkNotNull(...)
Я не понимаю смысл этого метода. Это означает, что вместо того, чтобы делать:
myObject.getAnything();
(что может привести к NullPointerException
если myObject равен нулю)
Я должен использовать
checkNotNull(myObject).getAnything();
который бросит NullPointerException
если myObject
нуль и возврат myObject
если это не нуль.
Я озадачен, и это может быть самый глупый вопрос когда-либо, но...
Какой в этом смысл? Эти две строки делают то же самое, что и для результатов в любых ситуациях, о которых я могу думать.
Я даже не думаю, что последнее более читабельно.
Так что я должен что-то упустить. Что это?
3 ответа
Идея состоит в том, чтобы быстро потерпеть неудачу. Например, рассмотрим этот глупый класс:
public class Foo {
private final String s;
public Foo(String s) {
this.s = s;
}
public int getStringLength() {
return s.length();
}
}
Допустим, вы не хотите разрешать нулевые значения для s
, (или еще getStringLength
скину NPE). С классом как есть, к тому времени, когда вы поймете, что null
, уже слишком поздно - очень трудно выяснить, кто его туда поместил. Виновник вполне может быть в совершенно другом классе, и это Foo
экземпляр мог быть построен давно. Теперь вам нужно прочесать свою кодовую базу, чтобы узнать, кто мог бы поставить null
значение там.
Вместо этого представьте этот конструктор:
public Foo(String s) {
this.s = checkNotNull(s);
}
Теперь, если кто-то ставит null
там вы сразу же узнаете - и у вас будет трассировка стека, указывающая вам точно на вызов, который пошел не так.
В другой раз это может быть полезно, если вы хотите проверить аргументы, прежде чем предпринимать действия, которые могут изменить состояние. Например, рассмотрим этот класс, который вычисляет среднее значение всех длин строк, которые он получает:
public class StringLengthAverager {
private int stringsSeen;
private int totalLengthSeen;
public void accept(String s) {
stringsSeen++;
totalLengthSeen += s.length();
}
public double getAverageLength() {
return ((double)totalLengthSeen) / stringsSeen;
}
}
призвание accept(null)
приведет к тому, что NPE будет брошено - но не раньше stringsSeen
был увеличен. Это может быть не то, что вы хотите; как пользователь класса, я могу ожидать, что если он не принимает значения NULL, то его состояние должно быть неизменным, если вы передаете NULL (другими словами: вызов должен завершиться неудачно, но он не должен сделать объект недействительным). Очевидно, что в этом примере вы также можете исправить это, получив s.length()
перед увеличением stringsSeen
, но вы можете увидеть, как для более длинного и более сложного метода может быть полезно сначала проверить, что все ваши аргументы верны, и только затем изменить состояние:
public void accept(String s) {
checkNotNull(s); // that is, s != null is a precondition of the method
stringsSeen++;
totalLengthSeen += s.length();
}
myObject.getAnything();
(что может вызвать исключение NullPointerException, если myObject имеет значение null)
Нет... он будет бросать NPE всякий раз, когда myObject == null
, В Java нет возможности вызвать метод с null
приемник (теоретическим исключением являются статические методы, но их можно и нужно всегда вызывать без какого-либо объекта).
Я должен использовать
checkNotNull(myObject).getAnything();
Нет, ты не должен. Это было бы довольно излишним (обновление).
Вы должны использовать checkNotNull
чтобы быстро потерпеть неудачу. Без этого вы можете пройти нелегальную null
к другому методу, который передает его дальше, и так далее, и так далее, где он, наконец, терпит неудачу. Тогда вам может понадобиться удача, чтобы выяснить, что на самом деле самый первый метод должен был отказаться null
,
В ответе Ишавита упоминается важный момент: передавать нелегальное значение плохо, но хранить его и передавать позже - еще хуже.
Обновить
На самом деле,
checkNotNull(myObject).getAnything()
также имеет смысл, поскольку вы четко выражаете свое намерение не принимать нули. Без этого кто-то может подумать, что вы забыли чек, и преобразовать его в нечто вроде
myObject != null ? myObject.getAnything() : somethingElse
ОТО, я не думаю, что проверка стоит многословия. В лучшем языке система типов учитывает обнуляемость и дает нам некоторый смысл
myObject!!.getAnything() // checkNotNull
myObject?.getAnything() // safe call else null
myObject?.getAnything() ?: somethingElse // safe call else somethingElse
для обнуляемого myObject
в то время как стандартный синтаксис точки будет разрешен только тогда, когда myObject
как известно, не является нулевым.
Я прочитал всю эту тему несколько минут назад. Тем не менее я был смущен, почему мы должны использовать checkNotNull
, Затем посмотрите на предварительное условие класса документа Guava, и я получил то, что ожидал. Чрезмерное использование checkNotNull
определенно ухудшит производительность.
Моя мысль checkNotNull
Метод необходим для проверки данных, которые исходят от непосредственного пользователя или самого конца API для взаимодействия с пользователем. Его не следует использовать во всех методах внутреннего API, потому что, используя его, вы не можете остановить исключение, а исправьте свой внутренний API, чтобы избежать исключения.
По данным DOC: Ссылка
Использование checkNotNull:
public static double sqrt(double value) {
Preconditions.checkArgument(value >= 0.0, "negative value: %s", value);
// calculate the square root
}
Предупреждение о производительности
Цель этого класса - улучшить читаемость кода, но в некоторых случаях это может привести к значительным потерям производительности. Помните, что значения параметров для построения сообщения должны быть вычислены с нетерпением, а также может произойти автоматическое создание ячеек и создание массива varargs, даже если тогда проверка предусловия завершается успешно (как это должно почти всегда происходить в производстве). В некоторых случаях эти потраченные впустую циклы и распределение ресурсов ЦП могут привести к реальной проблеме. Чувствительные к производительности проверки предварительных условий всегда можно преобразовать в обычную форму:
if (value < 0.0) {
throw new IllegalArgumentException("negative value: " + value);
}