ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • null은 실수? Optional의 등장
    Java/Java 문법 종합반 강의 정리 2024. 1. 9. 19:44

    Null은 10억 달러 짜리 실수다?
    - null은 Tony Hoare가 만든 개념이다.
    - 기본적으로 아무것도 참조하지 않는다는 것을 의미한다.
    - 2009년에 Tony Hoare는 null이라는 개념을 만든것이 1조원짜리 실수라고 이야기 했다

    자세한 이야기는 아래의 링크에 있지만 간략하게 요약하자면,
    - null이라는 "개념"이 존재하기 때문에, 거의 모든 상황에 null이 발생할 수 있음을 경계해야 한다.
    - 이상적이라면 모두가 메서드 이름을 "findWhateverAndifnoExistReturnNull()"처럼 작성하고
     해당 메서드를 사용하는 모두는 null 체크를 해줘야 한다.
    - 하지만 그런일은 일어나지 않았고, 이것을 가리켜 10억 달러짜리 실수라고 밝혔다.
    https://zorba91.tistory.com/339

     

    왜 Null을 보고 나쁘다고 하는걸까?

    신입 때부터 관용어처럼 'null은 나쁘다'라는 말을 들어왔다. '왜 null은 나쁜가?'라는 궁금증을 가지고 있으면서도 막연히 객체, 값의 불안정함, null 처리를 위해 생기는 지저분한 코드들을 만들어

    zorba91.tistory.com

     


     


    다음과 같은 코드는 초보자들이 자주 하는 실수이며, null이 왜 위험한지를 단적으로 보여주는 예시이다.

    public class NullIsDanger {
        public static void main(String[] args) {
    
            SomeDBClient myDB = new SomeDBClient();
            
            String userId = myDB.findUserIdByUsername("HelloWorldMan");
    
            System.out.println("HelloWorldMan's user Id is : " + userId);
        }
    }
    
    class SomeDBClient {
    
        public String findUserIdByUsername(String username) {
            // ... db에서 찾아오는 로직
    				String data = "DB Connection Result";
    
            if (data != null) {
                return data;
            } else {
                return null;
            } 
        }
        
    }

     

    위 코드의 문제점

    1. 논리적으로도, 환경적으로도 null이 반환될 여지가 있음에도, null이 반환될 수 있음을 명시하지 않았다.

    2. 메인함수쪽에서, 사용 할 때 null체크를 하지 않아, 만약 null이 반환된다면 nullPointerException 이 발생한다.

     

    개선 1 : null이 반횐될 여지를 명시하고, 그 메서드를 사용하는 사람이 조심하기

    public class NullIsDanger {
        public static void main(String[] args) {
    
            SomeDBClient myDB = new SomeDBClient();
    
            String userId = myDB.findUserIdByUsernameOrThrowNull("HelloWorldMan");
            // 개선 1: null이 반환 될 수 있음을 인지한 메서드 사용자는, null을 대비합니다.
            if (userId != null) {
                System.out.println("HelloWorldMan's user Id is : " + userId);
            }
        }
    }
    
    class SomeDBClient {
        // 개선 1: 이 메서드는 null이 반환 될 수 있음을 명시합니다.
        public String findUserIdByUsernameOrThrowNull(String username) {
            // ... db에서 찾아오는 로직
    				String data = "DB Connection Result";
    
            if (data != null) {
                return data;
            } else {
                return null;
            }
        }
    
    }

     

    개선 1의 문제점

    사람은 누구나 실수를 하게 되어있다.

    누군가는 바빠서, 혹은 익숙하지 않아서 null체크를 하지 않는다면, 시스템은 위험에 빠진다.

     

    개선 2 : 객체를 감싸서 반환하기

    // 개선 2: 결과값을 감싼 객체를 만듭니다.
    class SomeObjectForNullableReturn {
        private final String returnValue;
        private final Boolean isSuccess;
    
        SomeObjectForNullableReturn(String returnValue, Boolean isSuccess) {
            this.returnValue = returnValue;
            this.isSuccess = isSuccess;
        }
    
        public String getReturnValue() {
            return returnValue;
        }
    
        public Boolean isSuccess() {
            return isSuccess;
        }
    }
    
    public class NullIsDanger {
        public static void main(String[] args) {
    
            SomeDBClient myDB = new SomeDBClient();
    
            // 개선 2 : 이제 해당 메서드를 사용하는 유저는, 객체를 리턴받기 때문에 더 자연스럽게 성공여부를 체크하게 됩니다.
            SomeObjectForNullableReturn getData = myDB.findUserIdByUsername("HelloWorldMan");
    
            if (getData.isSuccess()) {
                System.out.println("HelloWorldMan's user Id is : " + getData.getReturnValue());
            }
        }
    }
    
    class SomeDBClient {
        // 개선 2 : 결과값을 감싼 객체를 리턴합니다.
        public SomeObjectForNullableReturn findUserIdByUsername(String username) {
            // ... db에서 찾아오는 로직
            String data = "DB Connection Result";
    
            if (data != null) {
                return new SomeObjectForNullableReturn(data, true);
            } else {
                return new SomeObjectForNullableReturn(null, false);
            }
        }
    
    }

     

    이제 해당 메서드는 결과를 감싼 객체를 리턴하고, 감깐 객체를 받은 유저는 이 메서드가 위험할 수 있다는 것을

    더 쉽게 인지할 수 있다.

     

    개선 3 : 개선 2의 아이디어 발전시키기

    class SomeObjectForNullableReturn<T> {
        private final T returnValue;
        private final Boolean isSuccess;
       
        
    
        SomeObjectForNullableReturn(T returnValue, Boolean isSuccess) {
            this.returnValue = returnValue;
            this.isSuccess = isSuccess;
        }
    
        public T getReturnValue() {
            return returnValue;
        }
    
        public Boolean isSuccess() {
            return isSuccess;
        }
        
    }

     

    "감싸는 객체"를 조금 수정하면, 이제 위험 할 수 있는 모든 메서드에 사용 할 수 있게 된다.

     

    결론 : 개선 3의 아이디어를 발전시킨 것이 java.util.Optional 객체이다.

     

    - 실제 java.util.Optional 객체의 소스코드이다.

    - 우리가 데이터를 감싸면서 했던 로직들과, 그 외에도 처리를 편하게 해주는 로직들이 있다.

     

    Optional 기본 정리

    - java8에서는 Optional<T> 클래스를 사용해 Null Pointer Exception을 방지할 수 있도록 도와준다.

    - Optional<T>는 null이 올 수 있는 값을 감싸는 Wrapper 클래스이다.

    - Optional이 비어있더라도, 참조해도 Null Pointer Exception 가 발생하지 않는다.

     


     

    Optional 간단 사용법

     

    값이 null인 Optional 생성하기

    Optional<Car> emptyOptional = Optional.empty();

     

    값이 있는 Optional 생성하기

    Optional<Car> hasDataOptional = Optional.of(new Car());

     

    값이 있을수도 없을수도 있는 Optional 생성하기

    Optional<Car> hasDataOptional = Optional.ofNullable(getCarFromDB());

     

    Optional 객체 사용하기 (값 받아오기)

    Optional<String> carName = getCarNameFromDB();
    // orElse() 를 통해 값을 받아온다, 파라미터로는 null인 경우 반환할 값을 적는다.
    String realCarName = CarName.orElse("NoCar");
    
    //위는 예시코드고, 실제로는 보통 아래와 같이 사용한다.
    String carName = getCarNameFromDB().orElse("NoCar");
    
    //orElseGet()이라는 메서드를 사용해서 값을 받아올 수 있다.
    //파라미터로는 없는 경우 실행될 함수를 전달한다.
    Car car = getCarNameFromDB().orElseGet(Car::new);
    
    //값이 없으면, 그 아래 로직을 수행하는데 큰 장애가 되는 경우 예외를 발생시킬 수도 있다.
    Car car = getCarNameFromDB()
    			.orElseThrow(() -> new CarNotFoundException("NO CAR!");
Designed by Tistory.