Optional
Решаем проблему NullPointerException с помощью Optional. Безопасная работа с nullable-значениями без проверок на null
Проблема: NullPointerException
NullPointerException (NPE) — самое частое исключение в Java. Оно возникает, когда вы пытаетесь обратиться к методу или полю объекта, который равен null.
public class Main {
public static void main(String[] args) {
String name = null;
System.out.println(name.length()); // NullPointerException!
}
}
Для защиты от NPE приходится писать много проверок на null:
public class Main {
public static void main(String[] args) {
String name = getName();
if (name != null) {
System.out.println(name.length());
} else {
System.out.println("Имя не найдено");
}
}
static String getName() {
return null;
}
}
Аналогия: Представьте, что вы заказываете пиццу. Доставщик может принести пиццу, а может сказать «извините, пиццы нет». Без Optional вам приходится каждый раз проверять: «А пицца есть? А точно есть? А может, она протухла?». Optional — это как уверенный ответ: «Пицца точно есть, и вот она» или «Пиццы нет, но вот что я могу предложить вместо неё».
Optional — это контейнер, который может содержать значение (не null) или быть пустым. Он заставляет вас явно обработать оба варианта, избегая случайных NPE.
Создание Optional
Есть три способа создать Optional:
Optional.of(value) — создаёт Optional с гарантированно ненулевым значением. Если передать null, выбросит NullPointerException:
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Optional<String> name = Optional.of("Анна");
System.out.println(name.isPresent()); // true
Optional<String> empty = Optional.of(null); // NullPointerException!
}
}
Optional.ofNullable(value) — создаёт Optional, который может содержать null. Если значение null — Optional будет пустым:
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Optional<String> name1 = Optional.ofNullable("Анна");
System.out.println(name1.isPresent()); // true
Optional<String> name2 = Optional.ofNullable(null);
System.out.println(name2.isPresent()); // false
}
}
Optional.empty() — создаёт пустой Optional без значения:
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Optional<String> empty = Optional.empty();
System.out.println(empty.isPresent()); // false
System.out.println(empty.isEmpty()); // true
}
}
Правило: Используйте Optional.of() когда уверены, что значение не null. Используйте Optional.ofNullable() когда значение может быть null. Никогда не храните null в Optional.
Проверка наличия значения
Optional предоставляет несколько способов проверить, есть ли значение:
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Optional<String> name = Optional.of("Анна");
Optional<String> empty = Optional.empty();
System.out.println(name.isPresent()); // true
System.out.println(name.isEmpty()); // false
System.out.println(empty.isPresent()); // false
System.out.println(empty.isEmpty()); // true
}
}
Но проверка isPresent() + get() — это всё ещё «старый» стиль. Гораздо удобнее использовать методы, которые мы рассмотрим дальше.
Получение значения
Есть несколько способов безопасно получить значение из Optional:
get() — получает значение напрямую. Если Optional пуст, выбросит NoSuchElementException:
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Optional<String> name = Optional.of("Анна");
System.out.println(name.get()); // Анна
Optional<String> empty = Optional.empty();
System.out.println(empty.get()); // NoSuchElementException!
}
}
orElse(defaultValue) — возвращает значение или значение по умолчанию, если Optional пуст:
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Optional<String> name = Optional.of("Анна");
Optional<String> empty = Optional.empty();
System.out.println(name.orElse("Неизвестно")); // Анна
System.out.println(empty.orElse("Неизвестно")); // Неизвестно
}
}
orElseGet(supplier) — возвращает значение или вычисляет его с помощью Supplier, если Optional пуст. Полезно, когда значение по умолчанию дорогое в вычислении:
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Optional<String> empty = Optional.empty();
String result = empty.orElseGet(() -> {
System.out.println("Вычисляю значение...");
return "Вычислено";
});
System.out.println(result); // Вычисляю значение... Вычислено
}
}
orElseThrow() — возвращает значение или выбрасывает исключение, если Optional пуст:
import java.util.Optional;
import java.util.NoSuchElementException;
public class Main {
public static void main(String[] args) {
Optional<String> name = Optional.of("Анна");
Optional<String> empty = Optional.empty();
System.out.println(name.orElseThrow()); // Анна
System.out.println(empty.orElseThrow()); // NoSuchElementException!
}
}
ifPresent: выполнение при наличии значения
Метод ifPresent() выполняет действие, только если значение присутствует. Это безопасная альтернатива проверке if (value != null):
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Optional<String> name = Optional.of("Анна");
Optional<String> empty = Optional.empty();
name.ifPresent(n -> System.out.println("Имя: " + n)); // Имя: Анна
empty.ifPresent(n -> System.out.println("Имя: " + n)); // Ничего не выведет
}
}
ifPresentOrElse() — выполняет одно действие при наличии значения и другое при его отсутствии:
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Optional<String> name = Optional.of("Анна");
Optional<String> empty = Optional.empty();
name.ifPresentOrElse(
n -> System.out.println("Найдено: " + n),
() -> System.out.println("Не найдено")
);
empty.ifPresentOrElse(
n -> System.out.println("Найдено: " + n),
() -> System.out.println("Не найдено")
);
}
}
Преобразование: map, filter, flatMap
Optional поддерживает те же операции, что и Stream: map, filter и flatMap. Это позволяет строить цепочки обработки.
map() — преобразует значение, если оно есть:
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Optional<String> name = Optional.of("Анна");
Optional<Integer> length = name.map(String::length);
System.out.println(length.orElse(0)); // 4
Optional<String> upper = name.map(String::toUpperCase);
System.out.println(upper.orElse("")); // АННА
Optional<String> empty = Optional.empty();
Optional<Integer> emptyLength = empty.map(String::length);
System.out.println(emptyLength.orElse(0)); // 0
}
}
filter() — оставляет значение, если оно удовлетворяет условию:
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Optional<String> name = Optional.of("Анна");
Optional<String> longName = name.filter(n -> n.length() > 5);
System.out.println(longName.isPresent()); // false
Optional<String> shortName = name.filter(n -> n.length() > 2);
System.out.println(shortName.isPresent()); // true
}
}
flatMap() — используется, когда функция преобразования возвращает Optional:
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Optional<String> name = Optional.of("Анна");
Optional<Integer> result = name.flatMap(n -> {
if (n.length() > 3) {
return Optional.of(n.length());
}
return Optional.empty();
});
System.out.println(result.orElse(0)); // 4
}
}
Цепочки операций с Optional
Главное преимущество Optional — возможность строить цепочки операций для безопасной обработки nullable-значений:
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Optional<String> name = Optional.of("Анна");
String result = name
.filter(n -> n.length() > 3)
.map(String::toUpperCase)
.orElse("Неизвестно");
System.out.println(result); // АННА
Optional<String> short = Optional.of("Б");
String result2 = short
.filter(n -> n.length() > 3)
.map(String::toUpperCase)
.orElse("Неизвестно");
System.out.println(result2); // Неизвестно
}
}
Практический пример: безопасный поиск
Давайте создадим пример с поиском пользователя, который возвращает Optional вместо null:
import java.util.HashMap;
import java.util.Optional;
public class UserFinder {
static HashMap<String, String> users = new HashMap<>();
static {
users.put("anna", "Анна Иванова");
users.put("boris", "Борис Петров");
users.put("vika", "Вика Сидорова");
}
public static Optional<String> findUser(String login) {
return Optional.ofNullable(users.get(login));
}
public static void main(String[] args) {
findUser("anna")
.ifPresent(name -> System.out.println("Найден: " + name));
findUser("dima")
.ifPresentOrElse(
name -> System.out.println("Найден: " + name),
() -> System.out.println("Пользователь не найден")
);
String name = findUser("boris")
.map(String::toUpperCase)
.orElse("НЕИЗВЕСТНО");
System.out.println("Имя: " + name);
}
}
Optional и Stream API
Optional отлично работает со Stream API. Многие терминальные операции Stream возвращают Optional:
import java.util.ArrayList;
import java.util.Optional;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(4);
numbers.add(5);
Optional<Integer> max = numbers.stream()
.max(Integer::compareTo);
System.out.println("Максимум: " + max.orElse(0)); // 5
Optional<Integer> first = numbers.stream()
.filter(n -> n > 10)
.findFirst();
System.out.println("Больше 10: " + first.orElse(-1)); // -1
Optional<Integer> found = numbers.stream()
.filter(n -> n == 3)
.findFirst();
found.ifPresent(n -> System.out.println("Найдено: " + n));
}
}
Когда использовать Optional
Optional полезен в следующих ситуациях:
- Возвращаемое значение метода может отсутствовать (поиск, получение по ключу)
- Результат вычисления зависит от условий
- Вы хотите избежать NullPointerException
- Вы хотите явно показать, что значение может отсутствовать
Когда НЕ использовать Optional:
- Как параметр метода (лучше использовать перегрузку или null)
- В полях класса (лучше использовать null или инициализацию)
- В коллекциях (коллекции не должны содержать Optional)
- Когда null — допустимое значение бизнес-логики
import java.util.Optional;
public class User {
private String name;
private Optional<String> email;
public User(String name, String email) {
this.name = name;
this.email = Optional.ofNullable(email);
}
public String getName() {
return name;
}
public Optional<String> getEmail() {
return email;
}
}
class Main {
public static void main(String[] args) {
User user1 = new User("Анна", "anna@mail.ru");
User user2 = new User("Борис", null);
user1.getEmail()
.ifPresent(e -> System.out.println("Email Анны: " + e));
user2.getEmail()
.ifPresentOrElse(
e -> System.out.println("Email Бориса: " + e),
() -> System.out.println("У Бориса нет email")
);
}
}
Итоги урока
- Optional — контейнер, который может содержать значение или быть пустым
- Optional.of() — создаёт Optional с гарантированно ненулевым значением
- Optional.ofNullable() — создаёт Optional, который может быть пустым
- Optional.empty() — создаёт пустой Optional
- isPresent()/isEmpty() — проверка наличия значения
- get() — получение значения (опасно без проверки!)
- orElse(value) — значение или значение по умолчанию
- orElseGet(supplier) — значение или вычисление по умолчанию
- orElseThrow() — значение или исключение
- ifPresent(consumer) — действие при наличии значения
- map() — преобразование значения
- filter() — фильтрация по условию
- flatMap() — преобразование, возвращающее Optional
- Optional безопаснее проверок на null и делает код выразительнее
Тест по Optional
5 вопросов