Java for Beginner
870 subscribers
1.01K photos
275 videos
14 files
1.69K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Раздел 9. Исключения, логирование, отладка

Глава 1. Иерархия исключений (Exceptions)

Objects.requireNonNull() – стандартная фабрика NullPointerException

Null-ссылка, введенная Тони Хоаром в 1965 году, впоследствии названная им "миллиардной ошибкой" (billion-dollar mistake), остается одним из наиболее частых источников runtime-ошибок в Java. NullPointerException занимает второе место по частоте среди всех дефектов программного обеспечения, что подчеркивает масштаб проблемы.

В Java nullability является неявной: если API не документировано явно, разработчик не может быть уверен, может ли возвращаемое значение быть null, что приводит к недопониманию и багам.

До Java 7 проверка параметров на null выполнялась через явные условия:
public void processOrder(Order order) {
if (order == null) {
throw new NullPointerException("Order cannot be null");
}
// Основная логика
}


Этот подход многословен и не предоставляет стандартизированного способа валидации. Java 7 ввела класс java.util.Objects с методом requireNonNull(), который превратил проверку null в однострочную операцию с гибкими возможностями кастомизации сообщений.


Методы requireNonNull: три перегрузки

Класс Objects предоставляет три перегруженные версии requireNonNull, каждая из которых подходит для разных сценариев:
// 1. Базовая версия без сообщения
public static <T> T requireNonNull(T obj)

// 2. Версия со статическим сообщением
public static <T> T requireNonNull(T obj, String message)

// 3. Версия с ленивым сообщением через Supplier
public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier)


Все три версии возвращают переданный объект, если он не null, что позволяет использовать их inline при инициализации полей или передаче параметров. Если объект null, выбрасывается NullPointerException с соответствующим сообщением.


Базовая версия: fail-fast без лишних слов
public class OrderService {
private final OrderRepository repository;

public OrderService(OrderRepository repository) {
this.repository = Objects.requireNonNull(repository);
}
}


Здесь requireNonNull(repository) возвращает repository, если он не null, или выбрасывает NullPointerException с дефолтным сообщением. Этот паттерн идеален для конструкторов, где требуется гарантия ненулевых зависимостей, а специфичное сообщение не критично.


Версия со статическим сообщением: контекст для отладки
public void updateInventory(Inventory inventory, String warehouseId) {
Objects.requireNonNull(inventory, "Inventory object cannot be null");
Objects.requireNonNull(warehouseId, "Warehouse ID must be specified");

inventory.adjustStock(warehouseId);
}


Статическое сообщение предоставляет контекст при возникновении исключения, облегчая отладку. Однако сообщение вычисляется всегда, даже если объект не null, что незначительно, но избыточно для горячих путей выполнения.


Версия с Supplier: ленивое вычисление сообщений

Третья перегрузка принимает Supplier<String> и вычисляет сообщение только при фактической необходимости — когда объект равен null:
public void processTransaction(Transaction tx) {
Objects.requireNonNull(tx,
() -> String.format("Transaction cannot be null at %s", Instant.now()));

// Сложное форматирование выполняется только если tx == null
}


Эта версия критически важна для сценариев, где формирование сообщения требует значительных вычислений: конкатенация строк, форматирование дат, обращение к внешним ресурсам. При нормальном выполнении Supplier не вызывается, что устраняет накладные расходы.

Сравнение производительности:
// Избыточно: сообщение формируется всегда
Objects.requireNonNull(user, "User " + userId + " not found in database " + dbName);

// Эффективно: сообщение формируется только при null
Objects.requireNonNull(user,
() -> "User " + userId + " not found in database " + dbName);


В первом случае конкатенация строк выполняется при каждом вызове метода. Во втором — только при нарушении предусловия.


#Java #для_новичков #beginner #exception #requireNonNull
👍3
Когда использовать requireNonNull

Валидация параметров методов и конструкторов

Основное применение requireNonNull — проверка предусловий (preconditions) на границе метода. Это реализация принципа fail-fast: ошибка обнаруживается немедленно, предотвращая каскадные сбои и упрощая отладку. Если метод валидирует параметры upfront, он быстро завершается с четким исключением, указывающим источник проблемы.
public class PaymentProcessor {
private final PaymentGateway gateway;
private final TransactionLogger logger;

public PaymentProcessor(PaymentGateway gateway, TransactionLogger logger) {
this.gateway = Objects.requireNonNull(gateway, "PaymentGateway is required");
this.logger = Objects.requireNonNull(logger, "TransactionLogger is required");
}

public PaymentResult process(PaymentRequest request) {
Objects.requireNonNull(request, "PaymentRequest cannot be null");

gateway.authorize(request);
logger.log(request);
return new PaymentResult();
}
}


Гарантия ненулевых возвращаемых значений


requireNonNull может использоваться для защиты от непреднамеренного возврата null из методов, особенно при делегировании к внутренним компонентам:
public Customer getCustomer(String customerId) {
Customer customer = customerRepository.findById(customerId);
return Objects.requireNonNull(customer,
() -> "Customer not found for ID: " + customerId);
}


Здесь requireNonNull служит последней линией защиты: если репозиторий вернул null (что не должно происходить по контракту, но возможно из-за бага), метод выбросит NullPointerException с информативным сообщением вместо передачи null вызывающему коду.

Защита внутреннего состояния при делегировании

