메이쁘

[Java] JAVA8 Optional Class에 대한 정의 및 사용방법 - Tutorial(Sample) 본문

Language/JAVA

[Java] JAVA8 Optional Class에 대한 정의 및 사용방법 - Tutorial(Sample)

메이쁘 2021. 12. 9. 14:29

 

https://github.com/201402407/JAVA_LOGIC_TUTORIAL

 

GitHub - 201402407/JAVA_LOGIC_TUTORIAL: 자바의 기본기를 다지는 코딩 가이드 및 자바 튜토리얼

자바의 기본기를 다지는 코딩 가이드 및 자바 튜토리얼. Contribute to 201402407/JAVA_LOGIC_TUTORIAL development by creating an account on GitHub.

github.com

-> 자바의 기본기를 담은 자바 튜토리얼(또는 가이드)

(Java 17)


[QUESTION 1] Optional<T> 클래스란? (Java 8)

[ANSWER] 가장 큰 이유는 NPE(NullPointerException)을 방지하기 위해 사용하는 클래스. null이 들어올 수 있는 값을 한 번 감싸는 Wrapper Class.

Generic Type이 있어, 필요한 값의 클래스를 지정할 수 있다.


[ANSWER] Optional Class를 통해 null이 들어와도 NPE가 발생하지 않게 해준다.

 


[QUESTION 2] Optional<T> 생성

*** QUESTION 수행을 위해 기본 class 및 subClass 정의

class TempObject {
	String name;
	List<Depth1> list;
	
	TempObject(String name, List<Depth1> list) {
		this.name = name;
		this.list = list;
	}

	public String getName() {
		return name;
	}

	public List<Depth1> getList() {
		return list;
	}
}

class Depth1 {
	Depth2 depth2;
	
	Depth1(Depth2 depth2) {
		this.depth2 = depth2;
	}

	public Depth2 getDepth2() {
		return depth2;
	}
}

class Depth2 {
	String depth;
	
	Depth2(String depth) {
		this.depth = depth;
	}
	
	public String getDepth() {
		return depth;
	}
}

 

 

[ANSWER] Optional.ofNullable(param) : param이 null일 수도 있고, 아닐 수도 있다. NPE방지

TempObject nullNameObj = new TempObject(null, null);
TempObject notNullNameObj = new TempObject("Eternals", null);

Optional<String> optNull = Optional.ofNullable(nullNameObj.getName());	// null이기 때문에 Optional은 empty.
Optional<String> opt = Optional.ofNullable(notNullNameObj.getName());	// Not null이기 때문에 Optional은 not Empty.


[ANSWER] Optional<T>.orElse(param) : Optional<T>가 null인 경우 param 값 리턴

String nullName = optNull.orElse("NULL-NAME!");	// null인 경우 "NULL-NAME!" 리턴
String notNullName = opt.orElse("NULL-NAME!");	// null인 경우 "NULL-NAME!" 리턴

System.out.println("-> name(null) : " + nullName);
// OUTPUT: -> name(null) : NULL-NAME!
System.out.println("-> name(not null) : " + notNullName);
// OUTPUT: -> name(not null) : Eternals

 


[QUESTION 3] Optional<T> null check 방어코드 활용

List<Depth1> depthList = new ArrayList<Depth1>();	// 임시 List 객체 생성
depthList.add(new Depth1(new Depth2(null)));	// Depth1, Depth2 객체 생성 후 List에 add
TempObject temp = new TempObject(null, depthList);	// TempObject Class 객체 생성

*** QUESTION3 수행을 위해 필요한 객체들 생성

 

 

[ANSWER] JAVA 8 이전 null 방어코드

// JAVA8 이전 Optional 미사용 시 null check 방어코드
static void legacyNullDefense(TempObject temp) {
	String depthStr = "";
	if(temp != null) {
		List<Depth1> tempList = temp.getList();
		if(tempList != null) {
			Depth1 depth1 = tempList.get(0);
			if(depth1 != null) {
				Depth2 depth2 = depth1.getDepth2();
				if(depth2 != null) {
					if(depth2.getDepth() != null) {
						depthStr = depth2.getDepth();
						System.out.println("-> depth의 깊이는 :: " + depthStr);
						return;
					}
				}
			}
		}
	}
	
	depthStr = "값이 없습니다.";
	System.out.println("-> depth의 깊이는 :: " + depthStr);
}

 

 

[ANSWER] JAVA 8 Optional null 방어코드

