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 и не страдать
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 — это экземпляр чего-то?
Нет такого типа, которому бы соответствовал 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.
Обработка отсутствующих значений в Java: исключения или null?
Существует типичная ситуация, когда метод в Java должен возвращать объект, но при определенных обстоятельствах этот объект может быть не найден. Например, можно представить метод, который ищет пользователя в базе данных по его идентификатору. Если пользователь с таким идентификатором существует, то метод возвращает его. Но что делать, если такого пользователя нет?
Существуют различные подходы к обработке таких ситуаций. Рассмотрим два основных из них.
Возвращение null
Первый подход заключается в том, чтобы возвращать null , если объект не найден. Это простой и понятный подход. Однако, он может привести к ошибкам. Если разработчик, использующий этот метод, забудет проверить возвращаемое значение на null , это может привести к NullPointerException .
User user = findUserById(id); System.out.println(user.getName()); // Если findUserById вернул null, здесь будет исключение
Бросание исключения
Второй подход заключается в бросании исключения, когда объект не найден. Это может быть любое исключение, но часто используется NoSuchElementException . Этот подход позволяет явно указать, что объект не найден, и заставляет разработчиков обрабатывать эту ситуацию.
User user; try < user = findUserById(id); >catch (NoSuchElementException e) < System.out.println("User not found"); return; >System.out.println(user.getName()); // Если дошли до этой строки, значит, пользователь найден
Какой подход выбрать?
Выбор между этими двуми подходами зависит от конкретной ситуации. Если отсутствие объекта — это нормальная ситуация, то можно возвращать null . Например, если метод ищет пользователя по имени, и такого пользователя может и не быть.
Если же отсутствие объекта — это неожиданная или некорректная ситуация, то лучше бросать исключение. Например, если метод ищет пользователя по идентификатору, и такой пользователь обязательно должен существовать.
В любом случае, важно явно указать в документации к методу, что он может возвращать null или бросать исключение, и в каких случаях это происходит. Это поможет другим разработчикам правильно использовать этот метод и избежать ошибок.
