Рубрики
Без рубрики

Руководство по использованию Необязательно в Java 8

Необязательный класс был введен в Java 8 для устранения многих проблем с нулевыми ссылками. В этой статье мы рассмотрим класс, как он используется, и улучшим код с помощью опции.

Автор оригинала: Luka Čupić.

Вступление

При написании любого вида кода на Java разработчики, как правило, чаще работают с объектами, чем с примитивными значениями ( int , boolean и т. Д.). Это связано с тем, что объекты лежат в основе объектно-ориентированного программирования: они позволяют программисту писать абстрактный код чистым и структурированным способом.

Кроме того, каждый объект в Java может содержать значение или нет. Если это так, то его значение хранится в куче, а переменная, которую мы используем, имеет ссылку на этот объект. Если объект не содержит значения, по умолчанию используется значение null – специальный заполнитель, обозначающий отсутствие значения.

Тот факт , что каждый объект может стать null в сочетании с естественной тенденцией использовать объекты вместо примитивов, означает, что какой-то произвольный фрагмент кода может (и часто будет) приводить к неожиданному NullPointerException .

До того, как в Java 8 был введен Необязательный класс, подобные ошибки NullPointerException были гораздо более распространены в повседневной жизни Java-программиста.

В следующих разделах мы углубимся в объяснение Необязательно и посмотрим, как его можно использовать для преодоления некоторых распространенных проблем, связанных с нулевыми значениями.

Необязательный класс

Необязательно по сути является контейнером. Он предназначен либо для хранения значения, либо для “пустого”, если значение отсутствует-замена значения null . Как мы увидим в некоторых последующих примерах, эта замена имеет решающее значение, поскольку она позволяет неявно проверять нуль для каждого объекта, представленного как Необязательный .

Это означает, что явная проверка на нуль больше не требуется с точки зрения программиста-она становится принудительной самим языком.

Создание Необязательного

Давайте посмотрим, насколько легко создавать экземпляры Необязательно и переносить объекты, которые у нас уже есть в наших приложениях.

Для этого мы будем использовать наш пользовательский класс, класс Космический корабль :

public class Spaceship {
    private Engine engine;
    private String pilot;

    // Constructor, Getters and Setters
}

И наш Движок выглядит так:

public class Engine {
    private VelocityMonitor monitor;

    // Constructor, Getters and Setters
}

И, кроме того, у нас есть Монитор скорости класс:

public class VelocityMonitor {
    private int speed;

    // Constructor, Getters and Setters
}

Эти классы произвольны и служат только для того, чтобы подчеркнуть, что за ними нет реальной реализации.

из()

Первый подход к созданию Необязательных s заключается в использовании метода .of () , передающего ссылку на ненулевой объект:

Spaceship falcon = new Spaceship();
Optional optionalFalcon = Optional.of(falcon);

Если бы falcon был null , метод . of() выдал бы Исключение NullPointerException .

Без Необязательно попытка доступа к любому из полей или методов falcon (при условии, что это null ), без выполнения проверки на нуль приведет к сбою программы.

С Необязательно , метод . of() замечает значение null и немедленно вызывает исключение NullPointerException – потенциально также может привести к сбою программы.

Если программа выходит из строя при обоих подходах, зачем вообще использовать Необязательно ?

Программа не рухнет где-то глубже в коде (при доступе к falcon ), но при самом первом использовании (инициализации) объекта null , минимизируя потенциальный ущерб.

ofNullable()

Если falcon разрешено использовать null , вместо метода .of() мы бы использовали .ofNullable() метод. Они выполняют то же самое, если значение не равно null . Разница очевидна, когда ссылка указывает на null , в этом случае – на .ofNullable() метод идеально подходит для этого фрагмента кода:

Spaceship falcon = null;
Optional optionalFalcon = Optional.ofNullable(falcon);

пусто()

И, наконец, вместо обертывания существующей ссылочной переменной ( null или не null ) мы можем создать null значение в контексте Необязательного . Это похоже на пустой контейнер, который возвращает пустой экземпляр Необязательно :

Optional emptyFalcon = Optional.empty();

Проверка значений

После создания Необязательных s и упаковки в них информации вполне естественно, что мы захотим получить к ним доступ.

Однако перед доступом мы должны проверить, есть ли какие-либо значения или Необязательные s пусты.

Присутствует()

Поскольку перехват исключений является сложной операцией, было бы лучше использовать один из методов API, чтобы проверить, существует ли значение, прежде чем пытаться получить к нему доступ, и изменить поток, если это не так.

Если это так, то для доступа к значению можно использовать метод . get () . Хотя подробнее об этом методе в последних разделах.

