ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] Optional로 null 데이터를 다룰 때 안티 패턴을 주의해야 한다.
    Backend/자바(java) 2024. 9. 27. 16:37

     

    사용자의 입력은 믿을 수 없는 정보이다.

    사용자 입력에서 발생할 수 있는 예외 케이스를 고민해야 한다. 그리고 예외 케이스에 대한 이 필요하다.

    ex) 이메일 입력창에 특수 문자를 입력할 수 있다.

     

    null을 대하는 자세

    → 항상 NPE를 방지하는 방향으로 경각심을 가지고 코드를 작성한다.

    return null을 자제한다. NPE 발생 여지를 만든다.

    → 대안책으로 Optional이 있다.

     

    Optional에 대하여

    → Optional은 비싼 객체다. 꼭 필요한 상황에서만 반환 타입으로만 사용한다. 반환 타입이 아닌 파라미터로 받지 말자.

    → Optional은 분기 케이스가 존재한다.

    • Optional 자체가 null인지
    • Optional에 담긴 데이터가 null인지

    따라서 파라미터Optional을 받아서는 안 된다. Optional의 분기 케이스를 확인해야 하므로 안티 패턴이 된다.

    → Optional을 받았다면 최대한 빠르게 해소하여 분기 케이스를 만들지 말자.

     

    Optional을 해소하는 방법

    • 분기문을 만드는 ifPresent(), get() 대신 풍부한 API를 사용한다.
      • ex) orElseGet(), orElseThorw(), ifPresent(), ifPresentOrElse()
    • orElse(), orElseGet()의 차이를 숙지한다. 무분별한 사용은 성능에 영향을 미친다.
      • orElse() : 파라미터로 넘어온 값이 항상 사용된다. → Optional에 담긴 데이터가 무거운 작업이라면 성능에 영향을 미친다. 왜냐하면 Optional에 담긴 데이터가 null이 아니더라도 작업을 반드시 실행하기 때문이다.
      • orElseGet() : Supplier를 받고 Optional에 담긴 데이터가 null인 경우에만 Supplier가 사용된다. 따라서 Optional에 데이터가 담겨있다면, 불필요하게 Supplier를 실행하지 않는다.
    public T orElse(T other) {
    	return value != null ? value : other;
    }
    
    public T orElseGet(Supplier<? extends T> supplier) {
    	return value != null ? value : supplier.get();
    }
    
    somethingOptional.orElse(performanceHeavy()); // performanceHeavy() 항상 실행
    somethingOptional.orElseGet(() -> performanceHeavy()); // Optional에 담긴 데이터가 null인 경우에만 실행
    

     

    Optional.orElse() → 안티 패턴

    orElse()는 Optional에 담긴 데이터와 상관없이 항상 파라미터로 전달받은 값을 사용한다.

    public T orElse(T other) {
            return this.value != null ? this.value : other;
    }
    
    • emptyObjectnotEmptyObject 둘 다 orElse()가 호출되면서 performanceHeavy()가 호출된다. notEmptyObject는 불필요한 계산을 하는 것이기 때문에 안티 패턴이 된다.
    import java.util.Optional;
    
    public class Main {
    
        private static final int JOB_COUNT = 100_000_000;
    
        public static void main(String[] args) {
            System.out.println(">>> emptyObject");
            Optional<Integer> emptyObject = Optional.empty();
            Integer result1 = emptyObject.orElse(performanceHeavy());
            System.out.println(">>> emptyObject = " + emptyObject);
            System.out.println(">>> result1 = " + result1 + "\\n");
    
            System.out.println(">>> notEmptyObject");
            Optional<Integer> notEmptyObject = Optional.of(10);
            Integer result2 = notEmptyObject.orElse(performanceHeavy());
            System.out.println(">>> notEmptyObject = " + notEmptyObject);
            System.out.println(">>> result2 = " + result2 + "\\n");
        }
    
        private static int performanceHeavy() {
            System.out.println(">>> performanceHeavy() 호출");
            int count = 0;
            for (int i = 0; i < JOB_COUNT; i++) {
                count++;
            }
            return count;
        }
    }
    

     

    notEmptyObjectperformanceHeavy()를 호출하였으나, 해당 메서드에 계산된 값을 사용하지 않는다.

     

    Optional.orElseGet() → Optional.orElse() 대안책

    orElseGet()은 Optional에 담긴 데이터가 null이 아니라면 파라미터로 받은 Supplier를 사용하지 않는다.

    @ValueBased
    public final class Optional<T> {
        public T orElseGet(Supplier<? extends T> supplier) {
            return this.value != null ? this.value : supplier.get();
        }
    }
    
    • emptyObject의 경우 Optional에 null 데이터가 담겨있기 때문에 performanceHeavy()를 호출한다.
      • performanceHeavy()가 호출되면서 result1에는 100_000_000이 담긴다.
    • notEmptyObject의 경우 Optional에 10이라는 데이터가 담겨있기 때문에 performanceHeavy() 를 호출하지 않는다.
      • performanceHeavy()가 호출되지 않기 때문에 result2에는 10이 담긴다.
    import java.util.Optional;
    
    public class Main {
    
        private static final int JOB_COUNT = 100_000_000;
    
        public static void main(String[] args) {
            System.out.println(">>> emptyObject");
            Optional<Integer> emptyObject = Optional.empty();
            Integer result1 = emptyObject.orElseGet(() -> performanceHeavy());
            System.out.println(">>> emptyObject = " + emptyObject);
            System.out.println(">>> result1 = " + result1 + "\\n");
    
            System.out.println(">>> notEmptyObject");
            Optional<Integer> notEmptyObject = Optional.of(10);
            Integer result2 = notEmptyObject.orElseGet(() -> performanceHeavy());
            System.out.println(">>> notEmptyObject = " + notEmptyObject);
            System.out.println(">>> result2 = " + result2);
        }
    
        private static int performanceHeavy() {
            System.out.println(">>> performanceHeavy() 호출");
            int count = 0;
            for (int i = 0; i < JOB_COUNT; i++) {
                count++;
            }
            return count;
        }
    }
    

     

    notEmptyObject는 10이라는 데이터를 가지고 있기 때문에 불필요하게 performanceHeavy()를 호출하지 않는다.

     

     

Designed by Tistory.