← Назад к вопросам

Что должна возвращать проверка на null

1.0 Junior🔥 281 комментариев
#Stream API и функциональное программирование#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Что должна возвращать проверка на null

Определение

Проверка на null — это валидация переменной перед её использованием. Вопрос о том, что возвращать, зависит от контекста и паттерна обработки.

Основные подходы

1. Boolean возвращение (простейший случай)

// Простая проверка
public boolean isNotNull(Object obj) {
    return obj != null;
}

// Использование
if (isNotNull(user)) {
    processUser(user);
}

Это редко используется, потому что лучше:

if (user != null) {
    processUser(user);
}

2. Throw Exception (fail-fast)

Рекомендуемый подход — выбросить исключение если null:

public void processUser(User user) {
    // Проверяем и выбрасываем исключение
    if (user == null) {
        throw new IllegalArgumentException("User cannot be null");
    }
    // Дальше используем user без проверок
    sendEmail(user.getEmail());
    updateBalance(user.getId());
}

Java утилиты для этого:

// Objects.requireNonNull (JDK 7+)
public void processUser(User user) {
    Objects.requireNonNull(user, "User cannot be null");
    // user гарантировано не null
}

// Spring Assert (если есть Spring)
public void processUser(User user) {
    Assert.notNull(user, "User must not be null");
}

// Google Preconditions (Guava)
public void processUser(User user) {
    checkNotNull(user, "User cannot be null");
}

3. Optional возвращение (современный подход)

// Метод возвращает Optional
public Optional<User> findUserById(Long id) {
    User user = repository.findById(id).orElse(null);
    return Optional.ofNullable(user);
}

// Использование
findUserById(123L)
    .ifPresentOrElse(
        user -> processUser(user),
        () -> handleUserNotFound()
    );

// Или с map
findUserById(123L)
    .map(User::getEmail)
    .ifPresent(email -> sendEmail(email));

4. Default value (fallback)

public String getUsername(User user) {
    // Если null, возвращаем дефолтное значение
    return user != null ? user.getName() : "Anonymous";
}

// Или с Optional
public String getUsername(User user) {
    return Optional.ofNullable(user)
        .map(User::getName)
        .orElse("Anonymous");
}

// Или с Objects.toString
public String getUsername(User user) {
    return Objects.toString(user, "Anonymous");
}

Практические примеры по контексту

Пример 1: Validation в конструкторе

@Service
public class UserService {
    private final UserRepository repository;
    
    // Выбросить исключение если null
    public UserService(UserRepository repository) {
        this.repository = Objects.requireNonNull(
            repository, 
            "Repository cannot be null"
        );
    }
}

Пример 2: API endpoint

@RestController
@RequestMapping("/api/users")
public class UserController {
    private final UserService userService;
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        // Возвращаем Optional обёрнутый в ResponseEntity
        return userService.findUserById(id)
            .map(user -> ResponseEntity.ok(mapToDTO(user)))
            .orElse(ResponseEntity.notFound().build());
    }
}

Пример 3: Stream обработка

public List<String> getEmails(List<User> users) {
    return users.stream()
        .filter(Objects::nonNull)  // Выфильтровать null'ы
        .map(User::getEmail)
        .filter(Objects::nonNull)  // Выфильтровать null emails
        .collect(Collectors.toList());
}

Пример 4: Getter с null-safety

public class User {
    private String email;
    
    // Плохо — может вернуть null
    public String getEmail() {
        return email;
    }
    
    // Хорошо — безопаснее
    public Optional<String> getEmailOptional() {
        return Optional.ofNullable(email);
    }
    
    // Или
    public String getEmailOrDefault(String defaultValue) {
        return email != null ? email : defaultValue;
    }
}

Сравнение подходов

Подход 1: Throw Exception

public User getUser(Long id) {
    User user = repository.findById(id);
    if (user == null) {
        throw new UserNotFoundException("User not found");
    }
    return user;
}

// Использование
User user = getUser(123L);  // Либо получаешь user, либо исключение
processUser(user);  // user гарантировано не null

Плюсы:

  • Простая логика — нет null'ов в коде
  • Fail-fast — сразу видим проблему
  • Нет ненужных проверок дальше

Минусы:

  • Нужно обрабатывать исключение
  • Менее удобно для optional данных

Подход 2: Optional

public Optional<User> getUser(Long id) {
    return repository.findById(id);  // Уже Optional!
}

// Использование
getUser(123L)
    .ifPresentOrElse(
        this::processUser,
        this::handleNotFound
    );

Плюсы:

  • Явно показывает что результат может быть пустой
  • Удобно для functional style
  • Цепирование операций

Минусы:

  • Может замораживать производительность на горячих путях
  • Не для всех случаев подходит

Подход 3: Default Value

public String getUsername(User user) {
    return user != null ? user.getName() : "Unknown";
}

Плюсы:

  • Простой fallback
  • Дефолт всегда доступен

Минусы:

  • Может скрывать ошибки
  • Неясно было ли значение null или дефолт

Best Practice рекомендации

1. Для обязательных данных (не должны быть null)

public void saveUser(User user) {
    // ДОЛЖНА быть проверка и выброс исключения
    Objects.requireNonNull(user, "User cannot be null");
    
    // user гарантировано не null дальше
    repository.save(user);
    eventPublisher.publishUserCreated(user);
}

2. Для опциональных данных (могут быть null)

public void updateUserEmail(User user, String newEmail) {
    // Может быть null — используй Optional
    Optional.ofNullable(newEmail)
        .ifPresent(email -> {
            user.setEmail(email);
            repository.save(user);
        });
}

3. Для поиска (может не найтись)

public Optional<User> findByEmail(String email) {
    return repository.findByEmail(email);  // Уже Optional
}

// Клиент:
findByEmail("john@example.com")
    .map(this::mapToDTO)
    .ifPresentOrElse(
        dto -> response.setBody(dto),
        () -> response.setStatus(404)
    );

4. Для внутренних проверок

private void internalMethod(User user) {
    // Если это приватный метод, можем полагаться на валидацию выше
    // Но все равно лучше проверить:
    assert user != null : "User should not be null";
    // или
    if (user == null) {
        throw new IllegalStateException("Internal error: user is null");
    }
}

Java 14+: Records и null-safety

// Record автоматически проверяет все параметры
public record User(
    Long id,
    String name,
    String email
) {}

// Компактный конструктор с проверкой
public record User(
    Long id,
    String name,
    String email
) {
    public User {
        Objects.requireNonNull(id, "ID cannot be null");
        Objects.requireNonNull(name, "Name cannot be null");
        // email может быть null
    }
}

Итоговый ответ

Что должна возвращать проверка на null:

  1. Для обязательных параметров: выбросить исключение (IllegalArgumentException, NullPointerException)
  2. Для опциональных данных: вернуть Optional
  3. Для fallback логики: вернуть default value
  4. Для валидации в начале метода: использовать Objects.requireNonNull()

Международное правило: fail-fast — сразу выбросить исключение если что-то не так, чем позволить null распространяться дальше по коду.