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 в других языках и отличия

Краткие примеры для популярных языков и ключевые отличия от 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. Ленивое сообщение и дорогостоящее вычисление только при ошибке

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

En
NotNull Validates that the specified argument is not null