반응형

Optional<T>

  • T 타입 객체의 래퍼클래스 - Optional<T>

Optional<T>는 래퍼클래스다.  
이렇게 T타입의 참조변수를 가지고 있다. 

Integer라든가, Long 같은것들이 래퍼클래스다.(기본형 타입을 객체로 사용해야 할 때 사용)
각각 Integer타입과 Long타입을 가지고 있는 클래스를 래퍼클래스라고 한다.

Optional<T> 도 지네릭 T 덕분에 어떤 타입이든지 저장할 수 있다.
클래스 안에 T타입의 참조변수를 가지고 있다.

그러니까, 모든 종류의 객체를 저장할 수 있다. 그리고 null도 저장할 수 있다.

이것이 왜 필요하냐면, 

1. null을 직접 다루는 것은, 위험하다. (NullpointerException) 그래서 Optional<T>에 담아서 간접적으로 null을 다루기 위함.
2. null을 직접 다루게 되면, null체크를 해줘야하는데, 그러면 if문이 필수다. 그러면 코드가 지저분해진다. 

이러한 문제들을 해결하려고 Optional<T>를 이용해서 간접적으로 null을 다룬다.

예를들어서 Object result = getResult(); 라는 코드가 있다고 가정해보자.
이 메서드가 객체를 반환한다고 했을 때, 객체 반환값은, 
1. null 이거나
2. 객체 이거나

둘중 하나일 것이다.

result.toString() 이런문장을 실행하는데,
만약에 result가 null이면 어떻게 될까? 

NullPointerException이 발생할 것이다.

그래서 항상 if(result != null)  result.toStrin(); 이런식으로 if문을 사용해줘야 할 것이다.
그래서 null일 수 있는 값을 다룰 때는, NullPointerException이 발생할 수 있으므로, if문으로 확인을 해줘야 한다.

그런데, 이러한 문제점으로 인한 번거로움을 해결하기 위해 나온 것이 Optional<T>이다.

지금까지는 result에 직접 null을 담았었으면,
이제 Optional<T>을 이용해서 null을 Optional 객체에 넣고, 이 객체를 가리키는 주소를 참조값으로 저장하게 된다.
그러면 reuslt의 결과가 null 이더라도, result의 값은 항상 null이 아닌 것이다.
항상 Optioanl 객체의 주소가 있을 것이다.

Optional<T>를 사용하면, 
NullpointerException이 발생할 수 있는 가능성이 없어지고,
if문으로 null을 체크하지 않아도 되서 코드가 간결해진다.

그렇기 때문에 Optional을 사용하는 것이다.

Null을 직접 다루지 않는 경우는 예를 들어 Stream이 있는데,
Stream은 Stream str = null; 이렇게 하지 않고,
Stream str = ""; 이렇게 쓰는데, 

빈문자열이 사실은, new char[0] 즉, 길이가 0인 char 배열이다.

만약에 stream str = null; 이렇게 쓴다면,
if(str != null) 이런식으로 null 체크를 해줘야 했을텐데, 항상  이렇게 "" 빈문자열로 다루면, if문을 사용할 필요가 없다. (그래도 체크를 해야되겠지만)

그래서 기본적으로 null 값으로 초기화 하지 않고, 이렇게 "" 빈문자열로 초기화를 한다.

다른 배열도 마찬가지다.
iv를 초기화 할 때, int[] arr = new int[0]; 이런식으로 초기화할 수 있다. 
물론 arr = null; 이렇게 쓸 수도 있겠지만, int arr = new int[0]; 이것이 더 좋은 코드다.

이렇게 하는 이유가 전부 "null을 직접 다루는 것은 위험"하기 떄문이다.

 

Optional<T> 객체 생성하기

  • Optional<T>객체를 생성하는 다양한 방법

이렇게 Optional 클래스에 static 메서드인 of 메서드를 쓰면 되는데, 
만약에 str이 "abc"를 저장하고 있는 주소 0x100을 참조한다고 했을 때, 
이것을 저장하는 Optional<String> optVal = Optional.of(str); 을 실행하면,

Optional 객체가 만들어지고, optVal은 Optional객체의 주소가 0x200이라고 가정했을 때 해당 주소를 참조하게 된다.
이때 객체 안에 str의 주소값 0x100이 들어잇는 것이다. 그러면 optVal의 value는 같은 "abc"가 저장된 객체를 가치키게 된다.

예전에는 직접 String을 다뤘는데, 
이제는 이런식으로 한단계 더 겨쳐서 사용하는 것이다.
왜냐하면 str 이 가리키는 값이 null일 수도 있기 때문이다.
그런데 이렇게 한단계 거치는 방식은, 참조값을 가리키고 있기 때문에 절대 null이 될수 없다.

 

그런데, Optional에 null을 넣는 Optional.of(null);은 쓸 수 없다. NullPointerException이 발생한다.

null을 저장할 수 있는 Optional은 .ofNullable(null); 이라는 메서드를 이용해서 만들어야 한다.
Optional.ofNullable(null); 을 사용한다. 
Optional은 대부분 null일수도 있는 값에 사용하므로 많이 사용하는 메서드다.

