개발자 취업준비/java

Optional이란?

naspeciallist 2025. 3. 25. 00:37

 

 

1. Optional이란?


 

Optional은 프로그래밍에서 값이 존재할수도 있고 존재하지 않을 수도 있는 상황을 표현하기 위해 사용하는 개념입니다. 특히 Null값으로 인한 오류(NullPointerException,NPE)를 방지하고 안전하게 값을 처리하기 위해 사용합니다.

 

[NPE(NullPointerExcetion)] 이란?

개발을 하다보면 가장 많이 발생하는 예외 중 하나가 바로 NPE(NullPointerException) 입니다. NPE를 피할려면 null 여부를 검사해야 하는데 null검사를 하기 위해서 구성한 코드는 너무 복잡하고 변수가 많다는 단점이 있습니다. 그래서 null 대신 초기값을 사용하길 권장하기도 합니다.

public class UserService {
    public String getUserName(User user) {
        if (user != null) {
            String name = user.getName();
            if (name != null) {
                return name.toUpperCase();
            }
        }
        return "UNKNOWN";
    }
}

class User {
    private String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

 

예를들어 위처럼 User를 확인하는 코드가 있습니다. User가 없으면 Null을 반환해 NPE가 발생 할 수 있기 때문에 if문을 이용하여 Null을 확인하게 됩니다. 하지만 위처럼 if문을 이용하여 반복해서 체크하는 경우 코드가 길어져 가독성이 떨어지고 실수로 NPE가 발생 할 수 있다는 단점이 있습니다. 

 

Null값으로 인한 오류를 더 안전하고 깔끔하게 처리하기 위해서 자바8부터 Optional이라는 것이 도입되게 되었습니다.  Optional을 통해 이제 더 이상 null을 안전하게 다룰수 있게 되었습니다.

 

[Optional이란?]

위에서 설명했던 것처럼 java 8부터 Optional<T> 클래스를 사용해 NPE를 방지할 수 있도록 하였습니다. Optional<T>는 null이 올 수 있는 값을 감싸는 Wrapper클래스로 참조하더라도 NPE가 발생하지 않도록 도와줍니다. 이를 활용하면 Null 참조로 인한 NPE 발생을 방지하고 보다 깔끔하게 코드를 구성 할 수 있습니다.

 

위에 직접if문을 이용하여 Null을 검사하였던 방식에서 Optional을 이용하여 Null값을 참조하는 방식으로 바꿔보겠습니다.

import java.util.Optional;

public class UserService {
    public String getUserName(User user) {
        return Optional.ofNullable(user)
                .map(User::getName)
                .map(String::toUpperCase)
                .orElse("UNKNOWN");
    }
}

 

위처럼 Optional을 이용하면 훨씬 깔끔하고 가독성 있게 코드를 구성 할 수 있습니다.

 

다른 예시를 하나 더 들어보겠습니다.

 

User의 주소를 가져오는데 Null값이면 "Unknown"을 반환받도록 구성하겠습니다.

public Address getUserAddress(User user) {
    if (user != null) {
        Address address = user.getAddress();
        if (address != null) {
            return address;
        }
    }
    return new Address("Unknown");
}

 

기존에 Null을 체크하는 방식입니다.

 

그리고 Optional을 이용한 방식입니다.

import java.util.Optional;

public class UserService {
    public Address getUserAddress(User user) {
        return Optional.ofNullable(user)
                .map(User::getAddress)
                .orElse(new Address("Unknown"));
    }
}

 

예제로 봤을 땐 크게 차이가 없어 보일 수도 있지만 만약에 체크해야 할 항목이 많아지고 코드가 복잡해지면 Optional을 사용하여 훨씬 더 깔끔하고 간결한 코드를 구성할 수 있습니다. 

 

정리하자면 Optional은 null 또는 값을 감싸서 NPE(NullPointException) 로부터 부담을 줄이기 위해 등장한 Wrapper 클래스입니다. 이 값은 있을 수도 있고 없을 수도 있는 값을 의미하며 null값을 직접 다루지 않기 위해서 사용합니다. Optional은 값을 Wrapping하고 사용할 때는 다시 꺼내서(Unwrapping) 사용해야 합니다. 만약 값이 null이라면 대체 값을 가져오거나 대체 함수를 호출하도록 구현하게 되는데 이 과정 자체가 추가적인 연산과 비용을 발생 시킵니다. 따라서 메소드의 반환값이 null이 아닌게 확실하다면 Optional을 굳이 사용할 필요는 없습니다.

 

 

2. Optional의 orElse와 orElseGet


 

위에서 설명했던 것처럼 Optional은 값이 있을 수도 있고 없을 수도 있는 컨테이너 입니다.

 

따라서 orElse와 orElseGet을 이용하여 Optional안에 값이 없을 때 대신 무엇을 반환할 지 정의 할 수 있습니다.

 

OrElse나 orElseGet을 이용하면 내부에 값이 없어도 정해둔 기본값 또는 기본 생성 로직을 실행해 반환합니다.

 

[orElse와 orElseGet의 차이점]

orElse는 파라미터로 값을 받습니다.

orElseGet은 파라미터로 함수형 인터페이스(함수)를 받습니다.

 

또한 실행시점에 관한 차이도 있습니다. orElse는 Optional은 값과 관계없이 미리 전달한 인자를 항상 실행합니다.

반대로 orElseGet은 optional값이 없을 때에만 전달받은 함수가 실행됩니다.

 

예시 코드를 작성해보겠습니다.

public void findUserNameOrElse() {
    String userName = "Empty";
    String result = Optional.ofNullable(userName)
    	.orElse(getUserName());
        
    System.out.println(result);
}

public void findUserNameOrElseGet() {
    String userName = "Empty";
    String result = Optional.ofNullable(userName)
    	.orElseGet(this::getUserName);
        
    System.out.println(result);
}

private String getUserName() {
    System.out.println("getUserName() Called");
    return "홍길동";
}

 

첫번째 함수는 값이 비어 있을 때 orElse를 호출하도록 되어있고 두번째 함수는 orElseGet을 호출하도록 되어있습니다. 

위의 함수를 실행해보면 다음과 같은 결과값이 나옵니다.

// 1. orElse인 경우
getUserName() Called
Empty

// 2. orElseGet인 경우
Empty

 

먼저 orElse 인 경우에는 다음과 같은 순서로 처리가 됩니다.

1. Optional.ofNullable를 이용하여 userName을 Optional로 감싸  userName 값을 가지는 객체를 생성합니다.

2. orElse를 통해 getUserName()가 실행된 후 반환값이 orElse 파라미터로 전달합니다.

3. OrElse가 실행되면서 감싼 userName의 값이  Null이 아니므로 "EMPTY"를 그대로 가지게 됩니다.

 

Optional.orElse()는 괄호 안에 값 자체를 넣어줘야 합니다. 하지만 그 값이 메서드 호출 결과가 GetUserName()이라는 메서드의 호출 결과 입니다. 이렇게 되면 그 값을 넘겨주기 위해서 메서드가 무조건 먼저 실행되어야 합니다..
요약하자면 Optional 안에 값이 있든 없든, orElse 안에 들어간 메서드는 일단 먼저 실행되고 그 결과가 넘어가게 되는 것입니다. 하지만 orElseGet()을 이용할 시 다른 방식으로 동작하게 됩니다.

 

1. Optional.ofNullable를 이용하여 userName을 Optional로 감싸  userName 값을 가지는 객체를 생성합니다.

2. getUserName() 함수 자체를 orElseGet 파라미터로 전달합니다.

3. orElseGet이 호출되면서 userName이 Null이 아니므로 "Empty"를 그대로 가지며 getUserName가 실행되지 않습니다.

 

 

orElse을 사용하는 경우 값이 항상 계산되기 때문에 큰부담이 발생하게 됩니다. 따라서 대체 값이 단순한 값(상수)나 가벼운 연산 결과인 경우에만 사용해야 합니다. 반대로 orElseGet은 Optional 값이 없을 때만 실행되므로 성능상 더 효율적입니다. 따라서 대체 값 생성이 무겁거나 함수 호출 복잡한 객체 생성이 필요한 경우 사용해야 합니다. 또한 Optional 객체가 비어있을 가능성이 매우 높고 기본값 생성 비용이 큰 경우도 필요할 때만 실행시켜 불필요한 리소스 낭비를 방지하기 위해 orElseGet을 사용하는게 바람직합니다. 

 

[orElse와 orElseGet 정리]

정리하자면 orElse는 항상 값 또는 메서드를 미리 계산하고 orElseGet은 값이 없을 때에만 필요한 함수가 실행된다는 점이 가장 큰 차이 입니다. 따라서 무거운 객체 생성 메서드나 DB호출, API 호출등의 외부 연산, 시간이 오래 걸리는 복잡한 계산 같은 경우 orElse를 사용할 시 리소스 낭비가 크게 발생할 수 있기 때문에 orElseGet을 사용하는걸 권장드립니다.