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 в C# — однозначно ошибочное решение, бездумно скопированное из более ранних языков.
- Самое страшное: в качестве значения любого ссылочного типа может использоваться универсальный предатель — null, на которого никак не среагирует компилятор. Зато во время исполнения легко получить нож в спину — NullReferenceException. Обрабатывать это исключение бесполезно: оно означает безусловную ошибку в коде.
- Перец на рану: сбой (NRE при попытке разыменования) может находится очень далеко от дефекта (использование null там, где ждут полноценный объект).
- Упитанный пушной зверек: null неизлечим — никакие будущие нововведения в платформе и языке не избавят нас от прокаженного унаследованного кода, который физически невозможно перестать использовать.
Этот ящик Пандоры был открыт еще при создании языка ALGOL W великим Хоаром, который позднее назвал собственную идею ошибкой на миллиард долларов.
Лучшая историческая альтернатива
Разумеется, она была, причем очевидная по современным меркам
- Унифицированный Nullable для значимых и ссылочных типов.
- Разыменование Nullable только через специальные операторы (тернарный — ?:, Элвиса — ?., coalesce — ??), предусматривающие обязательную обработку обоих вариантов (наличие или отсутствие объекта) без выбрасывания исключений.
- Примеры:
object o = new object(); // ссылочный тип - корректная инициализация object o = null; // ссылочный тип - ошибка компиляции, так как null недопустим object? n = new object; // nullable тип - корректная инициализация object? n = null; // nullable тип - корректная инициализация object o = n; // ссылочный тип - ошибка компиляции, типы object и object? несовместимы object o = n ?? new object(); // разыменование с fallback значением (coalesce), дополнительное значение будет вычислено только если n != null Type t = n ? value.GetType() : typeof(object); // специальный тернарный оператор - value означает значение n, если оно не null Type? t = n ? value.GetType(); // бинарная форма оператора ? - возвращает null, если первый операнд null, иначе вычисляет второй операнд и возвращает его, завернутого в nullable
Самое трагичное, что все это не было откровением и даже новинкой уже к моменту проектирования первой версии языка. Увы, тогда матерых функциональщиков в команде Хейлсберга не было.
Лекарства для текущей реальности
Хотя прогноз очень серьезный, летального исхода можно избежать за счет применения различных практик и инструментов. Способы и их особенности пронумерованы для удобства ссылок.
- Явные проверки на null в операторе if. Очень прямолинейный способ с массой серьезных недостатков.
- Гигантская масса шумового кода, единственное назначение которого — выбросить исключение поближе к месту предательства.
- Основной сценарий, загроможденный проверками, читается плохо
- Требуемую проверку легко пропустить или полениться написать
- Проверки можно добавлять отнюдь не везде (например, это нельзя сделать для автосвойств)
- Проверки не бесплатны во время выполнения.
- Позволяет использовать статический анализ
- Поддерживается R#
- Требует добавления изрядного количества скорее вредного, чем бесполезного кода: в львиной доле вариантов использования null недопустим, а значит атрибут придется добавлять буквально везде.
- Позволяет не использовать проверок на null там, где существует эквивалент нуля в виде объекта: пустой IEnumerable, пустой массив, пустая строка, ордер с нулевой суммой и т.п. Самое впечатляющее применение — автоматическая реализация интерфейсов в мок-библиотеках.
- Бесполезен в остальных ситуация: как только вам потребовалось отличать в коде нулевой объект от остальных — вы имеете эквивалент null вместо null object, что является уже двойным предательством: неполноценный объект, который даже NRE не выбрасывает.
- Любой метод или свойство, для которых явно не заявлена возможность возвращать null, должны всегда предоставлять полноценный объект. Для поддержания достаточно выработки хорошей привычки, например, посредством ревью кода.
- Разработчики сторонних библиотек ничего про ваше соглашение не знают
- Нарушения соглашения выявить непросто.
- Поддерживается R#
- Позволяет помечать явно опасные места, вместо массовой разметки по площадям как NotNull
- Неудобен в случае когда null возвращается часто.
- Позволяют элегантно и лаконично организовать проверку и обработку null значений без потери прозрачности основного сценария обработки.
- Практически не упрощают выброс ArgumentException при передаче null в качестве значения NotNull параметра.
- Покрывают лишь некоторую часть вариантов использования.
- Остальные недостатки те же, что и у проверок в лоб.
- Можно полностью исключить NRE
- Можно гарантировать наличие обработки обоих основных вариантов на этапе компиляции.
- Против легаси этот вариант немного помогает, вернее, помогает немного.
- Во время исполнения помимо дополнительных инструкций добавляется еще и memory traffic
- Сочетает элегантность кода с полнотой покрытия вариантов использования.
- В сочетании с типом Optional дает кумулятивный эффект.
- Отладка затруднена, так как с точки зрения отладчика вся цепочка вызовов является одной строкой.
- Легаси по-прежнему остается ахиллесовой пятой.
- В теории почти идеал, на практике все гораздо печальнее.
- Библиотека Code Contracts скорее мертва, чем жива.
- Очень сильное замедление сборки, вплоть до невозможности использовать в цикле редактирование-компиляция-отладка.
- Проверяется все: передача параметров, запись, чтение и возврат значений, даже автосвойства.
- Никакого оверхеда в исходном коде
- Никаких случайных пропусков проверок
- Поддержка атрибута AllowNull — с одной стороны это очень хорошо, а с другой — аналогичный атрибут у решарпера другой.
- С библиотеками, агрессивно использующими null, требуется довольно много ручной работы по добавлению атрибутов AllowNull
- Поддержка отключения проверки для отдельных классов и целых сборок
- Используется вплетение кода после компиляции, но время сборки растет умеренно.
- Сами проверки работают только во время выполнения.
- Гарантируется выброс исключения максимально близко к дефекту (возврату null туда, где ожидается реальный объект).
- Тотальность проверок помогает даже при работе с легаси, позволяя как можно быстрее обнаружить, пометить и обезвредить даже null, полученный из чужого кода.
- Если отсутствие объекта допустимо — NullGuard сможет помочь только при попытках передать его куда не следует.
- Вычистив дефекты в тестовой версии, можно собрать промышленную из тех же исходников с отключенными проверками, получив нулевую стоимость во время выполнения при гарантии сохранения всей прочей логики.
- Проверки во время компиляции.
- Можно полностью ликвидировать NRE в новом коде.
- В реальности не реализовано, надеюсь, что только пока
- Единообразия со значимыми типами не будет.
- Легаси достанет и здесь.
Итоги
Буду краток — все выводы в таблице:
Настоятельная рекомендация Антипаттерн На ваш вкус и потребности 4, 5, 7, 11, 12 (когда и если будет реализовано) 1, 2 3, 6, 8, 9, 10 На предвосхищение ООП через 20 лет не претендую, но дополнениям и критике буду очень рад.
Обновление
добавил примеры кода к утопической альтернативе.
9 вещей о NULL в Java


