개발/Java

Java 기초부터 다시 - Wrapper class Optional<T>

삽쟁이 2024. 1. 31. 09:58
반응형
public final class Optional<T> {

    private final T value;
    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }
    private Optional(T value) {
        this.value = value;
    }
    public static <T> Optional<T> of(T value) {
        return new Optional<>(Objects.requireNonNull(value));
    }
    @SuppressWarnings("unchecked")
    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? (Optional<T>) EMPTY
                             : new Optional<>(value);
    }
    ......

Optional Class는 모든 개발자의 친구이며 원수인 NULL을 상대하기 위해 자바8 이후에 개발자들에게 주어진 무기쯤으로 생각하면 편하다.

 

기본적으로 Optional은 Wrapper Class로써, 모든 형태의 변수를 감싸고, 주어진 매소드들을 통해 Null 처리를 할 수 있도록 도와준다. 주어진 기능은 Stream과 어느정도 유사하여, 리스트가 1개인 Stream정도로 생각해도 좋다.

 

뭔지 모르겠을땐 일단 사용해보면서 알아보자.

 

예제 클래스

public class Department { //부서 클래스
    private String departmentName; // 부서명
    private int departmentNumber; // 부서번호
    private String responsibilities; // 담당업무
}

public class Employee { // 사원 클래스
    private Department department; // 부서
    private String employeeName; // 사원명
    private int employeeNumber; // 사원번호
}

public class Stock { // 물품 클래스
    private Employee usingEmployee; // 사용사원
    private int stockNumber; // 물품번호
    private String stockName; // 물품명
}

 

생성 (1) <T> Optional<T> of

public static <T> Optional<T> of(T value) {
        return new Optional<>(Objects.requireNonNull(value));
}

가장 기본적인 Optional 객체 생성 방법인 of이다.

사용시 주의해야 할 점은 바로 of는 NULL을 인자로 허용하지 않는 다는 점이다. 만약 of를 통해 optional 객체 생성시 null 일경우, NullPointException이 발생한다.

Department itDepartment = new Department("IT Department", "System maintenance", 101);

Optional<String> dptName = Optional.of(itDepartment.getDepartmentName());

System.out.println(dptName.get()); // RESULT String "IT Department"

/****************************************************************/

Department itDepartment = new Department(null, "System maintenance", 101);

Optional<String> dptName = Optional.of(itDepartment.getDepartmentName());

System.out.println(dptName.get()); // NullPointException

of를 통해 객체 생성을 할 경우는 해당 값이 무조건 Null이 아닐 경우에만 사용해야 한다. 만약 해당 값이 Null 가능성이 있다면 아래의 ofNullable을 사용하여 Optional객체를 생성해야 한다. 

 

생성 (2) <T> Optional<T> ofNullable

public static <T> Optional<T> ofNullable(T value) {
    return value == null ? (Optional<T>) EMPTY
                         : new Optional<>(value);
}

of 와 달리 만약 참조할 객체 또는 값이 null 가능성이 있을 경우, ofNullable을 사용하여 Optional 객체를 생성한다.

Department itDepartment = new Department("IT Department", "System maintenance", 101);
Optional<String> dptName = Optional.ofNullable(itDepartment.getDepartmentName());
System.out.println(dptName); //RESULT : Optional[IT Department]
System.out.println(dptName.get()); // RESULT : "IT Department"

뭐... 별차이 없어 보인다. 하지만 Intelij를 사용하면....

of를 사용할 때와는 다르게 경고줄이 표시되며 "Optional.get() without "isPresent" check" 라는 경고문이 나온다.

isPresent는 Optional에서 제공해 주는 메소드로 값이 존재할 경우 True null일경우 False를 반환해 주는 메소드이다.

if(dptName != null) { // 이 코드와 동일하다고 보면 된다.
    return true;
} else {
    return false;
}

 

즉 Null 가능성이 있으니 isPresent 등을 사용하여 Null Check를 하라는 말이다.(컴파일 에러는 아니다.)

 

반응형

이거 그래서 왜씀? 뭐임?

답은 하나다. 앞에 힘들게 정리해 왔던 funtion / consumer / stream ...등등... 처럼 lamda 식으로 간결하게 처리 하기 위함이다.

 

