백기선 님의 <더 자바, Java 8>를 보고 공부한 내용을 기록합니다.
1. 필요성
•
개발자인 우리는 종종 null 체크를 깜빡하게 된다. 그래서 NPE 를 만들어내기도 한다.
•
java8부터는 이러한 부분을 보완하기 위해 Optional 이라는 타입이 추가되었다.
2. 적절한 값이 없을 경우, 리턴 방법들
메소드 작업 중에 값을 제대로 리턴하기 힘든 경우, 개발자가 선택할 수 있는 방법은 대략 다음과 같았다.
1.
null 을 리턴한다.
a.
비용문제는 없지만, 코드를 사용하는 클라이언트에게 null 처리 부담을 전가한다.
2.
예외를 던진다.
a.
스택트레이스를 찍게 되므로 비용이 비싸다.
3.
java 8 부터는 Optional 을 리턴한다.
a.
값이 있을수도 있고, 없을 수도 있음을 명시적으로 알려준다.
b.
빈 값에 대한 처리를 강제한다.
3. Optional
•
오직 값 한개가 들어있을수도 없을수도 있는 “컨테이너”
4. 주의할 것
4-1. 리턴 값으로만 쓰기를 권장한다.
•
매소드의 매개변수타입, 맵의 키 타입, 인스턴스 필드 타입으로 쓰지 않는다.
◦
매개변수타입 : null 값에 대한 연산이 발생할 가능성이 있기 때문에 오히려 더 번거롭고 위험해진다.
◦
맵의 키 타입 : 맵 인터페이스의 가장 중요한 것은 키는 null 이 아니라는 것이다. 이것에 optional type 이라는 것은 안된다.
◦
인스턴스 필드 타입 : 클래스를 쪼개던지, 다른 방법을 이용한다.
public class Food { private int id; private String name; private boolean isLiked; private String bestRestaurantName; // return type 으로만 사용한다. public Optional<String> getBestRestaurantName() { return Optional.ofNullable(bestRestaurantName); }}// 안티 패턴public class Food { private int id; private String name; private boolean isLiked; // 3. 인스턴스 필드 타입으로 쓰지 않는다. private Optional<String> bestRestaurantName; public void setBestRestaurantName(Optional<String> bestRestaurantName) { // null 에 대해서 처리할 때 NPE 발생 우려 // 오히려 더 복잡해진다. if (baseRestaurantName == null) { ...
} }}
Java
복사
4-2. Optional 을 리턴하는 메소드에서 null 을 리턴하지 말자.
•
차라리 Optional.empty() 를 이용할 것
public class Food { private int id; private String name; private boolean isLiked; private String bestRestaurantName; public Optional<String> getBestRestaurantName() { // return null; // 금지! return Optional.empty(); }}
Java
복사
4-3. Primitive type 을 위한 Optional 이 별도로 존재한다.
•
OptionalInt, OptionalLong …
•
박싱/언박싱 비용을 줄일 수 있다.
@Testvoid primitiveTypeOptionalTest() { OptionalInt optionalInt = OptionalInt.of(10); optionalInt.ifPresent(System.out::println); OptionalLong optionalLong = OptionalLong.of(10); if (optionalLong.isPresent()) { assertThat(optionalLong.getAsLong()).isEqualTo(10); }}
Java
복사
4-4. Collection, Map, Stream Array, Optional 등 이미 컨테이너 타입으로 존재하며 비어있는지 알 수 있는 타입들은 Optinal 로 굳이 감싸지 않는다.
•
컨테이너로 두 번 감싸는 꼴이다.
@Testvoid optional이_필요없을때_두번감싸지_말자() { Optional<Optional<String>> opt = Optional.ofNullable(null); // not recommended Optional<String> s = opt.get(); //NPE 발생 String s1 = s.get(); Optional<List<Food>> optionalFoods = Optional.of(asianFoods); // not recommended asianFoods.isEmpty(); // recommended}
Java
복사
5. 참고
•
이팩티브 자바 3판, 아이템 55 적절한 경우 Optional을 리턴하라.
6. Optional API Practice
public class OptionalApiTest { private List<Food> asianFoods = new ArrayList<>(){{ add(new Food(1, "비빔밥", true)); add(new Food(2, "설렁탕", false)); add(new Food(3, "불닭볶음면", true)); add(new Food(4, "떡볶이", true)); add(new Food(5, "궁중떡볶이", false)); add(new Food(6, "순대국", false)); }}; @Test void optionalOf를이용하여_Optional을_만들수있다() { Optional<Food> optionalOf = Optional.of(new Food(1, "라면", true)); Optional<Food> optionalNullable = Optional.ofNullable(null); Optional<Food> optionalEmpty = Optional.empty(); } @Test void optionalIsPresent를_이용하여_값의_존재여부를_확인한다() { Optional<Food> optionalOf = Optional.of(new Food(1, "라면", true)); Optional<Food> optionalNullable = Optional.ofNullable(null); Optional<Food> optionalEmpty = Optional.empty(); assertThat(optionalOf.isPresent()).isTrue(); assertThat(optionalNullable.isPresent()).isFalse(); assertThat(optionalEmpty.isEmpty()).isTrue(); } @Test void optionalGet을_이용하여_값을_꺼낸다() { Optional<Food> optionalRamen = Optional.of(new Food(1, "라면", true)); if (optionalRamen.isPresent()) { Food ramen = optionalRamen.get(); assertThat(ramen).isEqualTo(new Food(1, "라면", true)); } } @Test void null값을_Get하려고하면_예외가_발생한다() { Optional<Food> optionalNullable = Optional.ofNullable(null); assertThatThrownBy(() -> optionalNullable.get()) .isInstanceOf(NoSuchElementException.class); } @Test void empty값을_Get하려고하면_예외가_발생한다() { Optional<Food> optionalEmpty = Optional.empty(); assertThatThrownBy(() -> optionalEmpty.get()) .isInstanceOf(NoSuchElementException.class); } @Test void ifPresentConsumer로_반환값_없이_행위를_수행한다() { Optional<Food> optionalRamen = Optional.of(new Food(1, "라면", true)); optionalRamen.ifPresent(f -> System.out.println(f.getName())); //라면 } @Test void orElse는_무조건_실행되므로_이미_반환할_값이_있을떄_사용하면_좋다() { Optional<Food> optionalEmpty = Optional.empty(); Food food = optionalEmpty.orElse(createNewFood()); assertThat(food).isEqualTo(new Food(1, "라면", true)); System.out.println("====="); Optional<Food> optionalOf = Optional.of(new Food(1, "라면", true)); Food food2 = optionalOf.orElse(createNewFood()); assertThat(food2).isEqualTo(new Food(1, "라면", true)); //new food created //===== //new food created } @Test void orElseGet은_없을때만_실행되므로_새롭게_만들어줄떄_사용하면_좋다() { Optional<Food> optionalEmpty = Optional.empty(); Food food = optionalEmpty.orElseGet(() -> createNewFood()); //supplier assertThat(food).isEqualTo(new Food(1, "라면", true)); System.out.println("====="); Optional<Food> optionalOf = Optional.of(new Food(1, "라면", true)); Food food2 = optionalOf.orElseGet(OptionalApiTest::createNewFood); //supplier assertThat(food2).isEqualTo(new Food(1, "라면", true)); //new food created //===== } @Test void orElseThrow는_없는경우_에러를_던진다() { Optional<Food> optionalEmpty = Optional.empty(); assertThatThrownBy(() -> optionalEmpty.orElseThrow(() -> new NullPointerException())) .isInstanceOf(NullPointerException.class); Optional<Food> optionalEmpty2 = Optional.empty(); assertThatThrownBy(() -> optionalEmpty2.orElseThrow(NullPointerException::new)) .isInstanceOf(NullPointerException.class); } @Test void filter를_이용하여_optional_값을_걸러낼수있다() { Optional<Food> first = asianFoods.stream() .filter(f -> f.getId() > 2) .findFirst(); Food food = first.filter(f -> f.getId() == 3).orElseThrow(); assertThat(food.getName()).isEqualTo("불닭볶음면"); } @Test void flatMap으로_Optional에_들어있는값을_변환할수있다() { Optional<Food> first = asianFoods.stream() .filter(f -> f.getId() > 2) .findFirst(); Optional<String> filtered = first.map(f -> f.getBestRestaurantName()).orElse(Optional.empty()); assertThat(filtered).isEmpty(); Optional<String> filtered2 = first.flatMap(f -> f.getBestRestaurantName()); assertThat(filtered2).isEmpty(); } private static Food createNewFood() { System.out.println("new food created"); return new Food(1, "라면", true); }}
Java
복사