아무리 봐도 Optional이 이해가 안 됐고 와닿지가 않아서 하루종일 이것만 공부해 보기로 했다.
왜 이해를 못했냐면
*NPE를 예방하기 위해서라면, 결국 에러 발생되게 만드는 용도아닌가?란 생각이 들었고,
에러를 임의로 발생시키도록 하는 예외처리가 따로 있는데.
그리고 null이던 말던 왜 신경을 써야 하는지에 대해 불만이 있었다.
NPE(nullPointerException)
아무 객체도 가리키지 않는 참조에서 메서드나 필드에 접근하려 할 때 발생하는 자바 런타임 에러.
왜 발생하는데?=> 자바는 null이라는 특별 값에 대해 아무것도 없다라고 표시하는데,
그 상태에서 length(), toUpperCase(), .charAt(0)같은 메서드를 호출하면
자바가 어떻게 실행하지?하고 터뜨리는 에러가 NPE.
그러나 Java에서는 null을 그대로 냅두게 되면, 여러문제를 일으킬 수 있다.
Java에서 null 참조를 그대로 두면 어디서 NPE가 터질지 모르는 문제와 중첩된 if (x != null) 처리의 복잡함을 야기한다. Optional은 ‘값 없음’을 안전하게 캡슐화해서, NPE 없이 깔끔하게 널 체크 흐름을 작성할 수 있게 도와준다.
위 내용을 정리해보면
주의사항
위 내용에 대해 설명하고자 한다.
1) 절대 get() 만 믿고 쓰지 말 것 (isPresent()/ orElse.. 함께 사용 권장)
if(opt.isPresent()){
String v=opt.get();
}
// 값이 있으면 그대로, 없으면 기본값 반환
String v=opt.orElse("기본");
String v=opt.orElseGet(()->"기본");
요약하자면, get() 단독 사용은 예외 위험이 크니 항상 "값 있음" 여부를 확인하거나 isPresent() 아니면 기본값을 지정해주는 orElse/ orElseGet 방식을 쓰라는 말이다.
2) 필드나 파라미터 타입으로 Optional 남발은 피하고 주로 메서드 반환용으로 사용 할 것.
// 권장하지 않는 예시코드
public class User{
private Optional<String> middleName;
public void setMiddleNaem(Optional<String> m){...}
}
//권장되는 예시 코드
public Optional<User> findUserByEmail(String email){...}
요약하자면, Optional을 필드, 파라미터로 쓰면 오히려 부작용이 많으니 메서드 리턴으로만 쓰고 *내부 구현이나 호출 시에는 일반 Null체크를 쓰는 걸 추천한다.
*내부 구현이나 호출 시에는 일반 Null체크를 쓰는 걸 추천,
일반 null체크란? 참조형 변수가 null인지 if문으로 먼저 확인한 뒤에야 안전하게 사용하는 방식이다.
예를들면 필드처리, 메서드 파라미터, 호출.
즉, 여기서 내부 구현이란, 클래스 안에서 필드나 파라미터에 Optioanl을 쓰지않고
if(param != null)/ if(field != null)으로 단순히 널 여부만 체크하는 방식을 말한다.
호출 시에만 Optional API를 사용해 주면, 인터페이스 설계와 구현이 깔끔하게 분리된다.
잠시 필드처리, 메서드 파라미터, 호출에 대해 설명해보자면 아래와 같다.
일반 Null 체크 1)필드 처리 예시
public class User{
//Optional을 필드로 쓰면 직렬화·테스트·가독성이 모두 불편해짐
private Optional<String> middleName;
public User(Optional<String> middleName){
this.middleName=middleName;
}
public Optional<String> getMiddleName(){
return middleName;
}
}
public class User{
private String middleName;
public User(String middleName){
this.middleName=middleName != null?middleName:"";
}
public String getMiddleName(){
return middleName != null && !middleName.isEmpty()
?middleName
:"정보 없음";
}
}
일반 Null 체크 2) 메서드 파라미터 예시
잘못된 예(Optional 파라미터)
public void updateName(Optional<String> newNameOpt){
newNameOpt.ifPresent(n->this.name=n);
}
권장 예(null체크 파라미터)
public void updateName(String newName){
//호출 시 null인지 직접 검사
if(newName !=null && !newName.isBlack()){
this.name=newName;
}//else: 그대로 두거나 기본 처리.
}
일반 Null 체크 3) 호출 시 예시
// Optional 반환 메서드
public Optional<User> findUserById(String id){...}
//호출할 때
Optional<User> userOpt=repo.findUserById(id);
// ->Optional API로 안전하게 처리
if(userOtp.isPresent()){
User u=userOpt.get();
//내부적으로는 null 체크 없이 바로 사용 가능
System.out.println(u.getName());
}else{
System.out.println("사용자를 찾을 수 없습니다.");
}
일반 NULL체크에 대한 설명은 여기서 마무리하고 본론으로 돌아가자.
3) of() vs ofNullable() 처리를 명확히 인지 할 것
요약하자면, null이 아님이 확실할 때 of() 사용, null 가능성이 조금이라도 있으면 ofNullable() 사용해주면 된다.
따라서
문제 풀다가 추가적으로, 이해할 필요가 있겠다란 코드가 보였다.
Optional<String> optEmpty = Optional.ofNullable(null);
// orElse는 기본값을 즉시 호출합니다.
String a = optEmpty.orElse(defaultValue());
// "기본값 생성"이 즉시 출력되고 "DEF" 반환
// orElseGet은 기본값을 필요할 때만 호출합니다.
String b = optEmpty.orElseGet(() -> defaultValue());
// "기본값 생성"이 "DEF" 반환 시점에 출력
예제코드
// 1) of() – null이 절대 아닐 때만
Optional<String> optA = Optional.of("A값");
// get단독사용한 이유: null 아님이 100%이기 때문.
System.out.println("optA: " + optA.get()); // safe, 값 있음
// 2) ofNullable() – null 가능
String maybeB = null;
Optional<String> optB = Optional.ofNullable(maybeB);
// 3) empty() – 빈 Optional
Optional<String> optEmpty = Optional.empty();
System.out.println("-----------");
// 4) isPresent() + get() => null 가능성이 조금이라도 있기 때문에
//isPresent로 먼저 확인 후 get씀
if (optA.isPresent()) {
System.out.println("optA 존재: " + optA.get());
}
if (!optB.isPresent()) {
System.out.println("optB 비어있음");
}
System.out.println("-----------");
// 5) ifPresent() – 값 있을 때만 실행
optA.ifPresent(v -> System.out.println("ifPresent optA: " + v));
optB.ifPresent(v -> System.out.println("ifPresent optB: " + v)); // 실행 안 됨
System.out.println("-----------");
// 6) orElse() – 값 없으면 기본값
System.out.println("optB orElse: " + optB.orElse("기본B"));
// 7) orElseGet() – 값 없을 때만 supplier 실행
System.out.println("optEmpty orElseGet: " +
optEmpty.orElseGet(() -> {
System.out.println(" supplier 실행!");
return "Lazy기본";
})
);
System.out.println("-----------");
// 8) filter() + map()
Optional<String> optNum = Optional.of("123");
Optional<Integer> optLen = optNum
.filter(s -> s.length() > 2) // 길이 > 2만 통과
//map(s -> s.length())와 map(String::length); 일치 함
.map(String::length); // 길이를 Integer로 매핑
System.out.println("filter+map 결과: " + optLen.orElse(0));
// 출력결과
optA: A값
-----------
optA 존재: A값
optB 비어있음
-----------
ifPresent optA: A값
-----------
optB orElse: 기본B
supplier 실행!
optEmpty orElseGet: Lazy기본
-----------
filter+map 결과: 3
옵셔널 관련 문제
// 문제 4-1: Optional 입력값 처리
// 1) Scanner로 사용자로부터 문자열 input을 한 줄 입력받으세요.
// 2) Optional.ofNullable(input)으로 Optional<String> opt을 생성하세요.
// 3) opt.ifPresentOrElse를 사용해
// 값이 있으면 "입력값: {값}"을, 없으면 "값이 없습니다."를 출력하세요.
import java.util.Optional;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 여기에 코드 작성하세요
sc.close();
}
}
// 문제 4-2: Optional.of() 예외 확인
// 1) Optional<String> optA = Optional.of("ABC"); 선언
// 2) try-catch 구문 안에서 Optional.of(null)을 호출해 보세요.
// 3) catch(NullPointerException e) 블록에서
// "of(null) 호출 시 예외 발생!"을 출력하세요.
import java.util.Optional;
public class Main {
public static void main(String[] args) {
// 여기에 코드 작성하세요
}
}
// 문제 4-3: orElse vs orElseGet 비교
// 1) Optional<String> optEmpty = Optional.ofNullable(null); 선언
// 2) String a = optEmpty.orElse(defaultValue());
// 3) String b = optEmpty.orElseGet(() -> defaultValue());
// 4) defaultValue() 메서드를 정의하고,
// 내부에서 "기본값 생성"을 출력한 뒤 "DEF"를 반환하세요.
// 5) a와 b를 차례로 출력해 보세요.
import java.util.Optional;
public class Main {
public static void main(String[] args) {
// 여기에 코드 작성하세요
}
// 여기에 defaultValue() 메서드 작성
}
//[TIP]
// orElse(): 객체에 값이 없을때 지정한 기본값을 즉시 반환
//여기서 defaultValue()메서드는 orElse()가 호출될 때 즉시 실행됨
//orElseGet() : 객체에 값이 없을 때 람다식 사용하여 기본값을 계산하는 방식
//defaultValue()는 기본값을 반환하는 메서드,값이 없을 때 대체할 기본값을 생성하는 메서드
//람다식을 사용하면, orElse()와는 달리 값이 없을 때만 defaultValue() 메서드를 실행하게 됩니다.
// 그렇기 때문에 값이 있을 경우에는 defaultValue() 메서드가 호출되지 않습니다.
// 문제 4-4: Optional 필터 및 매핑
// 1) Optional<String> emailOpt = Optional.ofNullable("user@example.com"); 선언
// 2) filter(e -> e.contains("@"))로 검증 후
// 3) map(e -> e.substring(e.indexOf("@") + 1))로 도메인만 추출하세요.
// 4) orElse("도메인 없음")으로 최종 결과를 출력하세요.
import java.util.Optional;
public class Main {
public static void main(String[] args) {
// 여기에 코드 작성하세요
}
}
// 문제 4-5: 중첩 Optional 처리 (flatMap)
// 아래 Address, User 클래스를 정의하고
// 1) User user = new User("홍길동", null); 생성
// 2) Optional.ofNullable(user)
// .flatMap(u -> Optional.ofNullable(u.addr))
// .map(a -> a.city)
// .orElse("주소 정보 없음")
// 를 이용해 결과를 출력하세요.
import java.util.Optional;
class Address {
String city;
Address(String city) { this.city = city; }
}
class User {
String name;
Address addr;
User(String name, Address addr) {
this.name = name;
this.addr = addr;
}
}
public class Main {
public static void main(String[] args) {
// 여기에 코드 작성하세요
}
}