Избавляемся от NullPointerException в Java: чистый и масштабируемый подход

Проблема NullPointerException знакома каждому Java-разработчику. Первое, что приходит на ум – добавить != null. Это кажется быстрым решением, но в долгосрочной перспективе приводит к:

  • захламленному коду
  • ошибкам в работе (упущены проверки и т.д.)
  • ухудшению читаемости
  • трудностям при сопровождении

Так можно ли решить эту задачу элегантнее?

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

Шаг 1. Часто встречаемые типы данных

Перед тем как сразу писать != null, определите тип значения. Чаще всего это один из следующих:

  • String
  • List
  • произвольный объект (Object)
  • массив
  • Map

Шаг 2. Используем проверенные утилиты
Java и Spring предлагают готовые утилитные классы, избавляющие от ручных проверок на null:

ТипУтилитаМетод
StringStringUtilsisEmpty()
ОбъектыObjectUtilsisEmpty()
КоллекцииCollectionUtilsisEmpty()
МассивыObjectUtilsisEmpty()

Шаг 3. Заменяем ручные проверки утилитами

Проверка строк:

String name = "";
boolean isBlank = StringUtils.isEmpty(name); // true, если null или ""

Под капотом:

return str == null || "".equals(str);

Объекты:

Object user = null;
boolean isEmpty = ObjectUtils.isEmpty(user); // true

Коллекции и Map:

Map<String, Object> map = new HashMap<>();
boolean emptyMap = CollectionUtils.isEmpty(map); // true
List<String> list = null;
boolean emptyList = CollectionUtils.isEmpty(list); // true

Массивы:

Integer[] numbers = null;
boolean emptyArray = ObjectUtils.isEmpty(numbers); // true

Почему ObjectUtils.isEmpty() так универсален?
Его реализация охватывает множество типов:

Массивы:

public static boolean isEmpty(Object obj) {
if (obj == null) return true;
if (obj instanceof Optional) return !((Optional) obj).isPresent();
if (obj instanceof CharSequence) return ((CharSequence) obj).length() == 0;
if (obj.getClass().isArray()) return Array.getLength(obj) == 0;
if (obj instanceof Collection) return ((Collection) obj).isEmpty();
if (obj instanceof Map) return ((Map) obj).isEmpty();
return false;
}

Важный нюанс: ложные “непустые” значения

List list = Collections.singletonList(null);
ObjectUtils.isEmpty(list); // false
Object[] arr = new Object[] {null};
ObjectUtils.isEmpty(arr); // false

Хотя внутри – null, объект сам по себе не пустой. Это нужно учитывать.
Как проверить, что все элементы – null?
Для списка:

boolean allNulls = list.stream().allMatch(Objects::isNull);

Для массива:

boolean allNulls = Arrays.stream(array).allMatch(Objects::isNull);

Итоги

  1. Определите тип данных, с которым работаете
  2. Используйте подходящую утилиту
  3. Для продвинутых сценариев – например, когда нужно убедиться, что все элементы null – используйте stream-подходы.
  4. Избавьтесь от ручных проверок
Что проверяетеКлассМетод
StringStringUtilsisEmpty()
ОбъектObjectUtilsisEmpty()
КоллекцииCollectionUtilsisEmpty()
МассивObjectUtilsisEmpty()
Таб. 2 Быстрая таблица соответствий