Optional<T>는 모든 타입의 객체를 저장할 수 있다는 것을 명심하자.

 

  • null 대신 빈 Optional<T> 객체를 사용하자

null일 수 있는 값은, 그냥 쓰지 말고 Optional 객체에 넣어서 사용하자.

null 객체를 초기화 할 떄, Optional<String> optVal = null; 이런식으로 초기화 하면 안된다.
Optional<String> optVal = Optional.<String>empty(); 이렇게 빈 객체로 초기화해야 한다. <String>은 생략 가능하다.

Object[] objArr = null; 이렇게 하는 것 보다
Object[] objArr = new Object[0] 이렇게 크기가 0인 배열로 초기화 하는 것이 좋다.

Optional도 마찬가지로 null로 초기화 하지 말고, 빈 Optional 객체로 초기화하자.

NullPointerException을 줄이기 위한 것임을 기억하자.

 

Optional<T> 객체의 값

  • Optional객체의 값 가져오기 - get(), orElse(), orElseGet(), orElseThrow()

.get()으로 optional 객체에 저장된 값을 반환한다. null이면 예외가 발생한다.

.orElse(""); 는 optVal에 저장된 값이 null 일 때는, ""를 반환한다. 매개변수로 null일때 반환할 값을 주는 것이다. 

람다식을 넣을 수 있는 메서드도 있다.
orElseGet() 메서드는 람다식을 사용할 수 있다.  

orElseGet은 이렇게 Supplier람다식을 넣을 수 있다. Supllier는 입력없이 주기만 하는 것이다.
예를 들어서 String str3 = optVal.orElseGet(String::new); 에서 메서드참조를 람다식으로 바꾸면  () -> new String() 이다. 

orElseThrow()메서드는 get()과 거의 비슷한데, 다른점은,
예외 종류를 직접 지정해 줄 수 있다.

실제 사용에서는 get()과 orElseGet()이 주로 사용되는 메서드다.

 

  • isPresent() - Optional객체의 값이 null이면 false, 아니면 true를 반환한다.

isPresent()는 Optional 객체의 값이 null이면 false, 아니면 true를 반환하는 메서드다.

ifPresent()라는 메서드는 Optional 객체가 가지고 있는 값이 null이 아닐 때만 작업을 수행하게한다. null이면 아무일도 하지 않는다.
매개변수로는 매서드 참조다. 
람다식으로 바꿔보면, Optional의 value가 v라고 하면, (v) -> (System.out.println(v))가 될 것이다.

 

 

OptionalInt, OptionalLong, OptionalDouble

  • 기본형 값을 감싸는 래퍼클래스

 

이러한 것들은, 성능때문에 사용하는 것들이다. 원래는 Optional<T>를 사용해도 상관이 없는데, 성능때문에 사용하는 것이다.

람다와 스트림이 모든 것을 감싸고 있고, 모든 것을 객체로 다루기 때문에 성능이 조금 떨어진다.
그래서 성능을 조금 늘리려고  OptionalInt, OptionalLong, OptionalDouble 을 사용하는 것이다.

Optional<T>는 final T value 이렇게 참조변수로 되어 있는데,

OptionalInt를 보면, final int value; 로 기본형으로 되어있다.

이 차이다. OptionalInt, OptionalLong, OptionalDouble은 성능을 높이기 위해서 사용한다.

 

  • OptionalInt의 값 가져오기 - int getAsInt() 

그래서 Optional<T>에서 값을 꺼낼 때는 get()을 사용했는데,

OptionalInt 에서 값을 꺼낼 때는 getAsInt()를 사용해야한다.

OptionalLong에서 값을 꺼낼 때는 getAsLong()

OptionalDouble에서 값을 꺼낼 때는 getAsDouble() 을 사용한다.

 

  • 빈 Optional 객체와의 비교

OptionalInt인 경우에 OptionalInt.of(0)은 0을 저장하게 한 것이고,
OptionalInt.empty(); 는 아무값도 저장하지 않고 0을 저장 하도록 한 것인데 
둘다 0이 저장되어 있는데 이것을 어떻게 구별할까?

0을 저장해도 value가 0이고, empty를 해도 기본값이 0이라서 0인데, 이것을 어떻게 구별할까?

이러한 것 때문에, isPresnt()라는 반환값이 boolean타입의 iv변수가 존재한다.

그래서 만약에 값이 있으면 opt.isPresent() 가 true이고,
값이 없으면 opt2.isPresent() 가 false이다.

둘이 같은지 비교해보면, opt.equals(opt2)를 해보면 false가 나온다.
euqals가 value가 같아도 isPresent값 까지 같아야 true가 되도록 오버라이딩 되있는 것을 확인할 수 있다.

반응형

'JAVA' 카테고리의 다른 글

collect()와 Collectors  (0) 2022.11.30
스트림의 최종연산  (0) 2022.11.25
스트림의 중간연산(2)  (0) 2022.11.19
스트림의 중간연산(1)  (0) 2022.11.19
스트림의 연산  (0) 2022.11.16