В Java 8 есть полезный класс java.util.Optional. Он используется для избавления от большого числа проверок значений на null, которые затрудняют чтение кода.
Пусть, например, у нас есть класс:
1 2 3 4 5 6 7 8 9 |
class MyClass { public void myMethod() { } public Integer getSomeValue() { return 2; } } |
Пример кода с экземпляром этого класса и проверкой на null:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public static Integer testMethod(MyClass myClass) { if (myClass != null) { myClass.myMethod(); return myClass.getSomeValue(); } return null; } public static void main(String[] args) { MyClass myClass = new MyClass(); Integer result1 = testMethod(myClass); } |
Аналогичный код с использованием модного класса Optional:
1 2 3 4 5 6 7 8 9 10 |
public static Integer testMethodWithOptional(Optional<MyClass> myClass) { myClass.ifPresent(MyClass::myMethod); return myClass.map(MyClass::getSomeValue).orElse(null); } public static void main(String[] args) { MyClass myClass = new MyClass(); int result2 = testMethodWithOptional(Optional.ofNullable(myClass)); } |
Как видно из этого примера, код метода testMethodWithOptional стал короче на две строки. Если бы в исходном примере было бы ещё больше проверок на null, то сокращение размера кода было бы ещё больше заметно.
Давайте теперь разберёмся, что же у нас тут происходит. Сначала обратите внимание на вызов Optional.ofNullable(myClass). Этот вызов статического метода создаёт экземпляр класса Optional. Созданный экземпляр выступает в качестве контейнера, который может содержать, либо может не содержать не null значение. Для проверки того, содержит ли контейнер значение, нужно использовать метод isPresent(), который возвращает true, если контейнер содержит значение. В примере выше мы его не использовали, но метод весьма полезен.
В дальнейшем вся работа происходит через методы этого контейнера. Метод ifPresent позволяет вызвать какой-либо код, использующий объект из контейнера, но этот код будет выполняться только в том случае, если контейнер содержит значение. В данном случае мы передаём туда ссылку на метод myMethod.
Если же нам нужно вызвать какой-то код, результатом которого нам нужно воспользоваться в дальнейшем, то нам нужно использовать метод map, который возвращает экземпляр Optional, содержащий возвращённое значение, если оно НЕ null. В примере выше внутри метода map мы вызываем наш метод getSomeValue у класса MyClass.
Но из нашего метода testMethodWithOptional нам нужно вернуть не сам контейнер, а хранящееся в нём значение или null, если значения нет. Для этого мы вызываем метод orElse, который возвращает хранящееся в контейнере Optional значение, если оно есть, либо значение, которое мы передадим в качестве аргумента метода. В данном случае в качестве аргумента мы передаём null.
Сокращение размера кода и проверок на null становится очень сильно заметно, если нам нужно подряд вызвать сначала один метода объекта, затем если вернулось НЕ null, то вызвать другой метод над возвращённым объектом и т. д. Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import java.util.Arrays; import java.util.Optional; public class Main { static class A { public B getB() { return new B(); } } static class B { public C getC() { return new C(); } } static class C { public D getD() { return new D("from C"); } } static class D { private String value; public D(String value) { this.value = value; } @Override public String toString() { return "D [value=" + value + "]"; } } static class E {} public static void main(String[] args) { D d = Optional.ofNullable(new A()).map(A::getB).map(b -> b.getC()) .map(C::getD).orElse(new D("from orElse")); System.out.println("d = " + d); } } |
Попробуйте поиграться с кодом выше. Например, попробуйте поправить код класса C или B, чтобы они возвращали null, тогда вы увидите, что экземпляр класса D будет содержать строку "from orElse" , так как он создан в методе orElse, где ему в конструктор передалось именно это значение. В текущем же варианте кода экземпляр класса D будет содержать строку "from C".
Если метод вашего класса уже возвращает экземпляр Optional, то использование метода map приведёт к тому, что в результирующем значении будет Optional внутри Optional. Для того чтобы это избежать, используйте метод flatMap:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import java.util.Optional; public class Main { static class MyClass { public Optional<Integer> getSomeValue() { return Optional.ofNullable(2); } } public static void main(String[] args) { Integer result = Optional.ofNullable(new MyClass()) .flatMap(MyClass::getSomeValue) .orElse(Integer.valueOf(-1)); } } |
Если же мы вызываем цепочкой какие-то методы, и на самом последнем этапе нам нужно либо вернуть значение, либо бросить какое-то конкретное исключение, если значения нет, то нужно использовать метод orElseThrow:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import java.util.Optional; public class Main { static class MyClass { public Optional<Integer> getSomeValue() { return Optional.ofNullable(2); } } static class NoResultValueException extends Exception { } public static void main(String[] args) throws NoResultValueException { Integer result = Optional.ofNullable(new MyClass()) .flatMap(MyClass::getSomeValue) .orElseThrow(NoResultValueException::new); } } |
Всю эту статью мне, возможно, стоило бы включить в мой учебник, но тогда я почему-то пропустил этот полезный класс. Пусть пока отдельно здесь полежит, чтобы учебник не слишком разбухал.
И вообще, я что-то отстал от жизни. Пора уже начинать что-нибудь писать про нововведения в Java 9, а я тут про старьё какое-то пишу.
Отлично всё пишете! Спасибо Вам.
Думаю, с темпами развития Java скоро и 11 версия станет старьём.
«orElse(null)» — так не делают))