Java и null неразрывно связаны. Едва ли существует Java-программист, не встречавшийся с «null pointer exception» и это печальный факт. Даже изобретатель концепции «null» назвал ее своей ошибкой на миллиард долларов, тогда зачем Java поддерживает ее? null был здесь долгое время, и я полагаю, создатели Java знают, что он создает больше проблем чем решает, так почему же они все еще мирятся с этим. И это удивляет меня еще больше, потому что философией Java было упростить вещи, вот почему они больше не возятся с указателями, перегрузкой операторов и множественным наследованием, но почему null?Ну, я действительно не знаю ответ на этот вопрос, но, что я точно знаю, не имеет значения сколько бы null критиковался Java-программистами и open-source сообществом, мы должны жить с ним. Вместо того чтобы сожалеть, лучше узнать больше и быть уверенным что мы используем null правильно.
Почему Вы должны узнать о null в Java?
Потому что, если Вы не обратите внимания на null, будьте уверены, Java заставит страдать от ужасного java.lang.NullPointerException и Вы выучите этот урок, но пойдете более трудным путем. Написание устойчивого к «падениям» кода — это искусство и Ваша команда, заказчики и пользователи оценят это. По моему опыту, одна из основных причин NullPointerException это недостаток знаний о null в Java. Многие из Вас уже знакомы с null, остальные смогу узнать некоторые старые и новые вещи о ключевом слове null. Давайте повторим или узнаем некоторые важные вещи о null в Java.
Что есть null в Java
- Перво-наперво, null это ключевое слово в Java, так же как public , static или final . Регистр учитывается, Вы не можете писать null как Null или NULL, компилятор не распознает его и будет выброшена ошибка.
Object obj = NULL; // Not Ok Object obj1 = null //Okprivate static Object myObj; public static void main(String args[]) < System.out.println("What is value of myObjc : " + myObj); >What is value of myObjc : nullString str = null; // null can be assigned to String Integer itr = null; // you can assign null to Integer also Double dbl = null; // null can also be assigned to Double String myStr = (String) null; // null can be type cast to String Integer myItr = (Integer) null; // it can also be type casted to Integer Double myDbl = (Double) null; // yes it's possible, no errorint 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; // this is ok int j = itr; // this is also ok, but NullPointerException at runtimeInteger iAmNull = null; int i = iAmNull; // Remember - No Compilation ErrorНо, когда Вы запустите данный фрагмент кода, в консоли Вы увидите
Exception in thread "main" java.lang.NullPointerExceptionЭто часто происходит при работе с HashMap и Integer key . Выполнение кода, показанного ниже прервется, как только Вы его запустите.
import java.util.HashMap; import java.util.Map; /** * An example of Autoboxing and NullPointerExcpetion * * @author WINDOWS 8 */ public class Test < public static void main(String args[]) throws InterruptedException < Map numberAndCount = new HashMap<>(); int[] numbers = ; for(int i : numbers) < int count = numberAndCount.get(i); numberAndCount.put(i, count++); // NullPointerException here >> >Output: Exception in thread "main" java.lang.NullPointerException at Test.main(Test.java:25)Integer iAmNull = null; if(iAmNull instanceof Integer)< System.out.println("iAmNull is instance of Integer"); >elseOutput : iAmNull is NOT an instance of Integerpublic 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"); >>Output: I am static method, can be called by null reference Exception in thread "main" java.lang.NullPointerException at Testing.main(Testing.java:11)public void print(Object obj)может быть вызван как
print(null)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) < >> >Output: null == null is true in JavaОператор NULL
Оператор NULL — это оператор выражения с отсутствующим выражением . Она полезна, если синтаксис языка требует инструкции, но не оценки выражения. Она состоит из точки с запятой.
Инструкции null часто используются как местозаполнители в инструкциях итерации или как инструкции, в которых нужно разместить метки в конце сложных инструкций или функций.
В следующем фрагменте кода показано, как копировать одну строку в другую. Кроме того, код содержит инструкцию null.
// null_statement.cpp char *myStrCpy( char *Dest, const char *Source ) < char *DestStart = Dest; // Assign value pointed to by Source to // Dest until the end-of-string 0 is // encountered. while( *Dest++ = *Source++ ) ; // Null statement. return DestStart; >int main()