Что такое null в java
Перейти к содержимому

Что такое null в java

  • автор:

Java: Значение null

Особняком в Java стоит значение null . В Java оно не является типом. Это просто конкретное значение со специальным смыслом и логикой работы. Начнем с примера:

// Определение переменной без инициализации значением // С var такое не сработает, так как невозможно вывести тип String a; 

Что находится внутри переменной a ? Если мы ее распечатаем, то увидим null . Значение null используется для ссылочных типов, когда значение не определено.

Как такое возможно? Представьте, что мы хотим извлечь из базы данных пользователя, а его там нет. Что вернет нам запрос в базу? Вот именно для таких ситуаций и нужен null .

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

var user = // тут делаем запрос в базу // Если данных нет, то user станет null // Запись выше равносильна var user = null; 

Из вышесказанного следует важный вывод. Любой ссылочный тип данных может принимать значение null . То есть, null является значением любого ссылочного типа. А вот примитивные типы и null не совместимы. Примитивное значение всегда должно быть определено:

// Error: incompatible types: cannot be converted to int int x = null; 

Задание

Определите переменную с именем greeting , но не инициализируйте ее

Упражнение не проходит проверку — что делать? ��

Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:

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

В моей среде код работает, а здесь нет ��

Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.

Мой код отличается от решения учителя ��

Это нормально ��, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.

В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.

Прочитал урок — ничего не понятно ��

Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.

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

Полезное

Как общаться с null в Java и не страдать

Обложка поста Как общаться с null в Java и не страдать

Java и null неразрывно связаны. Трудно найти Java-программиста, который не сталкивался с NullPointerException . Если даже автор понятия нулевого указателя признал его «ошибкой на миллиард долларов», почему он сохранился в Java? null присутствует в Java уже давно, и я уверен, что разработчики языка знают, что он создает больше проблем, чем решает. Это удивительно, ведь философия Java — делать вещи как можно более простыми. Если разработчики отказались от указателей, перегрузки операторов и множественного наследования, то почему они оставили null ? Я не знаю ответа на этот вопрос. Однако не имеет значения, насколько много критики идет в адрес null в Java, нам придется с этим смириться. Вместо того, чтобы жаловаться, давайте лучше научимся правильно его использовать. Если быть недостаточно внимательным при использовании null , Java заставит вас страдать с помощью ужасного java.lang.NullPointerException . Наиболее частая причина NullPointerException — недостаточное понимание тонкостей использования null . Давайте вспомним самые важные вещи о нем в Java.

Что такое null в Java

Как мы уже выяснили, null очень важен в Java. Изначально он служил, чтобы обозначить отсутствие чего-либо, например, пользователя, ресурса и т. п. Но уже через год выяснилось, что он приносит много проблем. В этой статье мы рассмотрим основные вещи, которые следует знать о нулевом указателе в Java, чтобы свести к минимуму проверки на null и избежать неприятных NullPointerException .

1. В первую очередь, null — это ключевое слово в Java, как public , static или final . Оно регистрозависимо, поэтому вы не сможете написать Null или NULL , компилятор этого не поймет и выдаст ошибку:

Object obj1 = NULL; // Неверно Object obj2 = null; // ОК 

Эта проблема часто возникает у программистов, которые переходят на Java с других языков, но с современными средами разработки это несущественно. Такие IDE, как Eclipse или Netbeans, исправляют эти ошибки, пока вы набираете код. Но во времена Блокнота, Vim или Emacs это было серьезной проблемой, которая отнимала много времени.

2. Так же, как и любой примитивный тип имеет значение по умолчанию (0 у int , false у boolean ), null — значение по умолчанию любого ссылочного типа, а значит, и для любого объекта. Если вы объявляете булеву переменную, ей присваивается значение false . Если вы объявляете ссылочную переменную, ей присваивается значение null , вне зависимости от области видимости и модификаторов доступа. Единственное, компилятор предупредит о попытке использовать неинициализированную локальную переменную. Для того, чтобы убедиться в этом, вы можете создать ссылочную переменную, не инициализируя ее, и вывести ее на экран:

private static Object myObj; public static void main(String args[]) < System.out.println("Значение myObj : " + myObj); >// Значение myObjc: null 

Это справедливо как для статических, так и для нестатических переменных. В данном случае мы объявили myObj как статическую переменную для того, чтобы ее можно было использовать в статическом методе main .

