백기선 님의 <더 자바, Java 8 >를 보고 공부한 내용을 기록합니다.
메소드 작업 중에 값을 제대로 리턴하기 힘든 경우, 개발자가 선택할 수 있는 방법은 대략 다음과 같았다.
Copy 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 ) {
...
}
}
}
4-2. Optional 을 리턴하는 메소드에서 null 을 리턴하지 말자.
Copy
public class Food {
private int id;
private String name;
private boolean isLiked;
private String bestRestaurantName;
public Optional < String > getBestRestaurantName () {
// return null; // 금지!
return Optional . empty ();
}
}
4-3. Primitive type 을 위한 Optional 이 별도로 존재한다.
Copy @ Test
void primitiveTypeOptionalTest() {
OptionalInt optionalInt = OptionalInt . of ( 10 );
optionalInt . ifPresent ( System . out :: println);
OptionalLong optionalLong = OptionalLong . of ( 10 );
if ( optionalLong . isPresent ()) {
assertThat( optionalLong . getAsLong()) . isEqualTo ( 10 );
}
}
4-4. Collection, Map, Stream Array, Optional 등 이미 컨테이너 타입으로 존재하며 비어있는지 알 수 있는 타입들은 Optinal 로 굳이 감싸지 않는다.
Copy @ Test
void 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
}
Copy 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 ) ;
}
}