Введение
В этом практическом занятии (лабораторной работе) вы научитесь эффективно обрабатывать нулевые значения (null values) при работе с коллекциями в Java. Коллекции являются фундаментальными структурами данных, и крайне важно писать надежный код, который может безопасно управлять ситуациями, когда переменная коллекции может быть равна null. Вы узнаете, как проверять коллекции на null, комбинировать проверки на null и пустоту для комплексной валидации, а также использовать класс Optional для повышения безопасности при работе с null. Это поможет вам избежать распространенных ошибок NullPointerException.
Проверка коллекции на null
На этом этапе мы рассмотрим, как обрабатывать нулевые значения (null values) при работе с коллекциями в Java. Коллекции, такие как List или Set, являются фундаментальными структурами данных, и крайне важно писать код, который может безопасно обрабатывать ситуации, когда переменная коллекции может быть равна null.
В Java значение null означает, что переменная не ссылается на какой-либо объект. Если вы попытаетесь обратиться к методам или свойствам объекта, равного null, ваша программа завершится аварийно с ошибкой NullPointerException. Это очень распространенная ошибка в Java, и изучение способов ее избежания является обязательным.
Начнем с создания простой Java-программы, которая демонстрирует проблему.
Откройте файл
HelloJava.javaв редакторе WebIDE, если он еще не открыт.Замените все содержимое файла следующим кодом:
import java.util.List; public class HelloJava { public static void main(String[] args) { List<String> names = null; // Intentionally set to null // This line will cause a NullPointerException if names is null // int size = names.size(); // System.out.println("Size of the list: " + size); System.out.println("Program finished."); } }В этом коде мы объявляем список строк
namesи явно устанавливаем его равнымnull. Закомментированные строки показывают, что произойдет, если мы попытаемся вызвать методsize()для списка, равногоnull– это вызовет ошибкуNullPointerException.Сохраните файл (Ctrl+S или Cmd+S).
Теперь скомпилируем программу. Откройте терминал внизу WebIDE и убедитесь, что вы находитесь в директории
~/project. Выполните следующую команду:javac HelloJava.javaЕсли компиляция прошла успешно, вы не должны увидеть никакого вывода.
Теперь запустите программу:
java HelloJavaВы должны увидеть следующий вывод:
Program finished.Поскольку строка, которая вызывала бы ошибку
NullPointerException, закомментирована, программа запускается без аварийного завершения.
Теперь изменим код, чтобы проверить на null перед использованием коллекции.
Откройте файл
HelloJava.javaснова в редакторе.Измените метод
main, добавив проверку наnull:import java.util.List; public class HelloJava { public static void main(String[] args) { List<String> names = null; // Intentionally set to null if (names != null) { // This code will only run if names is NOT null int size = names.size(); System.out.println("Size of the list: " + size); } else { System.out.println("The list is null."); } System.out.println("Program finished."); } }Мы добавили оператор
if, который проверяет, не равен лиnamesзначениюnull(names != null). Код для получения размера списка и его вывода теперь находится внутри этого блокаif, что означает, что он будет выполняться только в том случае, еслиnamesявляется действительным объектом списка. Блокelseобрабатывает случай, когдаnamesравенnull.Сохраните файл.
Скомпилируйте модифицированную программу:
javac HelloJava.javaЗапустите программу снова:
java HelloJavaНа этот раз вы должны увидеть следующий вывод:
The list is null. Program finished.Программа правильно определила, что список был равен
null, и вывела соответствующее сообщение, избежав ошибкиNullPointerException.
Эта простая проверка if (collection != null) является самым базовым способом предотвращения ошибки NullPointerException при работе с коллекциями. Это фундаментальная техника, которую вы будете часто использовать в Java-программировании.
Комбинирование проверок на null и пустоту
На предыдущем этапе мы узнали, как проверить, является ли коллекция равной null. Однако коллекция может быть пустой (не содержать элементов), даже если она не равна null. Во многих случаях вы, возможно, захотите обрабатывать null-коллекцию и пустую коллекцию одинаково или, по крайней мере, предусмотреть оба варианта.
Проверка коллекции на пустоту выполняется с помощью метода isEmpty(). Этот метод возвращает true, если коллекция не содержит элементов, и false в противном случае.
Давайте изменим нашу программу, чтобы продемонстрировать разницу между null-списком и пустым списком, а затем объединим проверки.
Откройте файл
HelloJava.javaв редакторе WebIDE.Замените содержимое файла следующим кодом:
import java.util.List; import java.util.ArrayList; // Import ArrayList public class HelloJava { public static void main(String[] args) { List<String> nullList = null; // Intentionally set to null List<String> emptyList = new ArrayList<>(); // An empty list System.out.println("Checking nullList:"); if (nullList != null) { System.out.println("nullList is not null."); if (nullList.isEmpty()) { System.out.println("nullList is empty."); } else { System.out.println("nullList is not empty."); } } else { System.out.println("nullList is null."); } System.out.println("\nChecking emptyList:"); if (emptyList != null) { System.out.println("emptyList is not null."); if (emptyList.isEmpty()) { System.out.println("emptyList is empty."); } else { System.out.println("emptyList is not empty."); } } else { System.out.println("emptyList is null."); } System.out.println("\nProgram finished."); } }Мы добавили
emptyList, который инициализируется как новый пустойArrayList. Затем мы выполняем одинаковые проверки наnullи пустоту дляnullListиemptyList, чтобы увидеть разницу в выводе.Сохраните файл.
Скомпилируйте программу в терминале:
javac HelloJava.javaЗапустите программу:
java HelloJavaВы должны увидеть вывод, похожий на следующий:
Checking nullList: nullList is null. Checking emptyList: emptyList is not null. emptyList is empty. Program finished.Этот вывод четко показывает, что
nullListравенnull, в то время какemptyListне равенnull, но пуст.
Теперь объединим проверки на null и пустоту в одно условие. Распространенный шаблон - проверить, является ли коллекция либо null, либо пустой.
Откройте файл
HelloJava.javaв редакторе.Измените метод
main, чтобы объединить проверки:import java.util.List; import java.util.ArrayList; public class HelloJava { public static void main(String[] args) { List<String> names = null; // Can be null or an empty list // Combined check: is names null OR is names empty? if (names == null || names.isEmpty()) { System.out.println("The list is null or empty."); } else { System.out.println("The list is not null and not empty."); // You can safely iterate or access elements here // For example: // System.out.println("First element: " + names.get(0)); } // Let's test with an empty list List<String> anotherList = new ArrayList<>(); System.out.println("\nChecking anotherList (empty):"); if (anotherList == null || anotherList.isEmpty()) { System.out.println("anotherList is null or empty."); } else { System.out.println("anotherList is not null and not empty."); } // Let's test with a non-empty list List<String> populatedList = new ArrayList<>(); populatedList.add("Item 1"); System.out.println("\nChecking populatedList (not empty):"); if (populatedList == null || populatedList.isEmpty()) { System.out.println("populatedList is null or empty."); } else { System.out.println("populatedList is not null and not empty."); } System.out.println("\nProgram finished."); } }Мы используем логический оператор ИЛИ (
||), чтобы объединить условияnames == nullиnames.isEmpty(). Блокifбудет выполняться, если любое из условий истинно. Мы также добавили тесты с пустым списком и заполненным списком, чтобы увидеть, как работает объединенная проверка.Важное примечание: Порядок условий в
names == null || names.isEmpty()имеет решающее значение. Еслиnamesравенnull, первая часть условия (names == null) истинна. Из-за короткого замыкания в Java вторая часть (names.isEmpty()) не вычисляется, что предотвращает ошибкуNullPointerException. Если бы вы написалиnames.isEmpty() || names == null, иnamesбыл равенnull, вызовnames.isEmpty()привел бы к ошибкеNullPointerException. Всегда проверяйте наnullсначала, когда комбинируете с другими проверками объекта.Сохраните файл.
Скомпилируйте программу:
javac HelloJava.javaЗапустите программу:
java HelloJavaВы должны увидеть вывод, похожий на следующий:
The list is null or empty. Checking anotherList (empty): anotherList is null or empty. Checking populatedList (not empty): populatedList is not null and not empty. Program finished.Это демонстрирует, как объединенная проверка правильно определяет
nullи пустые списки и отличает их от непустых списков. Эта объединенная проверка является очень распространенным и безопасным способом обработки коллекций, которые могут бытьnullили пустыми.
Использование Optional для безопасности при работе с null
На этом этапе мы рассмотрим более современный подход к обработке потенциальных значений null в Java с использованием класса Optional, введенного в Java 8. Optional представляет собой контейнерный объект, который может содержать или не содержать не-null значение. Он предоставляет способ более явно представлять наличие или отсутствие значения, что может помочь уменьшить риск возникновения ошибок NullPointerException.
Хотя проверки if (collection != null) вполне допустимы и необходимы во многих ситуациях, использование Optional может сделать ваш код более читаемым и выразительным, особенно при работе с методами, которые могут вернуть значение или null.
Давайте посмотрим, как можно использовать Optional с коллекцией. Хотя Optional обычно используется для одиночных значений, вы можете столкнуться с ситуациями, когда метод возвращает Optional<List<SomeObject>>.
Откройте файл
HelloJava.javaв редакторе WebIDE.Замените содержимое файла следующим кодом, который демонстрирует использование
Optionalс потенциальноnullсписком:import java.util.List; import java.util.ArrayList; import java.util.Optional; // Import Optional public class HelloJava { // A method that might return an Optional containing a list, or an empty Optional public static Optional<List<String>> getNames(boolean includeNames) { if (includeNames) { List<String> names = new ArrayList<>(); names.add("Alice"); names.add("Bob"); return Optional.of(names); // Return an Optional containing the list } else { return Optional.empty(); // Return an empty Optional } } public static void main(String[] args) { // Case 1: Get names when includeNames is true Optional<List<String>> namesOptional1 = getNames(true); System.out.println("Checking namesOptional1:"); // Check if the Optional contains a value if (namesOptional1.isPresent()) { List<String> names = namesOptional1.get(); // Get the list from the Optional System.out.println("List is present. Size: " + names.size()); // You can also check if the list itself is empty if (names.isEmpty()) { System.out.println("List is empty."); } else { System.out.println("List is not empty. First name: " + names.get(0)); } } else { System.out.println("List is not present (Optional is empty)."); } System.out.println("---"); // Case 2: Get names when includeNames is false Optional<List<String>> namesOptional2 = getNames(false); System.out.println("Checking namesOptional2:"); if (namesOptional2.isPresent()) { List<String> names = namesOptional2.get(); System.out.println("List is present. Size: " + names.size()); if (names.isEmpty()) { System.out.println("List is empty."); } else { System.out.println("List is not empty. First name: " + names.get(0)); } } else { System.out.println("List is not present (Optional is empty)."); } System.out.println("\nProgram finished."); } }В этом коде:
- Мы определяем метод
getNames, который возвращаетOptional<List<String>>. Этот метод имитирует ситуацию, когда вы можете получить список или ничего не получить (представленное пустымOptional). - В методе
mainмы вызываемgetNamesс параметрамиtrueиfalse, чтобы протестировать оба случая. - Мы используем
namesOptional.isPresent()для проверки, содержит лиOptionalсписок. - Если
isPresent()возвращаетtrue, мы используемnamesOptional.get()для извлечения списка. Это безопасно, так как мы уже проверили наличие значения. - Внутри блока
isPresent()мы можем выполнить проверки на сам список, например,names.isEmpty().
- Мы определяем метод
Сохраните файл.
Скомпилируйте программу в терминале:
javac HelloJava.javaЗапустите программу:
java HelloJavaВы должны увидеть вывод, похожий на следующий:
Checking namesOptional1: List is present. Size: 2 List is not empty. First name: Alice --- Checking namesOptional2: List is not present (Optional is empty). Program finished.Этот вывод показывает, как
Optionalпомогает нам обрабатывать случай, когда список может не быть возвращен вообще.
Optional также предоставляет другие полезные методы для обработки отсутствия значения, такие как:
orElse(defaultValue): Возвращает значение, если оно присутствует, в противном случае возвращает значение по умолчанию.orElseGet(supplier): Возвращает значение, если оно присутствует, в противном случае возвращает результат функцииsupplier.orElseThrow(exceptionSupplier): Возвращает значение, если оно присутствует, в противном случае выбрасывает исключение, созданное функциейexceptionSupplier.ifPresent(consumer): Выполняет заданное действие, если значение присутствует.
Давайте изменим код, чтобы использовать ifPresent для более компактного способа обработки списка, когда он присутствует.
Откройте файл
HelloJava.javaв редакторе.Измените метод
main, чтобы использоватьifPresent:import java.util.List; import java.util.ArrayList; import java.util.Optional; public class HelloJava { public static Optional<List<String>> getNames(boolean includeNames) { if (includeNames) { List<String> names = new ArrayList<>(); names.add("Alice"); names.add("Bob"); return Optional.of(names); } else { return Optional.empty(); } } public static void main(String[] args) { // Case 1: Get names when includeNames is true Optional<List<String>> namesOptional1 = getNames(true); System.out.println("Checking namesOptional1 using ifPresent:"); namesOptional1.ifPresent(names -> { // This block only runs if namesOptional1 contains a list System.out.println("List is present. Size: " + names.size()); if (names.isEmpty()) { System.out.println("List is empty."); } else { System.out.println("List is not empty. First name: " + names.get(0)); } }); if (!namesOptional1.isPresent()) { // Still need a check if you need to handle the absence case System.out.println("List is not present (Optional is empty)."); } System.out.println("---"); // Case 2: Get names when includeNames is false Optional<List<String>> namesOptional2 = getNames(false); System.out.println("Checking namesOptional2 using ifPresent:"); namesOptional2.ifPresent(names -> { System.out.println("List is present. Size: " + names.size()); if (names.isEmpty()) { System.out.println("List is empty."); } else { System.out.println("List is not empty. First name: " + names.get(0)); } }); if (!namesOptional2.isPresent()) { System.out.println("List is not present (Optional is empty)."); } System.out.println("\nProgram finished."); } }Мы заменили структуру
if (namesOptional.isPresent()) { ... namesOptional.get() ... }наnamesOptional.ifPresent(names -> { ... }). Код внутри лямбда-выражения (names -> { ... }) будет выполняться только в том случае, еслиOptionalсодержит значение. Мы все еще добавили проверкуif (!namesOptional.isPresent())для обработки случая, когдаOptionalпуст, так какifPresentобрабатывает только случай наличия значения.Сохраните файл.
Скомпилируйте программу:
javac HelloJava.javaЗапустите программу:
java HelloJavaВывод должен быть таким же, как и раньше, что демонстрирует, что
ifPresentпредоставляет альтернативный способ обработки наличия значения вOptional.
Использование Optional может сделать намерения вашего кода более ясными в отношении того, может ли значение отсутствовать, и поощряет вас явно обрабатывать такое отсутствие, уменьшая вероятность непредвиденных ошибок NullPointerException.
Резюме
В этом практическом занятии (лабораторной работе) мы научились обрабатывать null-коллекции в Java, чтобы предотвратить ошибку NullPointerException. Мы начали с демонстрации проблемы вызова методов для null-коллекции, которая приводит к ошибке во время выполнения программы.
Затем мы рассмотрели различные методы для безопасной проверки, является ли коллекция null, прежде чем пытаться получить доступ к ее элементам или свойствам. Это фундаментальный навык для написания надежного и безошибочного кода на Java.