3. Несмотря на распространенное мнение, null не является ни объектом, ни типом. Это просто специальное значение, которое может быть присвоено любому ссылочному типу. Кроме того, вы также можете привести null к любому ссылочному типу:

String str = null; // null можно присвоить переменной типа String, . Integer itr = null; // . и Integer, . Double dbl = null; // . и Double. String myStr = (String) null; // null может быть приведен к String . Integer myItr = (Integer) null; // . и к Integer Double myDbl = (Double) null; // без ошибок. 

Как видите, приведение null к ссылочному типу не вызывает ошибки ни при компиляции, ни при запуске. Также при запуске не будет NullPointerException , несмотря на распространенное заблуждение.

4. null может быть присвоен только переменной ссылочного типа. Примитивным типам — int , double , float или boolean — значение null присвоить нельзя. Компилятор не допустит этого и выдаст ошибку:

int i = null; // type mismatch: cannot convert from null to int short s = null; // type mismatch: cannot convert from null to short byte b = null: // type mismatch: cannot convert from null to byte double d = null; // type mismatch: cannot convert from null to double Integer itr = null; // все в порядке int j = itr; // нет ошибки при компиляции, но NullPointerException при запуске 

Итак, попытка присвоения значения null примитивному типу — ошибка времени компиляции, но вы можете присвоить null типу-обертке, а затем присвоить это значение соответствуему примитиву. Компилятор ругаться не будет, но при выполнении кода будет брошено NullPointerException . Это происходит из-за автоматического заворачивания (autoboxing) в Java

5. Любой объект класса-обертки со значением null кинет NullPointerException при разворачивании (unboxing). Некоторые программисты думают, что обертка автоматически присвоит примитиву значение по умолчанию (0 для int , false для boolean и т. д.), но это не так:

Integer iAmNull = null; int i = iAmNull; // компиляция пройдет успешно 

Если вы запустите этот код, вы увидите Exception in thread «main» java.lang.NullPointerException в консоли. Это часто случается при работе с HashMap с ключами типа Integer . Код ниже сломается, как только вы его запустите:

import java.util.HashMap; import java.util.Map; public class Test < public static void main(String args[]) throws InterruptedException < MapnumberAndCount = new HashMap<>(); int[] numbers = ; for (int i : numbers) < int count = numberAndCount.get(i); // NullPointerException numberAndCount.put(i, count++); >> > 
Exception in thread "main" java.lang.NullPointerException at Test.main(Test.java:14) 

Этот код выглядит простым и понятным. Мы ищем, сколько каждое число встречается в массиве, это классический способ поиска дубликатов в массиве в Java. Мы берем предыдущее значение количества, инкрементируем его и кладем обратно в HashMap . Мы полагаем, что Integer позаботится о том, чтобы вернуть значение по умолчанию для int , однако если числа нет в HashMap , метод get() вернет null , а не 0. И при оборачивании выбросит NullPoinerException . Представьте, что этот код завернут в условие и недостаточно протестирован. Как только вы его запустите на продакшен – УПС!

6. Оператор instanceof вернет false , будучи примененным к переменной со значением null или к литералу null :

Integer iAmNull = null; if (iAmNull instanceof Integer) < System.out.println("iAmNull — экземпляр Integer"); >else
iAmNull не является экземпляром Integer 

Это важное свойство оператора instanceof , которое делает его полезным при приведении типов.

7. Возможно, вы уже знаете, что если вызвать нестатический метод по ссылке со значением null , результатом будет NullPointerException . Но зато вы можете вызвать по ней статический метод класса:

public class Testing < public static void main(String args[])< Testing myObject = null; myObject.iAmStaticMethod(); myObject.iAmNonStaticMethod(); >private static void iAmStaticMethod() < System.out.println("I am static method, can be called by null reference"); >private void iAmNonStaticMethod() < System.out.println("I am NON static method, don't date to call me by null"); >> 

Результат выполнения этого кода:

I am static method, can be called by null reference Exception in thread "main" java.lang.NullPointerException at Testing.main(Testing.java:5) 

8. Вы можете передавать null в любой метод, который принимает ссылочный тип, например, public void print(Object obj) может быть вызван так: print(null) . С точки зрения компилятора ошибки здесь нет, но поведение такого кода целиком зависит от реализации метода. Безопасный метод не кидает NullPointerException в этом случае, а тихо завершает работу. Если бизнес-логика позволяет, лучше писать безопасные методы.