Чтобы проверить , присутствует ли значение внутри Необязательно , мы используем метод .isPresent () . Это, по сути, замена null -проверки старых дней:

// Without Optional
Spaceship falcon = hangar.getFalcon();
if (falcon != null) {
    System.out.println(falcon.get());
} else {
    System.out.printn("The Millennium Falcon is out and about!");
}

// With Optional
Optional optionalFalcon = Optional.ofNullable(hangar.getFalcon());
if (optionalFalcon.isPresent()) {
    System.out.println(falcon.get());
} else {
    System.out.println("The Millennium Falcon is out and about!");
}

Поскольку falcon также не может находиться в ангаре, мы также можем ожидать нулевое значение, таким образом .Используется функция ofNullable () .

Если присутствует()

Чтобы сделать все еще проще, Необязательно также содержит условный метод, который полностью обходит проверку наличия:

Optional optionalFalcon = Optional.ofNullable(hangar.getFalcon());
optionalFalcon.ifPresent(System.out::println);

Если значение присутствует, содержимое печатается с помощью ссылки на метод . Если в контейнере нет ценности, ничего не происходит. Однако вы все равно можете использовать предыдущий подход, если хотите определить оператор else {} .

Это отражает то, что мы упоминали ранее, когда говорили, что null -проверки с Необязательными являются неявными и выполняются системой типов.

Пусто()

Другой способ проверить значение-использовать .isEmpty() . По сути, вызов Необязательный.isEmpty () – это то же самое, что вызов !Необязательно.isPresent() . Нет никакой особой разницы, которая существует:

Optional optionalFalcon = Optional.ofNullable(hangar.getFalcon());
if (optionalFalcon.isEmpty()) {
    System.out.println("Please check if the Millennium Falcon has returned in 5 minutes.");
} else {
    optionalFalcon.doSomething();
}

Вложенные проверки на Нуль

Наш Космический корабль класс, как определено ранее, имеет атрибут Двигатель , который имеет атрибут Монитор скорости .

Предположим теперь, что мы хотим получить доступ к объекту мониторинга скорости и получить текущую скорость космического корабля, принимая во внимание, что все эти значения потенциально могут быть нулевыми .

Git Essentials

Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!

Получение скорости может выглядеть примерно так:

if (falcon != null) {
    Engine engine = falcon.getEngine();
    if (engine != null) {
        VelocityMonitor monitor = engine.getVelocityMonitor();
        if (monitor != null) {
            Velocity velocity = monitor.getVelocity();
            System.out.println(velocity);
        }
    }
}

Приведенный выше пример показывает, насколько утомительно выполнять такие проверки, не говоря уже о количестве стандартного кода, необходимого для того, чтобы сделать проверки возможными в первую очередь.

Альтернативным решением с использованием Необязательно было бы:

Velocity velocity = falcon
    .flatMap(Spaceship::getEngine)
    .flatMap(Engine::getVelocityMonitor)
    .map(VelocityMonitor::getVelocity);

Примечание : Не знаете, что происходит выше? Ознакомьтесь с пояснением ниже для получения более подробной информации.

При использовании такого подхода явные проверки не требуются. Если какой-либо из объектов содержит пустой Необязательный , конечным результатом также будет пустой Необязательный .

Чтобы все работало подобным образом, нам нужно изменить наши существующие определения классов Космический корабль и Двигатель :

public class Spaceship {
    private Optional engine;
    private String pilot;

    // Constructor, Getters and Setters
}
public class Engine {
    private Optional monitor;

    // Constructor, Getters and Setters
}

Что мы изменили, так это определения атрибутов: теперь они заключены в Необязательные объекты, чтобы сделать возможным такое альтернативное решение.

Поначалу это может показаться немного утомительным, но если все спланировать с самого начала, то для его ввода потребуется почти столько же усилий.

Кроме того, наличие Необязательного атрибута вместо обычного объекта отражает тот факт, что атрибут может существовать или не существовать . Обратите внимание, как это весьма полезно, поскольку у нас нет семантических значений такого рода с регулярными определениями атрибутов.

Пример Объяснения

В этом разделе мы уделим немного времени объяснению предыдущего примера с плоскими картами и картами . Если вы понимаете это без дальнейших объяснений, пожалуйста, не стесняйтесь пропустить этот раздел.

Первый вызов метода выполняется на falcon , который имеет тип Необязательный<Космический корабль> . Вызов метода getEngine возвращает объект типа Необязательно<Двигатель> . Объединяя эти два типа, тип возвращаемого объекта становится Необязательным<Необязательный<Механизм>> .

Поскольку мы хотели бы рассматривать этот объект как Движок контейнер и выполнять дальнейшие вызовы к нему, нам нужен какой-то механизм для “снятия” внешнего Необязательного слоя.