자 그럼 Optional에서 제공하는 여러가지 메소드 몇몇을 사용해서 기존 Null처리 방식과 어떤 차이가 있는지 예제를 통해 확인해보자

 

1. ifPresent(Consumer<? super T> action)

public void ifPresent(Consumer<? super T> action) {
    if (value != null) {
        action.accept(value);
    }
}

ifPresent는 Optional에서 제공하는 메소드 중 하나로, 만약 참조하는 인자 값이 null이 아닐 경우 주어진 Consumer Action을 실행하는 메소드 이다.

단순하게 부서 클래스로 부터 부서명을 받아 출력하는 코드를 보면

// 전통적인 방식
String dptName = itDepartment.getDepartmentName();
if(dptName != null)
    System.out.println(dptName);

이걸 Optional의 ifPresent를 사용하여 코드를 작성한다면...

Optional<String> dptName = Optional.ofNullable(itDepartment.getDepartmentName());
dptName.ifPresent(s -> System.out.println("test1 data exist"));

이렇게 lamda식으로 간결하게 표현이 가능하다.(그냥 이게 좋은거라고 가스라이팅 합시다.)

 

2. Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)

or 메소드는 of나 ofNullable을 통해 객체를 생성하여 사용시, null 일경우 Default 객체 또는 Default 값을 넣어주기 위해 사용하는 메소드이다.

public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) {
    Objects.requireNonNull(supplier);
    if (isPresent()) {
        return this;
    } else {
        @SuppressWarnings("unchecked")
        Optional<T> r = (Optional<T>) supplier.get();
        return Objects.requireNonNull(r);
    }
}

구현된 내용을 보면 현제 Optional 객체의 값이 isPresent() 즉 존재하면 자기 자신을 return하고, 만약 값이 존재하지 않으면(Null일 경우) 인자로 받은 Supplier 인터페이스에 존재하는 Optional 객체를 반환한다.

 

3. T orElse(T other)

orElse또한 or과 동일하게 of나 ofNullable을 통해 객체를 생성하여 사용시, null 일경우 Default 객체 또는 Default 값을 넣어주기 위해 사용하는 메소드이다.

큰  차이점은 orElse는 Optional 객체를 반환하는게 아닌 값 그 자체를 반환한다는 것이다.

public T orElse(T other) {
    return value != null ? value : other;
}

or 메소드와 구현된 내용이 많이 다른걸 볼 수 있다. 단순하게 Optional의 Value값이 존재하면 해당 값을 return하고, null일경우 인자로 받은 값을 return 한다.

 

Optional<String> dptName = Optional.ofNullable(itDepartment.getDepartmentName());

Optional<String> hrDepartment = dptName.or(() -> Optional.of("HR Department"));
String hrDepartment = dptName.orElse("HR Department");

해당 코드를 통해 or과 orElse 반환값 차이를 확인 할 수 있다.

 

그 밖에도

2024.01.30 - [개발/Java] - Java 기초부터 다시 - interface Stream 에서 설명한 Stream의 Filter , Map 등의 기능도 사용 가능하다.

 

Optional 실 사용

앞서 예제로 설명한 부서(Department) / 사원(Employee)  / 물품(Stock) 클래스에서 물품을 입력하면 해당 물품을 사용하는 사원의 부서를 반환하는 함수를 만들어보자.

전통적인 방식대로 만든다면....

public String getDepartmentOfEmployeeFromStock(Stock stock) {
    if (stock != null) {
        Employee emp = stock.getUsingEmployee();
        if (emp != null) {
            Department department = emp.getDepartment();
            if (department != null) {
                String departmentName = department.getDepartmentName();
                if (departmentName != null) {
                    return departmentName;
                }
            }
        }
    }
    return "not exist department";
}

이렇게 하나하나 Null검사를 하면 Null에 안전하게 개발을 진행할것이다. PTSD 오지쥬?

Optional을 사용해서 조금 간편하게 구현을 다시하면...

public String getDepartmentOfEmployeeFromStock(Stock stock) {
    return Optional.ofNullable(stock)
            .map(s -> s.getUsingEmployee())
            .map(e -> e.getDepartment())
            .map(d -> d.getDepartmentName())
            .orElse("not exist department");
}

이렇게 표현이 가능하다. != null 로 부터 자유를 찾았다...

반응형