9. Вы можете сравнивать null , используя оператор == («равно») и != («не равно»), но не с арифметическими или логическими операторами (такими как «больше» или «меньше»). В отличие от SQL, в Java null == null вернет true :

public class Test < public static void main(String args[]) throws InterruptedException < String abc = null; String cde = null; if (abc == cde) < System.out.println("null == null is true in Java"); >if (null != null) < System.out.println("null != null is false in Java"); >// classical null check if (abc == null) < // do something >// not ok, compile time error if (abc > null) < // do something >> > 

Вывод этого кода:

null == null is true in Java 

Вот и все, что надо знать о null в Java. При наличии небольшого опыта и с помощью простых приемов вы можете сделать свой код безопасным. Поскольку null может рассматриваться как пустая или неинициализированная переменная, важно документировать поведение метода при получении null . Помните, что любая созданная и не проинициализированная переменная имеет по умолчанию значение null и что вы не можете вызвать метод объекта или обратиться к его полю, используя null .

Перевод статьи «9 Things about Null in Java»

Следите за новыми постами по любимым темам

Подпишитесь на интересующие вас теги, чтобы следить за новыми постами и быть в курсе событий.

Null. Правила использования

В своем выступлении “Null References: The billion dollar mistake” (“Нулевые ссылки: ошибка на миллиард долларов”), Тони Хоар описывает реализацию нулевых ссылок в языках программирования ALGOL, что также по его словам стало ошибкой стоимостью в миллиард долларов. Такие авторитетные книги, как Clean Code: A Handbook of Agile Software Craftsmanship (“Чистый код: настольное руководство по гибкой разработке ПО”) рекомендуют использовать нуль как можно реже. В то же время в книге Bug Patterns in Java (“Шаблоны ошибок в Java”) проблемам, связанным с нулевыми значениями, посвящается аж целых три главы. Тема “What is a null pointer exception and how do I fix it” (“Что такое исключение нулевого значения и как его исправить”), обсуждаемая на Stack Overflow, набрала уже более 3 млн просмотров. Работа с нулевыми значениями и впрямь может вызвать немало сложностей.

Я не отношусь к тем, кто можем говорить лауреатам премии Тьюринга вроде Хоар, как проектировать языки программирования, но при этом все же не считаю null заведомо плохим. В этой и последующих статьях мы рассмотрим, что это вообще такое, когда стоит или не стоит эти значения использовать, а также, как их изящно обрабатывать.

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

Несмотря на то, что в центре нашего внимания будет именно Java, основные принципы и обсуждение должны охватывать объектно-ориентированные языки в целом. Текущая статья в первую очередь предназначена для менее опытных программистов и всех тех, кто испытывает сложности, сталкиваясь с null. Но, думаю, что даже бывалые разработчики смогут найти здесь для себя полезные приемы.

Чем опасен null?

Null — это особое значение, поскольку оно не ассоциируется ни с каким типом (можете смело проверить это инструкцией instanceof в отношении любого другого класса в JRE) и с радостью занимает место любого другого объекта в присвоениях переменных и вызовах методов. Именно в этом и кроются две основных его опасности:

  1. Каждое возвращаемое значение сложного типа может быть null.
  2. Каждое значение параметра со сложным типом может также быть null.

В результате любое возвращаемое значение или объект параметра — это потенциальное исключение нулевого указателя (NPE), возникающее в случае неправильной обработки.

Будет ли в таком случае решением проверять каждое возвращаемое значение и параметр на null? Очевидно, что идея не очень. Во-первых, код будет загроможден проверками на null. Во-вторых, разработчики будут вынуждены тратить драгоценное время на поиск правильного способа обработки нулевых значений, которые никогда не возникнут, а проверки на null будут сбивать с толку других разработчиков.

Может тогда вообще никогда не присваивать значениям null? Тоже неудачное предположение. Если учесть тот факт, что в каждом языке программирования есть пустое значение ( nil, undefined, None, void и т.д.), то наличие общего значения, обозначающего отсутствие чего-либо, чрезвычайно полезно.