При реализации методов, делегирующих вызовы внутренним объектам, requireNonNull гарантирует, что поле инициализировано:
public class CachedDataProvider {
private DataProvider delegate;

public void initialize(DataProvider provider) {
this.delegate = Objects.requireNonNull(provider);
}

public Data fetchData() {
return Objects.requireNonNull(delegate, "Provider not initialized").fetch();
}
}


Вызов fetchData() до initialize() выбросит NullPointerException с понятным сообщением, вместо стандартного NPE на разыменовании null.


Документирование через Javadoc

Использование requireNonNull должно сопровождаться документированием в Javadoc. Тег @throws указывает, что метод выбрасывает NullPointerException при нарушении параметрических ограничений:
/**
* Обрабатывает платеж через указанный шлюз.
*
* @param request запрос на платеж, не может быть null
* @param gateway платежный шлюз, не может быть null
* @return результат обработки платежа
* @throws NullPointerException если request или gateway равны null
*/
public PaymentResult process(PaymentRequest request, PaymentGateway gateway) {
Objects.requireNonNull(request, "PaymentRequest is required");
Objects.requireNonNull(gateway, "PaymentGateway is required");

return gateway.process(request);
}


Для классов, где множество методов выбрасывают NullPointerException при нарушении предусловий, можно документировать это на уровне класса, избегая повторений в каждом методе.


Связь с аннотациями @NonNull

Разделение ответственности: runtime vs compile-time

Objects.requireNonNull() обеспечивает runtime-проверку: исключение возникает во время выполнения, если null передан в метод. Аннотации @NonNull (и их аналоги) предоставляют compile-time информацию о nullability, позволяя IDE и статическим анализаторам предупреждать о потенциальных проблемах до запуска программы.

Эти механизмы комплементарны, а не взаимоисключающие. Аннотации @NonNull документируют контракт и помогают инструментам, но не обеспечивают runtime-защиту. requireNonNull гарантирует защиту во время выполнения, но не предоставляет информации для статического анализа. Идеальный подход — комбинировать оба механизма.

#Java #для_новичков #beginner #exception #requireNonNull
👍3
Экосистема аннотаций nullability

В Java-экосистеме существует множество аннотаций
@NonNull из разных источников, каждая со своей семантикой и областью применения:
JSpecify (org.jspecify.annotations.NonNull) — современный стандарт, разработанный консорциумом Google, JetBrains, Spring и других. Применяется к использованию типа (type use), что позволяет различать nullability элементов массивов и generic-типов.
Spring Framework (org.springframework.lang.NonNull) — устаревшие аннотации из Spring 5, deprecated в Spring 7 в пользу JSpecify. Применялись к полям, параметрам и возвращаемым значениям.
JetBrains (org.jetbrains.annotations.NotNull) — аннотации IntelliJ IDEA, широко поддерживаемые IDE и Kotlin-компилятором.
JSR-305 (javax.annotation.Nonnull) — спецификация, больше не поддерживаемая активно, но широко распространенная в legacy-коде.
Jakarta Bean Validation (jakarta.validation.constraints.NotNull) — используется для runtime-валидации в фреймворках вроде Hibernate Validator.

JSpecify: современный стандарт

JSpecify, выпущенный в версии 1.0.0, представляет собой наиболее перспективный стандарт для null safety в Java. Он определяет три состояния nullability: unspecified (не указано), nullable (@Nullable) и non-null (@NonNull). Ключевая особенность — аннотация @NullMarked, применяемая на уровне пакета, которая устанавливает non-null как значение по умолчанию для всех типов в пакете, устраняя необходимость в явном @NonNull для каждого параметра.
// package-info.java
@NullMarked
package com.example.service;

import org.jspecify.annotations.NullMarked;


После этого все параметры, возвращаемые значения и поля в пакете считаются non-null по умолчанию. Только явно аннотированные @Nullable типы могут содержать null.
package com.example.service;

import org.jspecify.annotations.Nullable;

public class UserService {
// Не требует @NonNull — non-null по умолчанию благодаря @NullMarked
public User findById(String id) {
// ...
}

// Явно указано, что может вернуть null
public @Nullable User findByEmail(String email) {
// ...
}

// Параметр явно nullable
public void updateNickname(String id, @Nullable String nickname) {
// ...
}
}



Интеграция requireNonNull с аннотациями

Комбинированный подход использует @NonNull (или неявный non-null через @NullMarked) для документирования контракта и статического анализа, и requireNonNull для runtime-защиты:
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

@NullMarked
public class OrderService {
private final PaymentGateway gateway;

public OrderService(PaymentGateway gateway) {
// Runtime-защита: выбросит NPE с сообщением
this.gateway = Objects.requireNonNull(gateway, "PaymentGateway is required");
}

// Метод принимает non-null по умолчанию (благодаря @NullMarked)
public Order processOrder(String orderId) {
Objects.requireNonNull(orderId, "Order ID is required");
return gateway.process(orderId);
}

// Явно nullable параметр
public Order processOrderWithNotes(String orderId, @Nullable String notes) {
Objects.requireNonNull(orderId, "Order ID is required");
// notes может быть null — допустимо по контракту
return gateway.process(orderId, notes);
}
}


В этом примере:

IDE и статические анализаторы (NullAway, Checker Framework) предупреждают о попытке передать null в non-null параметры на этапе разработки
requireNonNull гарантирует защиту во время выполнения, если статический анализ был проигнорирован или null пришел из неаннотированного кода
Кастомные сообщения в requireNonNull обеспечивают контекст при runtime-ошибках


#Java #для_новичков #beginner #exception #requireNonNull
👍3