NotNull: примеры (JAVA)
notNull(Object object): voidОписание концепции notNull в Java
Проверка на null предназначена для гарантий того, что значение ссылки не будет равно null в месте использования. Под именем notNull встречаются разные реализации: статические методы, аннотации и типовые приёмы. Частые варианты в экосистеме Java:
- java.util.Objects.requireNonNull - стандартный метод, возвращает переданный объект или выбрасывает
NullPointerException. - com.google.common.base.Preconditions.checkNotNull (Guava) - проверка с выбрасыванием NPE и возможностью указать сообщение или формат.
- org.springframework.util.Assert.notNull - проверка с выбрасыванием
IllegalArgumentException, обычно для входных параметров в Spring-коде. - org.apache.commons.lang3.Validate.notNull - похожая проверка из Commons Lang, выбрасывает
NullPointerException. - Ломбок @NonNull - аннотация, генерирующая проверку при компиляции/делегирующую в рантайме, выбрасывает NPE при null-параметре.
- javax.validation.constraints.NotNull и производные Bean Validation - аннотация для валидации через провайдер (Hibernate Validator), не выбрасывает исключение автоматом, но формирует нарушение валидации.
- java.util.Optional.of - создание Optional из ненулевого значения;
Optional.of(null)бросит NPE и таким образом защищает контракт.
Подробности по сигнатурам и результатам
- Objects.requireNonNull(T obj)
- Аргументы: объект типа T.
- Возвращаемое значение: тот же объект (T) при ненулевом значении.
- Поведение: при null бросает
NullPointerExceptionбез сообщения.
- Objects.requireNonNull(T obj, Supplier<String> messageSupplier)
- Аргументы: объект T и ленивый источник строки (Supplier).
- Возвращаемое значение: объект T при ненулевом значении.
- Поведение: при null бросает NPE с сообщением из Supplier. Supplier вычисляется только в случае ошибки.
- Preconditions.checkNotNull(T reference, Object errorMessage)
- Аргументы: объект и сообщение или формат.
- Возвращаемое значение: объект при ненулевом значении.
- Поведение: при null бросает
NullPointerExceptionс указанным сообщением.
- Assert.notNull(Object object, String message)
- Аргументы: объект и текст сообщения.
- Возвращаемое значение: нет (void). Обычно используется для валидации параметров; при null выбрасывается
IllegalArgumentException.
- @NonNull (Lombok)
- Аргументы: аннотируется параметр, поле или метод.
- Возвращаемое значение: аннотация не возвращает значение; Lombok генерирует код, который в рантайме проверяет параметр и бросает NPE, если он null.
- @NotNull (Bean Validation)
- Аргументы: аннотируется поле/параметр/метод.
- Возвращаемое значение: сама аннотация не бросает исключений; при запуске валидатора возвращает набор нарушений (ConstraintViolation).
Выбор варианта зависит от контекста: для защиты внутренней логики более уместен NPE через Objects.requireNonNull или Guava; для проверки входных параметров API - Assert.notNull или валидация через Bean Validation; для удобства генерации кода - Lombok.
Короткие примеры использования notNull
Ниже несколько минимальных фрагментов с кодом и результатом.
Objects.requireNonNull без сообщения
import java.util.Objects;
public class Example1 {
public static String getName(String name) {
return Objects.requireNonNull(name);
}
public static void main(String[] args) {
System.out.println(getName("Alice"));
System.out.println(getName(null));
}
}
Alice Exception in thread "main" java.lang.NullPointerException at java.util.Objects.requireNonNull(Objects.java:...) at Example1.getName(Example1.java:...) at Example1.main(Example1.java:...)
Objects.requireNonNull с ленивым сообщением
import java.util.Objects;
public class Example2 {
public static String idOrFail(String id) {
return Objects.requireNonNull(id, () -> "ID отсутствует для потока " + Thread.currentThread().getName());
}
}
// Вызов:
// Example2.idOrFail(null);
Exception in thread "main" java.lang.NullPointerException: ID отсутствует для потока main at Example2.idOrFail(Example2.java:...)
Guava Preconditions.checkNotNull
import com.google.common.base.Preconditions;
public class Example3 {
public static String check(String s) {
return Preconditions.checkNotNull(s, "Строка не должна быть null");
}
}
// Example3.check(null);
Exception in thread "main" java.lang.NullPointerException: Строка не должна быть null at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:...) at Example3.check(Example3.java:...)
Optional.of как защита
import java.util.Optional;
public class Example4 {
public static Optional wrap(String s) {
return Optional.of(s);
}
public static void main(String[] args) {
System.out.println(wrap("ok").isPresent());
wrap(null);
}
}
true Exception in thread "main" java.lang.NullPointerException at java.util.Objects.requireNonNull(Objects.java:...) at java.util.Optional.of(Optional.java:...) at Example4.wrap(Example4.java:...)
Похожие подходы в Java и их особенности
- Objects.requireNonNull - стандарт, возвращает значение, удобен внутри конструкторов и фабрик.
- Preconditions.checkNotNull (Guava) - привычен в проектах с Guava, позволяет форматировать сообщение и использовать в однообразных проверках.
- Assert.notNull (Spring) - для проверки аргументов методов в слоях, где ожидается IllegalArgumentException при неверном входе.
- Validate.notNull (Commons) - ещё один вариант из Apache Commons, с аналогичным поведением.
- @NonNull (Lombok) и @NotNull (аннотации) - используются для документирования и генерации проверок или статического анализа, подходят для снижения шаблонного кода.
Когда предпочесть один из вариантов:
- Для простого, переносимого по библиотекам кода -
Objects.requireNonNull. - Если проект уже использует Guava -
Preconditions.checkNotNullдля единообразия с остальными проверками. - Для API и public-параметров в Spring-приложениях -
Assert.notNullили Bean Validation. - Для декларации контракта и интеграции со статическим анализом - аннотации @NotNull/@NonNull.
Аналоги notNull в других языках и отличия
Краткие примеры для популярных языков и ключевые отличия от Java-подходов.
- JavaScript / TypeScript
JS: явная проверка и выбрасывание ошибки.
function requireNonNull(x) { if (x == null) throw new Error('null или undefined'); return x; } console.log(requireNonNull('a')); requireNonNull(null);a Uncaught Error: null или undefined
TypeScript предоставляет оператор постфикса '!' (non-null assertion), который только снимает предупреждение компилятора, но не выполняет проверку в рантайме.
- Python
def require_non_null(x): if x is None: raise ValueError('None not allowed') return x print(require_non_null('x')) require_non_null(None)x Traceback (most recent call last): ... ValueError: None not allowed
В Python аннотации типов не выполняют проверку в рантайме по умолчанию.
- C#
using System; class Program { static void Main() { string s = null; // .NET 6+: ArgumentNullException.ThrowIfNull(s, nameof(s)); } }Unhandled exception. System.ArgumentNullException: Value cannot be null. (Parameter 's')
Начиная с .NET 6 добавлен ThrowIfNull, удобный аналог Java методов.
- PHP
function requireNonNull($x) { if ($x === null) throw new InvalidArgumentException('null'); return $x; } echo requireNonNull('ok'); requireNonNull(null);ok PHP Fatal error: Uncaught InvalidArgumentException: null
- SQL
Констрейнт
NOT NULLна уровне схемы предотвращает запись null в столбец и работает на уровне СУБД, в отличие от рантайм-проверок в приложении. - Go
В Go часто используется явная проверка на nil и возвращение ошибки: возвращаемое значение ошибки вместо исключений.
func RequireNonNil(v interface{}) error { if v == nil { return fmt.Errorf("nil not allowed") } return nil }// Возвращает error при nil, иначе nil
- Kotlin
fun test(s: String?) { val v: String = requireNotNull(s) { "s is null" } }IllegalStateException с сообщением 's is null' при null
Kotlin имеет встроенную систему nullable типов, что делает проверки на null частью типа.
Ключевые отличия: в Java часто используются исключения NPE/IllegalArgumentException, в функциональных языках и Go предпочитают возвращать ошибку. Аннотации в языках с статическим анализом уменьшают число рантайм-проверок.
Типичные ошибки при использовании notNull
- Ожидание, что аннотация @NotNull сама по себе запретит null в рантайме. На практике нужна интеграция с Bean Validator или статический анализ. Пример:
public class User { @javax.validation.constraints.NotNull private String name; } // При простом создании объекта без вызова валидатора name может быть null User u = new User(); System.out.println(u.getName());null
- Использование дорогой операции для сообщения в requireNonNull без Supplier - сообщение формируется всегда и тратит ресурсы. Решение - использовать лямбду-Supplier.
- Путаница между типом исключения: в API иногда ожидается IllegalArgumentException, а requireNonNull бросает NullPointerException. Это влияет на семантику и обработку ошибок.
- Перехват NullPointerException в широком catch-блоке mask-ит реальные ошибки. Лучше проверять явно и возвращать понятные сообщения.
try { doWork(); } catch (Exception e) { // Поймает NPE и скрывает место возникновения } - Доверие к Optional.ofNullable вместо requireNonNull в местах, где null недопустим. Optional может скрыть проблему, если далее код допускает пустой Optional.
Изменения и эволюция методов проверки null
- Objects.requireNonNull существует в Java в течение нескольких версий и остаётся стандартным способом бросать NPE при null.
- Java 9 добавила методы
Objects.requireNonNullElseиObjects.requireNonNullElseGet, которые помогают задать безопасное значение вместо null. - В Java 8 появились Supplier-версии методов и Optional как средство работы с возможным отсутствием значения.
- В экосистеме появились удобства: Lombok автоматизирует генерацию проверок, Guava долгое время задаёт стиль проверок в проектах, а в .NET и других языках появились аналоги в стандартной библиотеке (например, ArgumentNullException.ThrowIfNull в .NET 6).
- Стандарты валидации (Bean Validation - JSR 380) продолжают развиваться; аннотации @NotNull используют провайдеры вроде Hibernate Validator для генерации сообщений и интеграции с фреймворками.
Расширенные примеры и нетипичные сценарии применения
Несколько продвинутых приёмов с пояснениями.
1. Ленивое сообщение и дорогостоящее вычисление только при ошибке
public class ExpensiveMsg {
public static String compute() {
// имитация тяжёлой операции
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
return "Детальная информация";
}
public static void main(String[] args) {
String x = null;
// Сообщение вычислится только при null
Objects.requireNonNull(x, () -> compute());
}
}
Exception in thread "main" java.lang.NullPointerException: Детальная информация at ExpensiveMsg.main(ExpensiveMsg.java:...)
Пояснение: Supplier предотвращает ненужную работу при положительном сценарии.
2. Использование requireNonNull в конструкторе для явного контракта
public final class Person {
private final String name;
public Person(String name) {
this.name = Objects.requireNonNull(name, "name required");
}
}
// При попытке new Person(null) будет немедленная NPE до создания объекта
Exception in thread "main" java.lang.NullPointerException: name required
Пояснение: ранняя проверка обеспечивает инварианты неизменяемых объектов.
3. Комбинация Bean Validation и Spring MVC для входных DTO
// DTO
public class Req {
@javax.validation.constraints.NotNull
private String code;
// геттеры/сеттеры
}
// Контроллер Spring
@PostMapping("/api")
public ResponseEntity<Void> api(@Valid @RequestBody Req req) {
// поле req.getCode() гарантированно не null после валидации
}
При отправке тела без code фреймворк вернёт 400 с сообщением о нарушении валидации
Пояснение: аннотации работают с инфраструктурой валидации и возвращают клиенту понятный ответ вместо NPE.
4. Генерация проверок Lombok для методов и полей
import lombok.NonNull;
public class LombokExample {
public void process(@NonNull String s) {
System.out.println(s.length());
}
public static void main(String[] args) {
new LombokExample().process(null);
}
}
Exception in thread "main" java.lang.NullPointerException: s is marked non-null but is null at LombokExample.process(LombokExample.java:...)
Пояснение: Lombok генерирует проверку в начале метода, включающую имя параметра в сообщение.
5. Создание утилиты notNull для цепочек валидации
public final class V {
public static <T> T notNull(T v, String message) {
return Objects.requireNonNull(v, () -> message);
}
}
// Использование
String val = V.notNull(map.get("key"), "Ключ отсутствует");
При отсутствии ключа - NPE с сообщением 'Ключ отсутствует'
Пояснение: небольшая утилита упрощает стиль проверок и даёт одно место для модификации поведения.
6. Комбинация Optional и requireNonNull для ясности контрактов
public Optional<String> findNonNull(String id) {
String raw = repository.get(id); // может вернуть null
return Optional.ofNullable(raw).map(Objects::requireNonNull);
}
Если raw == null - Optional.empty(), иначе Optional с значением
Пояснение: использование Optional.ofNullable позволяет явно сигнализировать о возможном отсутствии значения, а requireNonNull внутри map не сработает (map не будет вызван при empty). Это демонстрирует сочетание стилей для ясности.