Ошибка в условии цикла while может породить бесконечный цикл в любой программе, но это не делает такие циклы плохими по природе. Аналогично будет неверным считать, что null всегда неуместен только из-за того, что его неправильное использование может привести к ошибкам. Null — это наиболее естественное значение для выражения конкретных вещей, но при этом очень неподходящее для выражения других. Компетентные разработчики должны уметь различать эти случаи. В следующем разделе я как раз перейду к объяснению этого.

Когда Null уместен, а когда нет

В этой части я рассмотрю сценарии, в которых null возвращается из методов и передается им, поясню несколько традиционных альтернатив (т.е. предшествующих Java 8) и приведу доводы в пользу уместности встроенного типа вроде null в некоторых случаях.

Возвращение null

Одной из основных идей ООП является моделирование принципов области бизнеса, в которой работает наше ПО. Для этого мы определяем классы, соответствующие данным принципам и их атрибутам. Если речь заходит об использовании нулевых значений, я считаю важным рассмотреть эти типы классов отдельно от остальных.

Сейчас я работаю над проектом электронной записи пациентов Columna, в котором у нас есть классы, представляющие элементы рабочего процесса больницы вроде пациентов, медикаментов, врачей, больничных отделений, госпитализаций и пр. При моделировании любой области возникают случаи, когда нам нужно допустить для определенного элемента отсутствие значения. Предположим, что у нас есть класс, представляющий госпитализацию с атрибутами, которые ее описывают: больничное отделение, куда помещается пациент, причина госпитализации, ее время и т.д. Аналогичным образом у нас может быть класс, который представляет пациента с набором атрибутов вроде имени и номера социального страхования. В любой момент времени пациент может быть госпитализирован или нет. Говоря более формально, у нас есть связь типа “имеет” с мощностью 0..1.

Представим метод, извлекающий из базы данных информацию о госпитализации данного пациента:

public Admission getAdmission(Patient patient);

Что должен возвращать этот метод для не госпитализированного пациента, если не null? Есть ли для выражения этого более точное значение? Спорю, что нет.

Существует и много других сценариев, в которых объект области напрямую ассоциирован с необязательными значениями в качестве атрибутов. Объект, представляющий медикаменты, содержит такие значения, как название препарата, его форму, активность, действующее вещество и т.д. Однако разнообразие медикаментов чрезвычайно обширно, начиная от антибактериальных кремов и заканчивая чаями из каннабиса (оставим или на ромашку заменим?:)), следовательно не все атрибуты будут актуальны для каждого. И снова возвращение null для отсутствующего атрибута выглядит очевидным способом сообщить, что допускается отсутствие значения. Есть ли для этого лучшая альтернатива?

Некоторые выступают за использование так называемого шаблона проектирования Null Object вместо null. Главная его идея в реализации пустого класса с минимумом или вообще без функциональности, т.е. нулевого объекта, который можно использовать вместо класса, содержащего действительную функциональность. Лучше всего данный шаблон показывает себя в сценариях, где нужно рекурсивно обойти структуру бинарного дерева в поиске суммы значений всех узлов. Для этого вы выполняете поиск в глубину и рекурсивно суммируете в каждом узле значения его левого и правого поддерева.

Дерево поиска обычно реализуется так, что каждый узел в нем имеет левого и правого потомка, которые являются либо также узлами, либо концевыми вершинами. Если такое представление дерева использует для концевых узлов null, то вам придется явно выполнять проверки на нулевых потомков, чтобы останавливать рекурсию в концевом узле, предотвращая попытку получения его значения. Вместо этого вам следует определить интерфейс Node с простым методом getValue() и реализовать его в представляющем узел классе, который вычисляет значение, складывая значения getValues() потомков, как показано на рисунке ниже. Реализуйте такой же интерфейс в классе, представляющем узел, и пусть класс концевого узла возвращает при вызове 0. Теперь нам больше не нужно различать код между концевым узлом и обычным. Необходимость явно проверять наличие null отпала вместе с риском получения исключения (NPE).

Для применения этого шаблона к примеру с госпитализацией нам потребуется создать интерфейс Admission . Затем мы определим класс AdmissionImpl для случаев, когда мы можем вернуть данные фактической госпитализации и класс AdmissionNullObjectImpl для случаев, когда не можем. Это позволит методу getAdmission() возвращать либо реальный AdmissionImpl , либо AdmissionNullObjectImpl . Так как вызывающий код использует общий тип Admission, мы можем рассматривать оба объекта одинаково, не рискуя получить исключение и не загромождая код проверками обработки null.

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

