Null-безопасность в Java: когда нули тоже имеют значение
В компании «Свой Банк» мы активно развиваем лучшие практики и стандарты в Backend-разработке. Но, прежде чем выработать хотя бы одну практику, необходимо изучить материалы, разобраться в теме и выработать подходящий вариант. Поэтому в данной статье затронем основные понятия и концепции работы null-безопасности в объектно-ориентированном языке программирования Java.
Что такое Null-безопасность и Nullability
Nullability — это концепция, которая описывает, может ли переменная или выражение содержать значение null. Несмотря на свою кажущуюся простоту, работа с null часто становится причиной сложных ошибок, таких как NullPointerException (NPE). Из-за этого возникла концепция null-безопасности, целью которой является защита кода от ошибок, связанных с null.
Null-безопасность означает, что в коде либо отсутствуют null-значения, либо они обрабатываются явно и безопасно. В Java существуют разные подходы к обеспечению null-безопасности, которые позволяют управлять возможностью появления null в коде и минимизировать риски, связанные с их использованием.
Итого Nullability — это способность объекта быть null.
Null-безопасность — это свойство языка программирования безопасно работать с null.
Null-безопасность в Java
Java изначально не имела встроенных механизмов для безопасной работы с null-значениями, что привело к распространению ошибок, вызванных его использованием.
Для предотвращения NPE требовалась проверка объектов перед их использованием, либо приходилось обрабатывать NullPointerException в try catch конструкции.
Начиная с версии Java 5, были добавлены аннотации, которые позволили добавлять метаданные о намерении использовать null-значения. Они помогают разработчикам и инструментам статического анализа понять, какие переменные могут быть null, а какие нет.
В версии Java 8 был добавлен Null wrapper Optional, который предоставляет способ работы с потенциально null значениями без необходимости явных проверок.
Пример Optional:Optional Однако даже с этими инструментами null-безопасность в Java требует внимания и дисциплины, так как многие старые библиотеки и API активно используют null. В Java null означает отсутствие объекта. Он используется для обозначения неинициализированных объектов, отсутствующих данных, а также для обозначения ситуаций, когда переменной присваивается неизвестное или неопределенное значение. В отличие от других объектов, null не содержит никаких данных и не поддерживает методы. Попытка вызвать метод на null приводит к NullPointerException. Пример использования null: Пример использования null:String name = null Здесь переменная name не ссылается на какой-либо объект. Если попытаться вызвать метод этой переменной, программа выбросит исключение. NullPointerException (NPE) — это одна из самых распространенных ошибок в Java. Она возникает, когда программа пытается использовать объект, который равен null. Это может привести к неожиданным сбоям и сложностям в отладке. Проблемы начинаются, когда код пытается работать с null так же, как с обычным объектом. Пример возникновения NullPointerException: User user = null;String name = user.getName(); // Возникает NullPointerException (NPE) Значение переменной user равно null, и при попытке вызвать метод getName() происходит сбой. В зависимости от написанного кода каждому объекту можно дать характеристику на время его жизни: Например, поле в объекте, которое никогда не инициализируется и не заполняется значением. В «Своем Банк» мы стремимся не создавать такие поля, даже если оно планируется использоваться когда-то в будущем, следуем принципу YAGNI («You aren’t gonna need it»). Например, поле в объекте, которое всегда инициализируется значением. Например, поле в объекте, которое инициализируется как null, но на определенном этапе всегда заполняется не null значением. Например, поле в объекте, которое в любой момент времени заполняется либо null-значением, либо не null-значением. Nullable объекты является самой частой причиной NullPointerException, т.к. не дают явного понимания, когда с объектом работать безопасно. Рассмотрим варианты, когда используются null: 1. Неинициализированное значение Объект объявлен, но не инициализирован. По умолчанию для объектов переменным присваивается null. Пример:Object obj = null; 2. Отсутствующее значение или возвращение null из методов При вызове метода, который возвращает значение, иногда можно получить null, если результат отсутствует, как в случае с методом Map.get(). Пример:Map 3. Незаполненное значение из внешних систем: Пустые поля из внешних систем могут быть представлены как null. Пример:String json = "{"name": "Ivan", "age": null}"; User user = objectMapper.readValue(json, User.class);user.getAge(); // вернет null 4. Неизвестное значение Например, неизвестное или неопределённое значение из перечисления (enum). Итого null — это контракт и намерение. В Java полностью избежать использования null невозможно из-за обратной совместимости и распространенного использования этого значения в стандартных библиотеках и фреймворках. Разработчики в «Своем Банке» стремятся не возвращать null «by design». Проверки на null все же необходимы там, где потенциально может возникнуть ситуация с его использованием. Многие языки имеют null-значения, но главное отличие между ними — это возможность избегать использование null. Мы уже поняли, что в Java невозможно избегать null. Другие языки имеют иные подходы для работы с null, например, в Kotlin встроена возможность избегать использование null, но даже это не исключает ошибок разработке и возникновения NullPointerException при использовании Java библиотек или интеграции с другими системами. Несмотря на то что null является неотъемлемой частью Java, существуют инструменты и библиотеки, которые помогают минимизировать его использование. 1. Non-null по умолчанию — объекты не могут быть null, если это явно не указано. В Java невозможно получить non-null по умолчанию, но можно добиться гарантии non-null: 2. Null wrapper — обертка для объектов, которые могут быть null. Вместо возвращения null можно возвращать объект класса Optional, который помогает избежать прямой работы с null. Это заставляет явно обрабатывать случай отсутствия значения. Пример Null wrapper:Optional 3. Явные проверки Самый простой и распространенный способ избежать NullPointerException — это проверка значения на null перед его использованием. Пример:if (name != null) { int length = name.length();} Null-безопасность — это подход, который помогает сделать работу с null более явной, понятной и безопасной. Чтобы добиться null-безопасности, можно использовать аннотации, Optional и другие подходы, предотвращающие передачу и возврат null-значений. О них мы подробнее поговорим в следующей статье.
Что такое null?
Когда возникает NullPointerException?
Как используется null?
Избавиться от NullPointerException — это не использовать null.
Решения
Заключение