[Java] 자바 8 버전 이상의 자바문법 : Optional
Optional
: null이 들어있는 레퍼런스 변수의 멤버에 접근하려고 할 때 발생하는 예외인 NullPointerException 을 우아하게 해결하기 위해 등장.
package pkg2;
public class OptionalTest{
private static String getSomeString(){
return null; // 이 메서드는 항상 null 을 반환한다.
}
public static void main(String[] args) {
String isThisNull = getSomeString();
System.out.println(isThisNull.toUpperCase());
// if (null != isThisNull) {
// System.out.println(isThisNull.toUpperCase());
// }
}
}
이 경우 NullPointerException 이 발생한다.
따라서
어떤 메서드를 호출했다면 해당 메서드에서 if 문을 통해 null 체크를 해줘야 한다. (주석참고)
하지만
Optional 을 사용하면 if문으로 null을 체크하는 코드를 개선할 수 있다.
package pkg2;
import java.util.Optional;
public class OptionalTest2 {
private static Optional<String> getSomeString2() {
return Optional.empty(); // null 을 반환하는 것이 아니라 비어있는 Optional 객체를 반환한다.
}
// 반환된 문자열이 비어있지 않은 경우 (empty 가 아닌 경우) 에는 ifPresent 의 인자로 들어간 람다 표현식을 실행하고,
// 비어있는 경우에는 실행되지 않는다.
public static void main(String[] args) {
Optional<String> inThisNull = getSomeString2();
// ifPresent() : 값이 존재할 수도 있고 존재하지 않을 수도 있는 값을 감사는 래퍼 클래스. Optional 객체에 값이 존재할 때만 지정된 동작을 수행한다. 존재하지 않을 경우 아무 동작도 수행하지 않는다.
inThisNull.ifPresent(str -> System.out.println("str.toUpperCase() = " + str.toUpperCase()));
}
}
Optional 로 선언된 메서드에서 반환된 문자열이 비어있지 않은 경우에는 ifPresent() 의 인자로 들어간 람다 표현식을 실행하고
비어있는 경우에는 실행되지 않는다.
이렇게 작성할 경우 앞서 작성했던 주석처럼 if문을 사용하여 null 체크를 할 필요가 없어진다.
비어있는 Optional 이 아닌 값이 채워져 있는 Optoional 을 반환하려면
package pkg2;
import java.util.Optional;
public class OptionalTest3 {
private static Optional<String> getSomeString() {
// 매개변수로 전달된 값이 null 인지 확인한 뒤, null 이면 빈 Optional 객체를 생성, null 이 아니면 해당 값을 감싸는 Optional 객체 생성
return Optional.ofNullable("public static void");
}
public static void main(String[] args) {
Optional<String> isThisNull = getSomeString();
// null 이 아닌 경우 ifPresent 메서드 동작
isThisNull.ifPresent(str -> System.out.println(str.toUpperCase()));
}
}
이런식으로 작성해서 null 이 아닌 객체인 경우도 작성해보았다.
하지만 여기서 중요한 게 하나 있다.
나도 이번에 새로 알게 된 개념인데
Optional 을 사용하면서 주의해야할 안티 패턴이 있다고 한다.
안티패턴
: 디자인 패턴 중 하나로 비효율적이거나 생산적이지 않은 패턴을 의미한다.
안티 패턴은 코드의 가독성을 떨어뜨리거나 성능상 심각한 손실을 유지하므로 지양해야한다.
대표적인 안티 패턴 중 하나인 isPresent() 메서드를 마치 if문 처럼 잘못 사용한 사례라고 한다.
package pkg2;
import java.util.Optional;
public class OptionalTest3 {
private static Optional<String> getSomeString() {
// 매개변수로 전달된 값이 null 인지 확인한 뒤, null 이면 빈 Optional 객체를 생성, null 이 아니면 해당 값을 감사는 Optional 객체 생성
return Optional.ofNullable("public static void");
}
public static void main(String[] args) {
Optional<String> isThisNull = getSomeString();
// 안티 패턴
// isPresent() 를 if문 처럼 잘못 사용한 사례
if (isThisNull.isPresent()) {
System.out.println(isThisNull.get().toUpperCase());
}
}
}
해당 코드를 보면
1. Optional 객체에 값이 존재하는지 확인한 뒤
2. 존재할 경우 get() 메서드를 사용하여 값을 꺼낸 뒤 toUpperCase() 라는 메서드를 실행하게 된다.
하지만 이 로직은 isPresent라는 메서드를 사용하는 의미가 퇴색된 코드이다.
즉, 위의 주석처리되었던 if문으로 Null값을 체크하는 로직이랑 다를게 없다. 따라서 Optional 객체를 사용하는 보람이 없는 것이다.
👨🏻🏫 Chat GPT
Optional은 값이 존재할 수도 있고 존재하지 않을 수도 있는 상황을 다룰 때 사용됩니다. isPresent()를 호출하고 get()을 이용하여 값을 가져오는 것은 그 자체로 올바른 방법이지만, 이것은 Optional을 사용하는 주요 목적인 NullPointerException을 방지하고 코드의 안정성을 높이는 것과는 조금 다릅니다.
Optional을 사용하는 것의 목적 중 하나는 값을 사용하는 코드에서 명시적으로 null 체크를 하지 않도록 돕는 것입니다. 그러나 isPresent()를 호출한 후에 get()을 사용하는 것은 명시적인 null 체크를 수행하는 것과 유사합니다. 이 경우에는 값이 존재하지 않는 경우에도 예외가 발생할 수 있습니다.
따라서 Optional을 사용할 때는 isPresent() 메서드를 사용하여 값의 존재 여부를 확인하고, 값이 존재할 때에만 get()을 사용하는 것보다는 ifPresent()를 사용하여 값을 안전하게 처리하는 것이 더 바람직합니다. 이렇게 하면 코드의 가독성과 안전성을 높일 수 있습니다.
따라서, isPresent() 메서드로 if 문을 작성하는 방식 보다는
ifPresent() 메서드로 값이 존재할 때 수행할 로직을 작성하는게 더 좋은 코드라고 한다.
ifPresent() 메서드의 내용
public void ifPresent(Consumer<? super T> action) {
if (value != null) {
action.accept(value);
}
}
해당 메서드는 Optional 객체가 값을 포함하고 있는 경우에만 주어진 작업을 수행합니다. 메서드 시그니처에서 볼 수 있듯이, action이라는 이름의 Consumer를 매개변수로 받습니다. Consumer는 제네릭 타입 T를 받아들이는 함수형 인터페이스입니다. 따라서 action은 Optional 객체가 값이 존재하는 경우에 그 값을 소비하는 역할을 할 것입니다.
메서드 내부에서는 먼저 value라는 필드가 null이 아닌지를 확인합니다. Optional 객체가 값을 포함하고 있을 때는 value를 action.accept() 메서드에 전달하여 해당 작업을 수행합니다. 값이 존재하지 않을 때는 아무런 작업도 수행하지 않습니다.
마지막으로, 메서드 설명에서는 NullPointerException에 대한 예외 처리에 대한 정보가 포함되어 있습니다. 이것은 value가 null이 아닌 경우에만 action.accept()를 호출하기 때문에, 만약 action이 null이라면 NullPointerException이 발생할 수 있음을 나타냅니다. 따라서 주어진 action이 null이 아닌지를 미리 확인하거나, ifPresent() 메서드를 호출하기 전에 null 체크를 수행해야 합니다.