사용 예제
이전 페이지에서 ``Optional``이 왜 사용되는지 배웠습니다. 이제 자바에서 이를 직접 사용해보도록 하겠습니다. (스펀지와) 아래 코드 예제들은 Java 8 버전 기준의 java.util.Optional 클래스를 사용합니다.
포장된 값 얻기
get()
메소드는 ``Optional``의 포장을 뜯어서 그 안에 담긴 값을 반환해 줍니다. 만약 값이 존재하지 않으면 ``NoSuchElementException``이 발생되므로, 값의 존재 여부를 먼저 확인해야 합니다.
Optional<String> opt = getOptionalString();
String wrappedString = opt.get();
값이 없을 경우를 처리하기
Optional
타입은 있을지도 없을지도 모르는 값을 제공하는 목적으로 설계되었기 때문에, 많은 사용 사례에서 값의 부재를 처리하는 것을 확인할 수 있습니다.
존재 확인
isPresent()
메소드는 Optional
의 값이 지금 존재할 경우 true를 반환합니다. 이것은 고전적인 null
값 검사와 동일한 기능을 하며, 가장 기본적인 검증 수단이 됩니다.
Optional<String> opt = getOptionalString();
if (opt.isPresent()) {
String wrappedString = opt.get();
// more code
}
기본 값 사용하기
값이 존재하지 않을 경우, 일반적인 대비책은 기본값을 대입하는 것입니다. orElse()
메소드를 사용하면 한 줄의 선언으로 Optional
에 존재하는 값 또는 지정된 기본값을 받아 사용 (또는 대입) 할 수 있습니다.
이렇던 코드가:
Optional<String> optionalString = optionalString();
String someString;
if (optionalString.isPresent()) {
someString = optionalString.get();
} else {
someString = DEFAULT_STRING;
}
이렇게 줄어듭니다:
String someString = getOptionalString().orElse(DEFAULT_STRING);
In some cases a default value has to be calculated in a way that has side effects or is particularly expensive. In such
a case it is desirable to calculate the default value only if needed (lazy evaluation). The orElseGet()
method
accepts a Supplier
instead of a pre-calculated value. If no value is present on the Optional
itself, the
Supplier
will be called. Since Supplier
is a functional interface, a lambda expression or method reference can
be passed instead.
이렇던 코드가:
Optional<String> optionalString = optionalString();
String someString;
if (optionalString.isPresent()) {
someString = optionalString.get();
} else {
someString = myPlugin.defaultString();
}
이렇게 줄어듭니다:
String someString = getOptionalString().orElseGet(myPlugin::defaultString);
값이 없을 경우 예외 처리하기
If a value being absent should lead to an exception, it is almost always better to throw a custom exception instead of
relying on the default NoSuchElementException
. If you call the orElseThrow()
method with a Supplier
, it will
return the wrapped value if it is present, or throw a Throwable
obtained from the Supplier
if the Optional
is empty. Again, as Supplier
is a functional interface, lambda expressions or method references may be used instead.
이렇던 코드가:
Optional<String> optionalString = optionalString();
if (!optionalString.isPresent()) {
throw new MyException();
}
String someString = optionalString.get();
이렇게 줄어듭니다:
String someString = getOptionalString().orElseThrow(MyException::new);
참고
Supplier에 대입한 Throwable
예외가 Checked Exception일 경우, 감싸고 있는 함수의 선언문에 예외 처리를 명시할 필요가 있습니다. (예시: public void doStuff() throws MyException
)
조건부 코드 실행
기본값을 사용할 수 없다면, 해당 값에 의존하는 코드는 실행될 수 없습니다. 이 문제는 간단한 조건문으로 해결될 수도 있지만, 이를 해결해주는 편리한 메소드가 몇 가지 있습니다.
값을 사용하기
현재 값을 처리하는 당신의 로직이 Consumer
또는 단일 매개변수 함수 (즉 함수형 인터페이스) 로 요약되어 있다면, ifPresent()
메소드가 해당 Consumer (또는 메소드) 를 받아들이고, 만약 Optional
에 값이 존재한다면 그 값은 Consumer로 넘겨질 것입니다. 하지만 Optional
이 없다면, 아무 것도 실행되지 않을 것입니다.
이렇던 코드가:
Optional<String> optionalString = getOptionalString();
if (optionalString.isPresent()) {
myPlugin.doSomethingWithString(optionalString.get());
}
이렇게 줄어듭니다:
Optional<String> optionalString = getOptionalString();
optionalString.ifPresent(s -> myPlugin.doSomethingWithString(s));
또는:
getOptionalString().ifPresent(myPlugin::doSomethingWithString);
필터링
It is also possible to pass a Predicate
. Only values that this Predicate
returns true for will be retained. If
no value is present or the Predicate
returns false
, an empty Optional
will be returned. Since this method
returns an optional, it allows for chaining with other methods.
이렇던 코드가:
Optional<String> optionalString = getOptionalString();
if (optionalString.isPresent()) {
String someString = optionalString.get();
if (stringTester.isPalindromic(someString)) {
myPlugin.doSomethingWithString(someString);
}
}
이렇게 줄어듭니다:
getOptionalString()
.filter(stringTester::isPalindromic)
.ifPresent(myPlugin::doSomethingWithString);
참고
Neither this filtering function nor the mapping functions described below modify the instance they are called on.
Optional
s are always immutable.
Mapping
Another chainable operation is mapping the potential value to a different one. If no value is present, nothing will
change. But if it is present, the map()
method will return an Optional
of the value returned by the provided
Function
(or an empty Optional
if that return value is null
).
이렇던 코드가:
Optional<String> optionalString = getOptionalString();
if (optionalString.isPresent()) {
String someString = optionalString.toLowerCase();
myPlugin.doSomethingWithString(someString);
}
이렇게 줄어듭니다:
getOptionalString()
.map(s -> s.toLowerCase())
.ifPresent(myPlugin::doSomethingWithString);
팁
If your mapping function already returns an Optional
, use the flatMap()
method instead. It will behave just
like map()
, except that it expects the mapping function to already return an Optional
and therefore will
not wrap the result.
Combined Example
Imagine a plugin that allows each player to have a pet following. Assume the existance of the following methods:
petRegistry.getPetForPlayer()
accepting aPlayer
and returning anOptional<Pet>
. This method looks up the pet associated with a given playerpetHelper.canSpawn()
accepting aPet
and returning aboolean
. This method performs all the necessary checks to make sure the given pet may be spawned.petHelper.spawnPet()
accepting aPet
and returning nothing. This method will spawn a previously not spawned pet.
Now from somewhere (probably the execution of a command) we got the optionalPlayer
variable holding an
Optional<Player>
. We now want to obtain this players pet, check if the pet is spawned and if it is not spawned,
spawn it while performing the according checks if each and every Optional
actually contains a value. The code only
using the basic isPresent()
and get()
methods gets nasty really quickly.
if (optionalPlayer.isPresent()) {
Player player = optionalPlayer.get();
Optional<Pet> optionalPet = petRegistry.getPetForPlayer(player);
if (optionalPet.isPresent()) {
Pet pet = optionalPet.get();
if (petHelper.canSpawn(pet)) {
petHelper.spawnPet(pet);
}
}
}
However through use of Optional
s methods for conditional code execution, all those presence checks are hidden,
reducing the boilerplate and indentation levels and thus leaving the code much more readable:
optionalPlayer
.flatMap(petRegistry::getPetForPlayer)
.filter(petHelper::canSpawn)
.ifPresent(petHelper::spawnPet);
Creating Optionals
Should you choose to provide an API following the same contract of using Optional
instead of returning null
values, you will have to create Optional
s in order to be able to return them. This is done by calling one of the
three static constructor methods.
Optional.empty()
will always return an empty Optional
.
Optional.of()
will return an optional wrapping the given value and raise a NullPointerException
if the value was
null
.
Optional.ofNullable()
will return an empty Optional
if the supplied value is null
, otherwise it will return
an Optional
wrapping the value.