Такой механизм существует, и он называется flatMap . Этот метод API объединяет операции map и flat , сначала применяя функцию к каждому из элементов, а затем сглаживая результат в одноуровневый поток.

С другой стороны, метод map применяет функцию только без выравнивания потока. В нашем случае использование map и flatMap дало бы нам Необязательный<Необязательный<Движок>> и Необязательный<Движок> соответственно.

Вызов flatMap для объекта типа Необязательно , следовательно, приведет к одноуровневому Необязательному , что позволит нам последовательно использовать несколько аналогичных вызовов методов.

Это , наконец, оставляет нам Дополнительный<Движок> , который мы хотели в первую очередь.

Альтернативные результаты

.Орел()

Предыдущий пример может быть дополнительно расширен с помощью метода OrElse(T другое) . Метод вернет Необязательный объект, для которого он вызывается, только в том случае, если в нем содержится значение.

Если Необязательный пуст, метод возвращает другое значение. По сути, это Необязательная версия тернарного оператора:

// Ternary Operator
Spaceship falcon = maybeFalcon != null ? maybeFalcon : new Spaceship("Millennium Falcon");

// Optional and orElse()
Spaceship falcon = maybeFalcon.orElse(new Spaceship("Millennium Falcon"));

Как и в случае с методом ifPresent () , этот подход использует преимущества лямбда-выражений , чтобы сделать код более читаемым и менее подверженным ошибкам.

.Орельсегет()

Вместо того, чтобы указывать другое значение непосредственно в качестве аргумента, мы можем использовать Поставщика вместо этого. Разница между .или иначе() и .orElseGet() , хотя, возможно, и не очевидна на первый взгляд, существует:

// orElse()
Spaceship falcon = maybeFalcon.orElse(new Spaceship("Millennium Falcon"));

// orElseGet()
Spaceship falcon = maybeFalcon.orElseGet(() -> new Spaceship("Millennium Falcon"));

Если возможно, Falcon не содержит значения, оба метода вернут новый Космический корабль . В этом случае их поведение такое же. Разница становится очевидной, если может быть, Falcon действительно содержит значение.

В первом случае новый космический корабль объект не будет возвращен, но он будет создан . Это произойдет независимо от того, существует ли значение или нет. Во втором случае новый космический корабль будет создан только если возможно, Falcon не содержит значения.

Это похоже на то, как do-while выполняет задачу независимо от цикла while , по крайней мере, один раз.

Это может показаться незначительной разницей, но это становится довольно важным, если создание космических кораблей является сложной операцией. В первом случае мы всегда создаем новый объект – даже если он никогда не будет использоваться.

..или в противном случае Get() следует предпочесть вместо .или в противном случае() в таких случаях.

.орЕльсЕтроу()

Вместо того, чтобы возвращать альтернативное значение (как мы видели в предыдущих двух разделах), мы можем создать исключение. Это достигается с помощью метода .orElseThrow () , который вместо альтернативного значения принимает поставщика, который возвращает исключение в случае, если его необходимо выбросить.

Это может быть полезно в тех случаях, когда конечный результат имеет большое значение и не должен быть пустым. Создание исключения в этом случае может быть самым безопасным вариантом:

// Throwing an exception
Spaceship falcon = maybeFalcon.orElseThrow(NoFuelException::new);

Получение значений из необязательных

.получить()

После просмотра множества различных способов проверки и доступа к значению внутри Необязательно , давайте теперь рассмотрим один последний способ получения значения, который также использует некоторые из ранее показанных методов.

Самый простой способ получить доступ к значению внутри Необязательного – это .get() . Этот метод возвращает текущее значение или создает исключение NoSuchElementException , если значение отсутствует:

Optional optionalFalcon = Optional.ofNullable(hangar.getFalcon());
if (falcon.isPresent()) {
    Spaceship falcon = optionalFalcon.get()

    // Fly the falcon
}

Как и ожидалось, метод .get() возвращает не нулевой экземпляр класса Spaceship и присваивает его объекту falcon .

Вывод

Необязательный был введен в Java как способ устранения проблем со ссылками null . До Необязательно каждому объекту разрешалось либо содержать значение , либо нет (т. е. быть нулевым ).

Введение Необязательно по существу обеспечивает нулевую -проверку системой типов, что делает ненужным выполнение таких проверок вручную.

Это был большой шаг как в улучшении языка, так и в его удобстве использования за счет добавления дополнительного уровня проверки типов. Использование этой системы вместо старомодной нулевой -проверки позволяет писать четкий и лаконичный код без необходимости добавлять шаблонные шаблоны и выполнять утомительные проверки вручную.