NotNull: примеры (JAVA)

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

  • Ожидание, что аннотация @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. Ленивое сообщение и дорогостоящее вычисление только при ошибке

Пример java
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 в конструкторе для явного контракта

Пример java
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

Пример java
// 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 для методов и полей

Пример java
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 для цепочек валидации

Пример java
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 для ясности контрактов

Пример java
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). Это демонстрирует сочетание стилей для ясности.

Аналоги 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 function comments

En
NotNull Validates that the specified argument is not null