Что будет возвращать класс AdmissionNullObject , когда в нем нет данных? Что он должен возвращать вместо объекта локации, когда вызывается getLocation() ? Какой будет подходящая начальная дата для возврата?

Во многих случаях вам придется писать код, который в определенный момент должен будет проверять что-то для обработки ложных значений. Так почему бы просто не использовать проверки на null изначально, избегая определения дополнительных усложняющих код классов? Бывают случаи, когда рассмотренный шаблон работает прекрасно, но на мой взгляд таких случаев в реальных условиях мало, поскольку его можно использовать только для объектов с методами, содержащими пустые значения, или когда вы можете вернуть что-то гармонично вписывающееся в поток окружающего кода.

Еще одна альтернатива — это использовать вместо null пустую string , если атрибутом является простая строка. Это избавляет нас от риска получить NPE, но при этом для правильной обработки пустого значения, скорее всего, потребуется столько же проверок, как и в случае с null. Кроме того, семантика будет отличаться: пустая строка представляет строку с пустым значением, а null не представляет ничего. Это становится актуальным, когда приложению нужно различать, ввел пользователь информацию в виде значения пустой строки или нет. Вы избавляетесь от риска получить NPE ценой применения несколько сбивающего с толку значения.

Теперь давайте рассмотрим применение null в коде, не моделирующем реальные концепции. Большинство классов в базе объектно-ориентированного кода не имеют соответствий в реальной жизни и существуют только в виде абстракции инфраструктуры, обработки и преобразования, а также для группировки связанной функциональности. При этом не до конца понятно, должно ли допускаться их представление нулевым значением. Если мы вызываем геттер для значения с очень абстрактным типом вроде TwoFactorComplexStrategyHandlerDelegateBean , стоит ли нам ожидать, что оно будет null?

Думаю, что нет. В случае класса, представляющего явления из реальной жизни, мы можем сделать обоснованное предположение. Но вышеприведенные типы классов не дают нам для этого никакой возможности, и разобраться в таких случаях можно только, читая код. По этой причине лучше избегать возвращения null вместо других типов, так как у разработчиков редко возникают причины ожидать возвращения null. Если вы этого не ждете, то зачем затрачивать усилия на защиту кода от этих значений?

Такие объекты де факто не должны быть представлены нулевым значением. Не только в нашей базе кода, но также и в JRE, библиотеках и фреймворках. Это можно назвать проблематичным, но вопрос насколько? Важно понимать, что несмотря на необходимость минимизировать использование null, мы не должны добиваться этого ценой переполнения баз кода сложными обходными маневрами с целью избежать появления таких значений. Как я уже отмечал, альтернативы в данном случае не всегда хороши. Однако есть случаи возвращения null, которых избежать легко, хотя встречаются они частенько. Возвращение null для классов в таких ситуациях вызывает подозрение. Например, иногда null возвращается из методов геттеров, где объект не может быть создан по определенным причинам вроде ошибки сервера при его вызове методом. Этот метод обрабатывает ошибку, регистрируя ее в инструкцию catch , и вместо создания экземпляра объекта возвращает null. Это легко исправить. Исключение должно использоваться для указания на неполадку и привлекать для ее обработки вызывающий код. Возвращение null в подобных сценариях будет сбивать с толку, не обеспечит обработку ошибки и может перенести проблему в другую часть кода, где было бы лучше использовать систему fail-fast, немедленно останавливающую работу приложения в случае потенциального сбоя.

Еще одно ошибочное использование null — это представление связи “имеет” с мощностью 0..* (в начале статьи я говорил о связях 0..1). Если вернуться к примеру с объектом пациента, то в нем пациент может иметь одного/нескольких зарегистрированных родственников или не иметь их совсем. Тем не менее я часто вижу, что люди возвращают null, когда для заполнения списка или других типов коллекций нет данных. Аналогичным образом null в качестве аргумента используется в методах для замещения отсутствующих коллекций. Но его применение в данном случае нежелательно по ряду причин. Он сбивает с толку, поскольку коллекцией мощность связи представляется идеально, а присвоив null типу коллекции, вы только добавляете ненужные риски в код. Цикл for , основанный на коллекции, ничего не делает, если эта коллекция пуста. В противном же случае он перебирает каждый элемент, выполняя определенные действия. Если вы позволите коллекции быть null, то выразите, по сути, пустой список, означающий, что в нем обрабатывать нечего. Однако при этом вам придется обеспечить выполнение проверки на null для каждого последующего метода, использующего эту коллекцию, иначе может возникнуть NPE. В случаях, когда нет данных для представления — вызывайте методы с пустыми коллекциями. Это также легко, как вызвать Collections.emptyList() , emptyMap() , emptySet() и т.д. Ненамного больше работы, чем объявить null, зато намного лучше.

