#12. Optional API (Null๊ฐ ์ฒ๋ฆฌ)
by JiwonDev์๋ฐ๋ ์ปดํ์ผ ์์ ์ ํ์ธ๊ฐ๋ฅํ null safe ์ฐ์ฐ์ ๋ฐ๋ก ์ ๊ณตํด์ฃผ์ง ์๋๋ค. ๊ทธ๋์ ๊ธฐ์กด์๋ ์กฐ๊ฑด๋ฌธ์ผ๋ก ํ์ธํ๊ฑฐ๋ ์์ธ๋ฅผ ๋์ ธ ์ฑ ์์ ์ฝ๋ ์ฌ์ฉ์์๊ฒ ๋๊ธฐ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์๋ค.
* ์ฐธ๊ณ ๋ก Optional์ Stream ์ ์ฉ ๋ฉ์๋๊ฐ ์๋๋ค. Optional.Stream() ํตํด ์คํธ๋ฆผ์ ์์ฑํ์ฌ(Java9๋ถํฐ ์ถ๊ฐ๋จ) ์ฌ์ฉํ๋ ๊ฒ์ด๋ค.
# NPE (NullPointerException)
์์๋ ์ธ๊ธํ์ง๋ง, ์๋ํ๋ ์๋ํ์ง์์๋ ๊ฐ์ฒด์ ๊ฐ์ ํ ๋นํด์ฃผ์ง์์ null์ด ๋ค์ด๊ฐ์ ์๊ธฐ๋ NPE ๋ฌธ์ ๋ ํํ ์ผ์ด๋๋ ์์ธ์ด๋ฉด์ ๊น๋ํ๊ฒ ํด๊ฒฐํ๊ธฐ๋ ์ด๋ ค์ด ์ฃผ์ ์๋ค. (* ์ด๋ฅผ ์กฐ๊ธ์ด๋๋ง ํด๊ฒฐํ๋ ค๊ณ 2020๋ Java14๋ถํฐ๋ instanceof ํค์๋์ NPE์ ๋ํ ์ค๋ฅ๋ฅผ ๋ ์์ธํ๊ฒ ๋ณด์ฌ์ฃผ๋ ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋์๋ค.)
// ํธ๋ํฐ ์ ์กฐ์ฌ์ ์ด๋ฆ์ ๋ฐํํ๋ค.
public String getPhoneManufacturerName(Person person) {
// `person` ์ด null ์ด๋ฉด?
// ์๋๋ฉด `getPhone()` ๋ฉ์๋์ ๊ฒฐ๊ณผ๊ฐ null ์ด๋ฉด?
return person.getPhone().getManufacturer().getName();
}
// ๋ง์ฝ.. ๋ด๊ฐ ๋ง๋ ๋ฉ์๋๊ฐ ์๋๋ผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ฒ๋ผ ํธ์ถํ๋ ์
์ฅ์ด๋ผ๋ฉด?
String manufacturerName = getPhoneManufacturerName(person);
// manufacturer ๊ฐ null ์ ์ฐธ์กฐํ๊ณ ์๋ ๊ฒฝ์ฐ NPE๊ฐ ๋ฐ์ํ ์ ์๋ค.
String lowerCaseName = manufacturerName.toLowerCase();
# NPE๋ฅผ ์๋ฐฉํ๋ ๊ณ ์ ์ ์ธ ๋ฐฉ๋ฒ
Java8 ์ด์ ์๋ ๋ค์๊ณผ ๊ฐ์ ๋ฐฉ์์ผ๋ก NPE๋ฅผ ์๋ฐฉํ์๋ค.
// ์ง๊ทธ๋ฌ์ด ์์ธ์ฒ๋ฆฌ ์ฝ๋
public String getPhoneManufacturerName(Person person) {
if (person != null) {
Phone phone = person.getPhone();
if (phone != null) {
Manufacturer manufacturer = phone.getManufacturer();
if (manufacturer != null) {
return manufacturer.getName();
}
}
}
return "Samsung";
}
๊ทธ๋ฌ๋ฉด์ ๊ฐ๋ฐ์๋ค์ด ์์ด๋์ด๋ฅผ ๋ธ ๊ฒ์ด, ๋ ๊ฐ์ฒด ํจํด(Null Object Pattern)์ด ์๋ค. ์ด ํจํด์ ํต์ฌ์ null ํค์๋๋ฅผ ์ฌ์ฉํ๋ ๋์ ์ด๋ฅผ ๋์ฒดํ ์ ์๋ null ๋ด๋น ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ์ฌ์ฉํ๋ ๊ฒ์ด์๋ค.
/* ๋ ๊ฐ์ฒด(Null Object) ํด๋์ค๋ฅผ ๋ฐ๋ก ๊ตฌํ. */
class NullMessenger implements Messenger {
@Override
public void send(String msg) {
// ๊ทธ๋ฅ ๋น ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค. ์๋ฌด ๋์๋ ํ์ง ์๋๋ค.
}
}
/* null ๊ฐ์ฒด๊ฐ ์๋ ํด๋์ค๋ค. */
interface Messenger {
void send(String msg);
}
class Kakao implements Messenger {
@Override
public void send(String msg) {...}
}
class Line implements Messenger {
@Override
public void send(String msg) {...}
}
class MessengerFactory {
public static Messenger getMessenger(String name) {
if ( ... ){
return ... // ํน์ ์กฐ๊ฑด์ ๋ง๋ Messenger ๊ตฌํ์ฒด ํด๋์ค์ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค.
}
// ๊ฒฐ๊ณผ๊ฐ ์๋ ๊ฒฝ์ฐ `null`์ ๋ฐํํ์ง ์๊ณ `NullMessenger`๋ฅผ ๋ฐํํ๋ค.
return new NullMessenger();
}
}
// ์ค์ ์ฌ์ฉํ๋ ์์
Messenger messenger = MessengerFactory.getMessenger("KakaoTalk");
messenger.send("Hello");
์ด๋ ๊ฒ ํจ์ผ๋ก์ NPE ์๋ฌ๋ ํผํ ์ ์์์ง๋ง, ๋ชจ๋ ๊ฐ๋ฐ์๊ฐ ํด๋น ๊ฐ์ฒด์ 'Null ์ ์ฉ ํด๋์ค'๋ฅผ ์๊ณ ์์ด์ผ ์ฌ์ฉํ ์ ์๋ค๋ ๋จ์ ์ด ์์๋ค. ๋ง์ฝ ์ธํฐํ์ด์ค์ ๋ด์ฉ์ด ์ถ๊ฐ๋๊ฑฐ๋ ์ ํํ ํด๋์ค ๊ตฌํ์ ์ ๋ชจ๋ฅธ๋ค๋ฉด ๊ธฐ์กด์ if(..null) ์ค๋ณต์ฒ๋ฆฌ๋ณด๋ค ํจ์ฌ ๋ ๋ณต์กํ ๊ฒ์ฌ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ ์ง๋ ๋ชจ๋ฅธ๋ค.
# Optional Class์ ๋ฑ์ฅ
์ด๋ฌํ ๋ฌธ์ ์ ๋ค์ ํด๊ฒฐํ๊ธฐ์ํด Java8 ๋ถํฐ ๊ณต์์ ์ผ๋ก ์ ๊ณตํ๋ Null ์ ์ฉ ๊ฐ์ฒด๊ฐ Optional Class์ด๋ค. ์์์ ๋งํ null-object pattern์ ์๋ฐ์์ ๊ณต์์ ์ผ๋ก ๊ตฌํํ๊ณ ํ์คํ ํ ๊ฒ์ด๋ค.
๋ค๋ฅธ ํด๊ฒฐ์ฑ ์ ์์๋์?
Java1.7์ null-safe ์ฒ๋ฆฌ๋ฅผ ์ํ ์ฐ์ฐ์ ๋์ ํ๋ ค๊ณ ์๋ํ์๋ค. ( Project Coin ) ํ์ง๋ง ์๋ก์ด ์์ ์ฅ์น๋ฅผ ์ ์ฉํ๋ ์ด์ ๋ณด๋ค ๋์ ํ์ ๋ JVM์ด๋ ์ฝ๋๋ฅผ ์์ฑํจ์ ์์ด ์ถ๊ฐํด์ผํ๋ ๋น์ฉ์ด๋ ํด๊ฒฐํด์ผํ ๋ฌธ์ ์ ๋ค์ด ์์๊ธฐ์ ๊ฒฐ๊ณผ์ ์ผ๋ก ์น์ธ ๋์ง๋ ์์๋ค.
// Java7์ Project Coin - ์๋น์ค ์ฐ์ฐ์๊ฐ ์ ์ฉ๋์๋ค๋ฉด ์ด๋ฐ ์ฝ๋๊ฐ ๊ฐ๋ฅํ์ง ์์์๊น?
public String getPhoneManufacturerName(Person person) {
// ์ฒด์ด๋ ์ค์์ null์ด ์๋ค๋ฉด, "Samsung"์ ๋ฐํํ๋ค.
return person?.getPhone()?.getManufacturer()?.getName() : "Samsung";
}
# Optional ์์ฑ
์ ๋๋ฆญ ํ์ ์ผ๋ก ์ ์ธํ๊ณ , ์ ์ฉ Static Factory Method๋ฅผ ์ด์ฉํ์ฌ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
Optional<Person> person; // Person ํ์
์ `Optional` ๋ณ์
Optional<Phone> phone; // phone ํ์
์ `Optional` ๋ณ์
// ์ฌ์ฉ์์
phone1 = Optional.empty();
phone2 = Optional.ofNullable(phone);
# Optional.empty()
๋น์ด์๋ Optional ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค. ๋ด๋ถ์ ์ผ๋ก๋ ์ฑ๊ธํด ์ธ์คํด์ค๋ฅผ ๋ฐํํ๊ฒ ๋๋ค.
// ๋ฉ์๋ ์๊ทธ๋์ฒ
// `Optional` ํด๋์ค์ ์ ์ ๋ฉค๋ฒ๋ก ์ ์ธ๋์ด ์ฑ๊ธํค์ ๊ตฌํํจ.
private static final Optional<?> EMPTY = new Optional<>();
public static<T> Optional<T> empty() {
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
// ์์
Optional<String> emptyOpt = Optional.empty();
# Optional.of ( T value )
๊ฐ์ด null ์ด๋ผ๋ฉด NPE ์์ธ๋ฅผ ๋์ง๋ค. ๋ง์ฝ ํด๋น ๊ฐ์ฒด์ ๊ฐ์ด ๋ฐ๋์ ์์ด์ผ ํ๋ ๊ฒฝ์ฐ ์ฌ์ฉํ๋ฉด ๋๋ค.
// ๋ฉ์๋ ์๊ทธ๋์ฒ
public static <T> Optional<T> of(T value);
// ์์
Optional<String> opt = Optional.of("result");
# Optional.ofNullable( T value )
๊ฐ์ด null์ผ ์๋ ์๋ T ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค. ๋ง์ฝ ๊ฐ์ด null์ด๋ผ๋ฉด ๋น์ด์๋ Optional ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค.
// ๋ฉ์๋ ์๊ทธ๋์ฒ
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
// ์์
Optional<String> opt = Optional.ofNullable(null);
# Optional ์ค๊ฐ ์ฐ์ฐ
# Filter
// ๋ฉ์๋ ์๊ทธ๋์ฒ
public Optional<T> filter(Predicate<? super T> predicate);
// ๊ฐ์ด ๋น์ด์๋ค๋ฉด(null์ด๋ผ๋ฉด) NPE ์์ธ๋ฅผ ๋ฐ์์ํจ๋ค.
Optional.of("True").filter((val) -> "True".eqauls(val)).orElse("NO DATA"); // "True"
Optional.of("False").filter((val) -> "True".eqauls(val)).orElse("NO DATA"); // "NO DATA"
// ์กฐ๊ฑด์ ๋ง๋ Optional(=person) ๊ฐ์ฒด๋ฅผ ๋ฐ๋๋ค. ๋น์ด์๋ค๋ฉด empty Optional์ ๋ฐํํ๋ค.
Optional.ofNullable(person).filter(p -> p.getName().equals("Kimtaeng"));
# map
mapper ํจ์๋ฅผ ํตํด ์ ๋ ฅ ๊ฐ์ ์์ ํ๋ค.
// ๋ฉ์๋ ์๊ทธ๋์ฒ
public<U> Optional<U> map(Function<? super T, ? extends U> mapper);
// ์์
Integer test = Optional.of("1").map(Integer::valueOf).orElseThrow(NoSuchElementException::new); // string to integer
// Optional ๊ฐ์ฒด(=person)๊ฐ ๊ฐ์ด ์๋ค๋ฉด map์ ์คํํ๊ณ , ๋น์ด์๋ค๋ฉด ์๋ฌด๋ฐ ์ฐ์ฐ์ ํ์ง ์๋๋ค.
Optional.ofNullable(person).map(p -> p.getName());
# flatMap
์ค์ฒฉ๋ Optional ๊ตฌ์กฐ๋ฅผ ํ ๋จ๊ณ ์์ ๊ณ ๋จ์ผ ์์๋ก ๋ง๋ค์ด์ค๋ค. ์ดํด๊ฐ ๋์ง์๋๋ค๋ฉด ์ด ๋งํฌ๋ฅผ ์ฐธ์กฐํ์.
// ๋ฉ์๋ ์๊ทธ๋์ฒ
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper);
// ์์
String result = Optional.of("result")
.flatMap((val) -> Optional.of("good"))
.get();
System.out.println(result); // print 'good'
class Person {
private Optional<Phone> phone;
// getter, setter ์๋ต
}
/* person ๊ฐ์ฒด๋ฅผ ์ฃผ๋ฉด ํด๋น ๊ฐ์ฒด๊ฐ ๊ฐ์ง๊ณ ์๋ phone์ ๋ฐํํจ.*/
// ์๋ํ์ง ์๋ ์ฝ๋. Optional ์ผ๋ก Optional<phone>๋ฅผ ๊ฐ์ธ๊ณ ์๋ค.
public Optional<Phone> testMap(Person person) {
// Optional<Optional<Phone>> ํ์
์ด๊ธฐ ๋๋ฌธ์ ์ปดํ์ผ ์ค๋ฅ ๋ฐ์
return Optional.ofNullable(person)
.map(Person::getPhone);
}
// ์๋ํ๋ ์ฝ๋. flatMap์ ์ด์ฉํ์ฌ Optional<> ์ค์ฒฉ์ ํ ๋จ๊ณ ์ ๊ฑฐํด์ค๋ค.
// Optional<Optional<phone>>์ Optional<Phone>์ผ๋ก ์ ๊ทผํ๋ค. Stream flatmap๊ณผ ๋์๋ฐฉ์์ด ์ ์ฌํ๋ค.
public Optional<Phone> testFlatMap(Person person) {
return Optional.ofNullable(person)
.flatMap(Person::getPhone);
}
# or (null ๋์ฒด๊ฐ ์ง์ )
๊ฐ์ด ์กด์ฌํ๋ ๊ฒฝ์ฐ Optional ๊ฐ์ฒด๋ฅผ, ์๋ ๊ฒฝ์ฐ ๋น์ด์๋ Optional์ด ์๋๋ผ or(~)์ ๋ฑ๋กํ Optional ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค.
Optional.ofNullable("Hi")
.filter(value -> value.length() > 3) // `filter` ์กฐ๊ฑด์ ๊ฑธ๋ฌ์ง. ๋น Optional ๋ฐํ
.or(() -> Optional.of("Hello")) // ๊ฐ์ด ์์ผ๋ฏ๋ก ๋์ฒด Optional[Hello] ๋ฐํ
# Stream์ผ๋ก ๋ณํ
์ค๊ฐ์ ๋น์ด์๋ Optional ๊ฐ์ฒด๊ฐ ๋ฐํ๋๋ ๊ฒฝ์ฐ, Stream.empty()๊ฐ ๋ฐํ๋๋ฉฐ ํจ์ ์ฒด์ด๋์ด ๋ ์ด์ ์งํ๋์ง ์๋์ ์ ์ ์ํ์. ์คํธ๋ฆผ ์ฐ์ฐ์ด ๋ฉ์ถ๋ค.
public void testOptionalWithStream() {
// ์ฐธ๊ณ ๋ก List.of๋ ์๋ฐ 9์์ ์ถ๊ฐ๋ ํฉํฐ๋ฆฌ ๋ฉ์๋. ํํ์ ๋ง๋ ๋ค.
// Unmodifiable list(์์ ๋ถ๊ฐ๋ฅํ ๋ฆฌ์คํธ)๋ฅผ ๋ฐํํ๋ฏ๋ก ์ฃผ์
List.of(4, 3, 2, 5)
.stream() // stream์ผ๋ก ๋ณต์ฌํด์ ์ฌ์ฉ๊ฐ๋ฅํ๋ค.
.map(value -> value > 2 ? Optional.of(value) : Optional.empty())
.flatMap(Optional::stream)
.forEach(System.out::println);
}
๋ฌผ๋ก ๋๋ค์๊ณผ ๋ฉ์๋ ๋ ํผ๋ฐ์ค๋ ์ ํ์ฌํญ์ด๋ค. ๊ผญ ํ์ํ๋ค๋ฉด ์ต๋ช ํด๋์ค๋ฅผ ์ฌ์ฉํด๋ ์๊ด์๋ค.
// ์์ ์์ ํ ๊ฐ์ ์ฝ๋์ด๋ค. ์ดํด๋ฅผ ๋๊ธฐ์ํด ์ค๊ฐ์ค๊ฐ println์ ์ถ๊ฐํ์๋ค.
public void testOptionalWithStream() {
List.of(4, 3, 2, 5)
.stream()
.map(value -> {
return value > 2 ? Optional.of(value) : Optional.empty();
})
.peek(v -> {
// ๋์ด์ค๋ ๊ฐ์ ์ถ๋ ฅํ๊ธฐ ์ํจ
System.out.println("peek: " + v);
})
.flatMap(new Function<Optional<? extends Object>, Stream<?>>() {
@Override
public Stream<?> apply(Optional<?> o) {
System.out.println("flatMap: " + o);
return o.stream();
}
})
.forEach(v -> {
System.out.println("forEach: " + v);
});
}
# Optional ๊ฐ ๊บผ๋ด์ค๊ธฐ (์ข ๋จ ์ฒ๋ฆฌ)
'๐ฑBackend > Java' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
#14. JSP์ ์น ํ๋ก๊ทธ๋๋ฐ (0) | 2021.07.16 |
---|---|
#13 JSP (Java Server Pages) (0) | 2021.07.14 |
#11 Stream API (0) | 2021.07.12 |
#10 Modern Java (Java8~) (0) | 2021.07.12 |
#8 Java - 3 (์ฐ๋ ๋, IO, ๋คํธ์ํฌ) (0) | 2021.07.09 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev