Какой смысл в гуаве checkNotNull

Я довольно новичок в Guava (давайте будем честными, я не «довольно новичок», я полный новичок в этом вопросе), поэтому я решил просмотреть некоторую документацию и был очень поражен, читая это:

com.google.common.base.Preconditions.checkNotNull(...)< /а>

Я не понимаю смысла этого метода. Это означает, что вместо выполнения:

myObject.getAnything();

(что может вызвать NullPointerException, если myObject имеет значение null)

я должен использовать

checkNotNull(myObject).getAnything();

который выдаст NullPointerException, если myObject равно null, и вернет myObject, если оно не равно null.

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

Какой в ​​этом смысл? Эти две строки делают то же самое, что и для результатов в любых ситуациях, которые я могу придумать.

Я даже не думаю, что последний более читаем.

Значит, я что-то упускаю. Что это?


person Ar3s    schedule 03.10.2014    source источник
comment
Мне не очень нравится идея добавления checkNotNull в каждом месте. ИМХО, было бы предпочтительнее, если бы вы явно проверяли свой ввод, вместо того, чтобы чрезмерно полагаться на синтаксический сахар, который помогает запутать ваш код. Fail fast — это нормально, но checkNotNull просто не дает никакой другой информации для устранения неполадок --> вы можете просто сделать свое собственное исключение для неправильного формата ввода и добавить красивое сообщение, объясняющее конкретную ситуацию.   -  person Hoàng Long    schedule 21.12.2015
comment
Я полностью согласен, что это смешно. По моему скромному мнению, проверка на нуль, а затем самостоятельный запуск NPE - это полная чушь. По крайней мере, сгенерируйте исключение ValidationException или IllegalArgumentException.   -  person Lawrence    schedule 29.08.2016
comment
Рассмотрите возможность улучшения диагностики с помощью checkNotNull(reference, errorMessageTemplate, errorMessageArgs): guava.dev/releases/23.0/api/docs/com/google/common/base/   -  person Vadzim    schedule 16.06.2020


Ответы (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 будет увеличено. Это может быть не то, что вы хотите; как пользователь класса, я могу ожидать, что если он не принимает нули, то его состояние должно быть неизменным, если вы передадите нулевое значение (другими словами: вызов должен завершиться ошибкой, но он не должен сделать объект недействительным). Очевидно, в этом примере вы также можете исправить это, получив s.length() перед увеличением stringsSeen, но вы можете видеть, что для более длинного и сложного метода может быть полезно сначала проверить, что все ваши аргументы действительны, и только затем изменить состояние :

    public void accept(String s) {
        checkNotNull(s); // that is, s != null is a precondition of the method

        stringsSeen++;
        totalLengthSeen += s.length();
    }
person yshavit    schedule 03.10.2014
comment
Разве не должно быть больше this.s=Preconditions.checkNotNull(s); - person Ar3s; 03.10.2014
comment
но да, я не подумал о чехле для хранения. - person Ar3s; 03.10.2014
comment
Типичный шаблон — this.s = checkNotNull(s); со статическим импортом. - person ColinD; 03.10.2014
comment
@ Ar3s Я добавил еще один вариант использования. - person yshavit; 04.10.2014
comment
Лично я считаю, что такого рода сообщений об ошибках следует избегать. Какой бы трюк вы ни делали, исключение NullPointerException практически не имеет смысла. Я категорически против передачи/возврата null в ЛЮБОЙ функции (если вы не работаете с устаревшим API), поэтому любые попытки сделать это должны привести к исключению WrongApiUsage. - person Hoàng Long; 21.12.2015
comment
@HoàngLong Я также хочу, чтобы checkNotNull выдавал исключение IllegalArgumentException вместо NPE. Для меня NPE означает, что кто-то пытался разыменовать нулевой указатель, а не то, что кто-то поймал тот факт, что он был нулевым, прежде чем он попытался его разыменовать. Но с учетом сказанного я также думаю, что IllegalArgumentException: foo was null не более полезен, чем NullPointerException: foo в любом практическом смысле. Итак, хотя я бы хотел, чтобы ребята из Guava выбрали IllegalArgumentException, удобство и стандартизация checkNotNull переопределяют эту гниду, по крайней мере, для меня. - person yshavit; 02.03.2016
comment
@yshavit, если вы хотите исключение IllegalArgumentException, вы можете использовать checkArgument вместо checkNotNull. checkNotNull имеет достаточно хорошее значение, если вы используете реализацию со строковым объяснением. Он предназначен для защиты, а не для логической проверки. - person A.J. Brown; 20.12.2016

myObject.getAnything(); (что может вызвать исключение NullPointerException, если myObject имеет значение null)

Нет... он будет вызывать NPE всякий раз, когда myObject == null. В Java нет возможности вызвать метод с null приемником (теоретически исключением являются статические методы, но их можно и нужно всегда вызывать без какого-либо объекта).


Я должен использовать checkNotNull(myObject).getAnything();

Нет, не следует. Это было бы довольно избыточно (Обновить).

Вы должны использовать checkNotNull, чтобы быстро ошибиться. Без него вы можете передать недопустимый null другому методу, который передает его дальше, и так далее, и так далее, где он, в конце концов, терпит неудачу. Тогда вам может понадобиться удача, чтобы узнать, что на самом деле самый первый метод должен был отказаться от null.


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

Обновлять

Фактически,

 checkNotNull(myObject).getAnything()

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

 myObject != null ? myObject.getAnything() : somethingElse

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

 myObject!!.getAnything()                    // checkNotNull
 myObject?.getAnything()                     // safe call else null
 myObject?.getAnything() ?: somethingElse    // safe call else somethingElse

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

person maaartinus    schedule 03.10.2014
comment
Так что это похоже на проверку в начале метода, что предварительное условие о методе действительно? - person Juru; 03.10.2014
comment
Точно. Мгновенная проверка недопустимых аргументов гарантирует, что вам не придется искать их позже. Более того, каждый, кто смотрит на метод, видит, что запрещено (конечно, это должно быть задокументировано...). - person maaartinus; 03.10.2014
comment
@Juru: Да, именно поэтому он в классе Preconditions! - person ColinD; 03.10.2014

Я прочитал всю эту ветку несколько минут назад. Тем не менее, я был сбит с толку, почему мы должны использовать checkNotNull. Затем просмотрите документ класса Precondition Guava, и я получил то, что ожидал. Чрезмерное использование checkNotNull определенно ухудшит производительность.

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

Согласно DOC: Ссылка

Использование чекнотнулл:

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);
}
person iamcrypticcoder    schedule 06.03.2017
comment
Даю -1, извините: я не рекомендую использовать это для проверки пользовательских данных, потому что вы захотите дать пользователю более удобное нетехническое сообщение об ошибке. Кроме того, исключения во время выполнения не предназначены на случай, если можно ожидать возникновения ошибочной ситуации. Можно ожидать, что пользователь время от времени совершает ошибку. Я бы предложил проверять аргументы метода в общедоступных методах, но не в частных. Раннее обнаружение ошибок программирования стоит больше, чем преждевременная оптимизация. Заменяйте проверку предварительного условия только в том случае, если вы обнаружите, что это реальная проблема с производительностью. - person Henno Vermeulen; 31.08.2018
comment
@HennoVermeulen Как вы сказали, проверка аргументов метода в общедоступных методах, я также сказал, что это не следует использовать в каждом методе внутреннего API. Спасибо - person iamcrypticcoder; 31.08.2018
comment
Проголосовал за повышение производительности, но, как говорили другие, не лучший подход к проверке пользователей! - person Tullochgorum; 08.11.2019