Необязательный делает ваш код читаемым и позволяет избежать исключений нулевого указателя.
Говорят, что программисты, которые не сталкивались с исключениями нулевого указателя, не являются программистами Java.null
Это вызвало много проблем. Java 8 представила метод, называемыйjava.util.Optional
Новый класс может избежатьnull
вызвал много проблем.
давайте посмотрим на одинnull
Какой вред могут нанести цитаты. Сначала создайте классComputer
, структура показана на следующем рисунке:
Что происходит, когда мы вызываем следующий код?
String version = computer.getSoundcard().getUSB().getVersion();
Приведенный выше код вроде бы не проблема, но многие компьютеры (например, Raspberry Pi) на самом деле не имеют звуковой карты, поэтому вызовитеgetSoundcard()
Метод обязательно вызовет исключение нулевого указателя.
Обычный, но плохой подход состоит в том, чтобы вернуть нулевую ссылку, чтобы указать, что компьютер не имеет звуковой карты, но это означает, что вызывается нулевая ссылка.getUSB()
метод, который, очевидно, генерирует исключение управления во время выполнения программы, что приводит к остановке выполнения программы. Подумайте, как неловко вдруг получить эту ошибку, когда ваша программа работает на клиентском компьютере?
Великий компьютерщик Тони Хоар однажды написал: «Я думаю, что создание нулевых ссылок в 1965 году обошлось мне в миллиард долларов. В начале использования нулевых ссылок меня больше всего привлекала простота реализации».
Итак, как избежать исключений нулевого указателя во время работы программы? Вы должны быть бдительными и постоянно проверять возможные ситуации с нулевым указателем, например:
String version = "UNKNOWN";
if(computer != null)
{
Soundcard soundcard = computer.getSoundcard();
if(soundcard != null){
USB usb = soundcard.getUSB();
if(usb != null){
version = usb.getVersion();
}
}
}
Однако вы можете видеть, что в приведенном выше коде слишком много проверок на null, и вся структура кода становится очень уродливой. Но мы должны принять такое решение, чтобы убедиться, что система не запускает нулевой указатель. Если в нашем бизнес-коде будет много таких суждений об нулевых ссылках, это будет раздражать, а также приведет к плохой читаемости нашего кода.
Нулевые ссылки также являются большой потенциальной проблемой, если вы забудете проверить, является ли значение, которое вы даете, нулевым. В этом посте я покажу, что использование нулевой ссылки в качестве представления о том, что значение не существует, является плохим подходом. Вместо использования нулевых ссылок нам нужна лучшая модель для представления значений, которых не существует.
Java 8 представила новый класс под названиемjava.util.Optional<T>
, Дизайн этого класса вдохновлен языками Haskell и Scala. Этот класс может содержать произвольное значение, как показано на диаграмме и в коде ниже. Необязательный параметр можно рассматривать как значение, которое может содержать значение. Если необязательный элемент не содержит значения, он пуст, как показано на рисунке ниже.
public class Computer {
private Optional<Soundcard> soundcard;
public Optional<Soundcard> getSoundcard() { ... }
...
}
public class Soundcard {
private Optional<USB> usb;
public Optional<USB> getUSB() { ... }
}
public class USB{
public String getVersion(){ ... }
}
Приведенный выше код показывает, что компьютер может заменить звуковую карту (звуковая карта может существовать, а может и не существовать). Также возможно, что звуковая карта содержит порт USB. Это улучшение, модель может более четко отражать, что данное значение может не существовать.
Но что делатьOptional<Soundcard>
Что с этим объектом? В конце концов, вы хотите получить номер порта USB. очень простой,Optional
Класс содержит методы для обработки наличия или отсутствия значения. по сравнению с нулевой ссылкойOptional
Классы заставляют вас выполнять обработку, зависящую от значения, что позволяет избежать исключений нулевого указателя.
Следует отметить, что класс Optional не предназначен для замены нулевых ссылок. Вместо этого он разработан, чтобы упростить понимание API, и когда вы видите сигнатуру функции, вы можете сказать, может ли значение, которое должно быть передано функции, не существовать. Это предложит вам открыть необязательный класс для обработки фактической ситуации со значением.
Использовать дополнительный режим
Хватит разговоров, давайте посмотрим на код! Давайте сначала посмотрим, как использовать Optional для перезаписи традиционного обнаружения нулевых ссылок. В конце этой статьи вы поймете, как использовать Optional.
String name = computer.flatMap(Computer::getSoundcard)
.flatMap(Soundcard::getUSB)
.map(USB::getVersion)
.orElse("UNKNOWN");
Создать необязательный объект
Можно создать пустой необязательный объект:
Optional<Soundcard> sc = Optional.empty();
Далее необходимо создать необязательный параметр, содержащий ненулевое значение:
SoundCard soundcard = new Soundcard();
Optional<Soundcard> sc = Optional.of(soundcard);
Если звуковая карта имеет значение null, исключение нулевого указателя будет сгенерировано немедленно (что лучше, чем при получении свойства звуковой карты).
Используя ofNullable, вы можете создать необязательный объект, который может содержать пустые ссылки:
Optional<Soundcard> sc = Optional.ofNullable(soundcard);
Если звуковая карта является нулевой ссылкой, необязательный объект будет пустым.
Обработка значений в необязательном
Теперь, когда у вас есть необязательный объект, вы можете вызвать соответствующий метод, чтобы определить, существует ли значение в необязательном объекте. Вместо обнаружения нулей мы можем использоватьifPresent()
метод, например:
Optional<Soundcard> soundcard = ...;
soundcard.ifPresent(System.out::println);
Это устраняет необходимость проверки нулей, и если необязательный объект пуст, какая информация не будет напечатана.
вы также можете использоватьisPresent()
метод, чтобы увидеть, действительно ли существует необязательный объект. Кроме того, существуетget()
Метод может вернуть содержащееся в необязательном объекте значение, если оно существует. В противном случае он выкинетNoSuchElementException
аномальный. Эти два метода можно использовать в комбинации следующим образом, чтобы избежать исключений:
if(soundcard.isPresent()){
System.out.println(soundcard.get());
}
Но этот метод устарел (он не обеспечивает больших улучшений по сравнению с обнаружением нулей), и мы рассмотрим обычный способ работы.
Возврат значений по умолчанию и связанные операции
Обычным действием при встрече с нулевым значением является возврат значения по умолчанию, что вы можете сделать с помощью троичного выражения:
Soundcard soundcard = maybeSoundcard != null ? maybeSoundcard : new Soundcard("basic_sound_card");
С необязательным объектом вы можетеorElse()
Использовать переопределение, когда необязательный параметр пуст.orElse()
Может быть возвращено значение по умолчанию:
Soundcard soundcard = maybeSoundcard.orElse(new Soundcard("defaut"));
Точно так же его можно использовать, когда необязательный параметр пуст.orElseThrow()
Выбросить исключение:
Soundcard soundcard =
maybeSoundCard.orElseThrow(IllegalStateException::new);
Используйте фильтр для фильтрации определенных значений
Мы часто вызываем метод объекта, чтобы определить его свойства. Например, может потребоваться определить, является ли номер порта USB определенным значением. Чтобы быть в безопасности, вам нужно проверить, является ли медицинский, указывающий на USB, нулевым, прежде чем звонитьgetVersion()
метод, например:
USB usb = ...;
if(usb != null && "3.0".equals(usb.getVersion())){
System.out.println("ok");
}
Если вы используете Необязательно, вы можете использоватьfilter
Переписать функцию:
Optional<USB> maybeUSB = ...;
maybeUSB.filter(usb -> "3.0".equals(usb.getVersion())
.ifPresent(() -> System.out.println("ok"));
Метод фильтрации требуетpredicate
Напротив как параметр. Если значение в Необязательном существует и удовлетворяетpredicate
, то функция фильтра вернет значение, удовлетворяющее условию, в противном случае она вернет пустой необязательный объект.
Извлечение и преобразование данных с помощью метода карты
Распространенным шаблоном является извлечение некоторых свойств объекта. Например, дляSoundcard
объект, вам может потребоваться получить его USB-объект, а затем определить номер его версии. Обычно наша реализация выглядит так:
if(soundcard != null){
USB usb = soundcard.getUSB();
if(usb != null && "3.0".equals(usb.getVersion()){
System.out.println("ok");
}
}
Мы можем переписать это, используя метод картыПроверяйте значение null перед извлечением объектатип объекта.
Optional<USB> usb = maybeSoundcard.map(Soundcard::getUSB);
Это то же самое, что и функция карты, использующая потоки. Использование потока требует передачи функции в качестве аргумента функции карты, которая будет применяться к каждому элементу в потоке. Когда поток представляет собой пространство-время, ничего не происходит.
Функция, в которую будет передано значение, содержащееся в необязательномконвертировать(здесь есть функция получения USB от звуковой карты). Если необязательный объект является пространственно-временным, ничего не произойдет.
Затем мы объединяем метод карты и метод фильтра, чтобы отфильтровать звуковую карту, номер версии USB которой не равен 3.0.
maybeSoundcard.map(Soundcard::getUSB)
.filter(usb -> "3.0".equals(usb.getVersion())
.ifPresent(() -> System.out.println("ok"));
Таким образом, наш код начинает немного походить на то, что мы дали ему в начале, без проверки нуля.
Используйте функцию flatMap для передачи дополнительного объекта
Теперь, когда представлен пример рефакторинга кода с использованием Optional, как нам безопасно реализовать следующий код?
String version = computer.getSoundcard().getUSB().getVersion();
Обратите внимание, что приведенный выше код предназначен для извлечения другого объекта из одного объекта, что может быть достигнуто с помощью функции карты. В предыдущей статье мы установили, что компьютер содержит необязательный объект, а звуковая карта содержит необязательный объект, поэтому мы можем реорганизовать код следующим образом.
String version = computer.map(Computer::getSoundcard)
.map(Soundcard::getUSB)
.map(USB::getVersion)
.orElse("UNKNOWN");
К сожалению, приведенный выше код компилируется с ошибками, так почему? Компьютерная переменная имеет тип Необязательный, поэтому нет проблем с вызовом функции карты. ноgetSoundcard()
метод возвращаетOptional<Soundcard>
объект, который возвращаетOptional<Optional<Soundcard>>
Тип объекта, делается второй вызов функции карты, и вызывается результатgetUSB()
функция становится нелегальной. На следующей диаграмме показан этот сценарий:
Реализация исходного кода функции карты выглядит следующим образом:
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
Видно, что функция карты будет вызываться сноваOptional.ofNullable()
, что приводит к возвратуOptional<Optional<Soundcard>>
Optional предоставляет функцию flatMap, предназначенную для преобразования значения объекта Optional (например, операции карты), а затем сжатия двухуровневого объекта Optional в один. На следующем рисунке показана разница между преобразованием типов объекта Optional путем вызова map и flatMap:
Итак, мы можем написать:
String version = computer.flatMap(Computer::getSoundcard)
.flatMap(Soundcard::getUSB)
.map(USB::getVersion)
.orElse("UNKNOWN");
Первый flatMap гарантирует, что будет возвращено значение Optional вместо Optionalmap()
,так какgetVersion()
Он возвращает объект String вместо необязательного объекта.
Наконец мы переписали уродливый код, который мы начали с вложенных проверок null, в более читаемый код, который избегает исключений нулевого указателя.
Суммировать
В этой статье мы используем новые классы, предоставляемые Java 8.java.util.Optional<T>
. Первоначальная цель этого класса не в том, чтобы заменить нулевые ссылки, а в том, чтобы помочь разработчикам разрабатывать лучшие API, просто читая сигнатуру функции, чтобы узнать, принимает ли функция значение, которое может существовать или не существовать. Кроме того, Необязательный вынуждает вас открыть Необязательный, а затем обработать, существует ли значение, что позволяет вашему коду избежать потенциальных исключений нулевого указателя.
Наконец
Спасибо, что прочитали. Если вам интересно, вы можете подписаться на общедоступную учетную запись WeChat, чтобы получать последние push-статьи.