Передача нулевых параметров

Из предыдущего раздела следует, что допустимо использовать нулевые аргументы при вызове методов с моделирующими область типами параметров, имеющими необязательные значения. При этом методы должны обеспечивать безопасное их использование. На практике же нулевые параметры применяются для гораздо большего спектра задач. Когда нам нужно предоставить такой параметр методу, мы должны обеспечить, чтобы все последующие обработки этого параметра были защищены от null, а это может оказаться нелегко. И даже несмотря на это, ваша программа может находиться в состоянии, скрывающем небезопасное поведение, что приведет к раскрытию проблемы только при других условиях. Предоставление нулевых параметров также добавляет риск вызвать ошибки при изменении кода в его следующих за их добавлением частях.

Как же полностью избежать нулевых параметров?

Нередко нам нужно использовать какую-либо функциональность в существующем методе, но текущий контекст вызова несколько иной, и мы либо не можем обеспечить все вызываемые методом значения, либо требуется больше информации, чем допускает его структура. Само собой мы не хотим повторять практически идентичный метод. Переиспользование является одним из столпов легко обслуживаемого кода, и одинаковая функциональность не должна реализовываться в нескольких местах, поскольку это не только усложнит поддержание синхронности кода, но и внесет риск появления ошибок. Поэтому мы изменяем существующий код под наши задачи и используем null для параметров, которые предоставляются не всегда. Некоторые методы по своей структуре могут принимать по меньшей мере несколько нулевых параметров, другие же не могут совсем. Тем не менее может оказаться затруднительным определить, какие параметры могут иметь значение null, и подходит ли оно для представления отсутствующего значения.

В таких языках, как Python, сигнатуры методов могут содержать предустановленные значения параметров, используемые при отсутствии значения аргумента в вызове метода. Тем не менее в Java такое невозможно. Ближайшим аналогом этого будет использовать перегрузку метода, когда в классе одна и та же сигнатура метода определяется несколько раз с разными параметрами. Один метод будет содержать всю функциональность и принимать весь набор параметров, а другие будут просто “декораторами” для вызова этого метода, каждый из которых будет получать свой поднабор параметров. Методы-декораторы определяют, какие значения должны использовать вместо отсутствующих параметров, чтобы вызывающему компоненту не пришлось их предоставлять. Жестко прописывая, какие значения должны предоставляться, когда у вызывающего их не хватает, мы уменьшаем риск появления ошибок и делаем принимаемые значения параметров явными.

Аналогичным образом можно разбирать конструкторы, но также можно использовать шаблон строитель. Он помогает минимизировать число параметров конструктора и удаляет необходимость передавать в него нулевые значения, предоставляя для создания класса объект Builder . Смысл данного шаблона в косвенном инстанцировании объекта через промежуточный класс builder . Для предоставления аргументов, которые вы могли бы передать в конструктор напрямую, вы вызываете соответствующий каждому сеттер. Если значение еще не было установлено, builder предоставит его. Затем вы вызываете для builder метод Create() , и он инстанцирует объект за вас. Как и в большинстве шаблонов, в строителе вводятся дополнительные классы и сложность, поэтому прежде, чем его использовать, убедитесь, что в этом есть смысл. Использование его только ради избежания вызова конструктора с парой нулевых значения, скорее всего, будет излишним.

В рассмотренном выше решении нулевые значения по-прежнему передаются в методы внутри объекта, но вызывающий и вызываемые методы спроектированы с учетом этого. Любой метод, вызываемый с одним из этих значений, по умолчанию должен корректно обрабатывать null. При этом нужно запретить сторонним вызывающим объектам передавать нулевые значения для параметров, которые не учтены в структуре, поскольку эти значения могут не поддерживаться, и правильная их обработка не гарантируется.

Воспринимайте null правильно