static void optionalNullDefense(TempObject temp) {
	Optional<TempObject> tempOpt = Optional.ofNullable(temp);
	Optional<List<Depth1>> tempListOpt = tempOpt.map(TempObject::getList);
	Optional<Depth1> depth1Opt = tempListOpt.map(tempList -> tempList.get(0));
	Optional<Depth2> depth2Opt = depth1Opt.map(Depth1::getDepth2);
	Optional<String> depthStrOpt = depth2Opt.map(Depth2::getDepth);
	String depthStr = depthStrOpt.orElseGet(() -> notHaveValue());	// arrow function. functional interface인데, null인 경우만 호출
	System.out.println("-> depth의 깊이는 :: " + depthStr);
}

private static String notHaveValue() {
	return "값이 없습니다.";
}

 

[ANSWER] JAVA 8 Optional null 방어코드 -> 파이프라이닝

static void optionalNullDefense(TempObject temp) {
		String depth2Str = tempOpt.map(TempObject::getList)
				.map(tempList -> tempList.get(0))
				.map(Depth1::getDepth2)
				.map(Depth2::getDepth).orElse("값이 없습니다.");	// orElse param이 함수 return값인 경우, null이 아니어도 함수 실행하게 됨
		System.out.println("-> depth의 깊이는 :: " + depth2Str);
}

 


[QUESTION 4] Optional 사용 장단점

[ANSWER] 장점 : 코드의 가독성과 안정성은 높이고 코드 분석 시간은 줄일 수 있다.
1) 객체가 null이어도 NPE가 발생하지 않고 비즈니스 로직대로 흘러갈 수 있음
2) Optional 사용 시 'NULL 값을 받을 수 있다.' 라고 다른 개발자가 인지할 수 있기 때문에 분석 시간 줄일 수 있음
3) 비즈니스 코드와 null 방어코드가 뒤섞여 있지 않아 로직을 더 쉽게 파악할 수 있음

[ANSWER] 단점 : null check 및 Wrapping 시 오버헤드가 발생하여 성능이 저하될 수 있다.
-> null이 절대 나오지 않으면 Optional을 사용하지 않는 것이 성능에 더 좋다.
-> 즉, 함수 결과가 null이 될 수 있고 이를 인지해서 처리해야할 때 사용하는 것이 좋다.

 


[QUESTION 5] Optional<T> 메소드

(출처 : 블로그)

 

 

of(T) : Optional<T>
-> 파라미터로 받은 객체를 Optional로 감싸 반환한다. 만약 파라미터가 null이면 NPE가 발생한다.


ofNullable(T) : Optional<T>
-> 기본적으로 of()와 동일하나 파라미터가 null이면 빈 Optional을 반환한다.


empty() : Optional<T>
-> 빈 Optional을 반환한다. Optional 객체의 중간 연산 중에 값이 null이 되면 내부적으로 이 메서드를 호출한다. 

 


다음으로는 Optional의 중간 연산이다. 중간 연산은 non static 메서드면서 동시에 반환 값이 Optional<T>라는 특징이 있다.


filter(Predicate<? super T> predicate) : Optional<T>
-> Stream API의 filter()와 동일하다. predicate의 조건에 맞는 값을 필터링한다.


map(Function<? super T, ? extends U> mapper) : Optional<T>
-> Stream API의 map()과 동일하다. Optional로 감싸진 객체를 다른 객체로 변경하도록 데이터 변경을 한다.


flatMap(Function<? super T, Optional<U>> mapper) : Optional<T>
-> Optional안의 Optional이 있는 이중 구조일 때, 단일 구조로 변경하여 map()의 기능을 수행할 수 있다.

 


이번에는 Optional의 종료 연산이다. 이들은 Optional에서 벗어나 값으로 반환되는 특징이 있다.


get() : T
-> Optional의 값을 반환한다. 만약 값이 빈 값이라면 NPE가 발생한다.


orElse(T) : T
-> get과 동일한 기능을 수행하지만, 값이 비어있다면 파라미터로 받은 값으로 반환한다.
-> 또한, orElseGet보다 비용이 높고, Optional의 값이 null이든 아니든 항상 param이 호출된다.


orElseGet(Supplier<? extends T> other) : T
-> get과 동일한 기능을 수행하지만, 값이 비어있다면 파라미터에서 제공하는 값을 반환한다.
-> 또한, orElse보다 비용이 낮고, Optional의 값이 null이 아닐 경우에만 functional Interface가 호출된다.


orElseThrow(Supplier<? extends X> exceptionSupplier) : T
-> get과 동일한 기능을 수행하지만, 값이 비어있다면 파리미터에서 생성한 exception을 발생시킨다. 
 

 

기타 기능


isPresent() : boolean
-> Optional의 값이 있다면 true, 비어있다면 false를 반환한다. 상태를 확인할 뿐 값에 어떤 영향도 미치지 않는다.


ifPresent(Consumer<? super T> consumer) : void
-> Optional의 값이 있다면 파라미터(함수형 인터페이스)를 실행하고, 비어있다면 false를 반환한다. 

 

 

 

 

이상입니다.

 

감사합니다

Comments