Все больше языков программирования начинают реализовывать определенные возможности с учетом безопасности. Например, в таких языках, как Clojure, F# и Rust переменные по умолчанию неизменяемы. Компилятор допускает изменение значений только для тех из них, которые объявлены со специальным модификатором. Такой способ использования опасных функций вынуждает программистов переопределять поведение по умолчанию, указывая тем самым, что они осознают степень риска и делают это не без весомых оснований. И к null нам стоит относиться аналогичным образом. Нужно придерживать это значение для особых случаев, где оно будет вполне уместно, ограничив при этом его использование в целом, опять же не ценой усложнения кода креативными обходными решениями. При каждом намерении использовать null вместо перемещающегося между методами значения следует учесть оправданность этого. В таком случае вы должны гарантировать, что в итоге оно не окажется в том месте, где может вызвать проблемы, и другие разработчики будут знать, что значение может быть null. Если же этого обеспечить нельзя, то лучше рассмотреть другие варианты.

  • Фреймворк Executor в Java
  • Java. Вложенные классы
  • Портируем решатель судоку с Java на WebAssembly

null — это экземпляр чего-то?

Нет такого типа, которому бы соответствовал instanceof от null .

RelationalExpression: RelationalExpression instanceof ReferenceType 

В рантайме результат оператора instanceof будет true , если значение RelationalExpression не null и ссылка может быть приведена к ReferenceType без получения исключения ClassCastException . Иначе результат будет false .

Это означает, что для любого типа E и R , для любого E o , где o == null , o instanceof R будет всегда false .

К какому типу принадлежит null ?

Есть также специальный тип — null, тип выражения null , у которого нет имени. И т.к. тип null не имеет имени, невозможно объявить переменную с типом null или привести переменную к типу null. null ссылка — единственное возможное значение выражение типа null . null всегда можно привести к любому ссылочному типу. В действительности, можно игнорировать тип null и притвориться, что null — это просто специальный литерал, который может быть любым ссылочным типом.

Что такое null ?

Как сказано в цитате из JLS выше, можно считать, что » null — это просто специальный литерал, который может быть любым ссылочным типом».

В Java null == null (что верно не для всех языков). Из описания java.lang.Object :

public boolean equals(Object obj)

Для любой не null переменной x , x.equals(null) должен возвращать false .

null также является значением по умолчанию для всех ссылочных типов.

  • Статические переменные класса, переменные экземпляра и массивы инициализируются значением по умолчанию при создании: * Для всех ссылочных типов (§4.3), дефолтное значение — null .

Вы можете использовать это свойства для отложенной инициализации, когда поле будет иметь начальное значение null до тех пор, пока оно фактически не будет использовано, где будет заменено «реальным» значением (вычисление которого может быть дорогостоящим).

Есть и другие применения. Если посмотреть на java.lang.System :

public static Console console()

Returns: The system console, if any, otherwise null.

Это очень распространённая практика: null используется для обозначения несуществующего объекта.

public String readLine() throws IOException

Returns: A String containing the contents of the line, not including any line-termination characters, or null if the end of the stream has been reached.

readLine() будет возвращать instanceof String для каждой строки, пока не получит null , обозначающий конец. Это позволяет обрабатывать каждую строку следующим образом:

String line; while ((line = reader.readLine()) != null)

Примечание: пустая строка — не проблема, т.к. «» != null .

Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.

If this map permits null values, then a return value of null does not necessarily indicate that the map contains no mapping for the key; it’s also possible that the map explicitly maps the key to null . The containsKey operation may be used to distinguish these two cases.

Здесь мы видим, что использование null может усложнить дело. Тут говорится, что, если такого ключа нет, будет возвращён null . Второе утверждение гласит, что даже если элемент по такому ключу есть, всё равно может вернуться null .

К примеру, java.util.Hashtable делает вещи проще путём запрета null в ключах и значениях; так что, если V get(Object key) вернёт null это однозначно говорит о том, что под таким ключом ничего нет.

Операции автоматического анбоксинга на null выбросят java.lang.NullPointerException :

Integer i = null; // при анбоксинге null в integer будет выброшен NullPointerException int a = i; 

Если резюмировать, то null используется как специальное значение для обозначения:

  • Не инициализированного состояния.
  • Терминальное условие
  • Несуществующий объект.
  • Неизвестное значение.

Как null представлен в памяти?

The Java Virtual Machine specification does not mandate a concrete value encoding null .

Небольшое дополнение

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *