본문 바로가기

Programming/Java

[Java] Optional 사용하기

반응형

안녕하세요. 남산돈가스입니다.

오늘은 Java Optional 에 대해서 알아보려고 합니다.

숨어있는 폭탄같은 null

자바 프로그래머라고 한다면, 누구나 범하는 실수 중 하나가 NullPointerException(이하 NPE)이죠. 컴파일 레벨에서는 잠잠히 숨어있다가 런타임 레벨에서 혜성처럼 등장하여 개발자들의 멘탈을 흔들어버리는 null 참조... 

라이브 환경에서 생각지도 발생해버리면 오히려 맥이 다 빠져버리는 NPE는 자바 프로그래머들에게는 숙명처럼 붙어다닐 예외가 아닐까 싶습니다.

이 NPE를 막기 위한 null check에 대한 반복적인 코드 사용과 이를 미연에 방지하기 위한 클래스가 바로 Optional 입니다.

Java Optional 이란?

Java Optional은 Java8 부터 도입 된 Clasas입니다. Optional이 생겨난 이유는 간단하게 말하자면 널(null) 때문입니다. 위에서 언급한 것 처럼 자바 프로그래밍에서 흔히 범하는 NullPointerException 예외를 이 Optional을 이용하여 별도의 조건 처리 없이 처리할 수 있게 되었습니다.

Optional 는 제네릭 타입 <T>를 가지는 클래스이며, Integer나 Boolean과 같이 객체의 타입을 감싸고 있는 Wrapper Class 입니다.

Java 공식 API Note에서의 Optional은 다음과 같이 설명되어 있습니다. 

API Note:Optional is primarily intended for use as a method return type where there is a clear need to represent “no result,” and where using null is likely to cause errors. A variable whose type is Optional should never itself be null; it should always point to an Optional instance.

메서드가 반환할 결과값이 ‘없음’을 명백하게 표현할 필요가 있고, null을 반환하면 에러를 유발할 가능성이 높은 상황에서 메서드의 반환 타입으로 Optional을 사용하자는 것이 Optional을 만든 주된 목적이다. Optional 타입의 변수의 값은 절대 null이어서는 안 되며, 항상 Optional 인스턴스를 가리켜야 한다.

그럼 코드를 보며 설명드리겠습니다.

Java Optional 사용

Optional 객체 생성

Optional 은 of 나 ofNullable 메서드를 이용하여 생성할 수 있습니다.

Optional<String> optStr = Optional.of("This is Optional");
Optional<Integer> optNum = Optional.ofNullable(15);

of 는 null이 아닌 값을 가지는 Optional 객체를 반환합니다. of 메소드를 통해 생성 된 Optional 객체에 null이 저장되면 NPE 가 발생하게 됩니다. 그러므로 참조하는 변수의 값이 만약 null이 될 가능성이 있다면, ofNullable() 메소드를 사용해 Optional 객체를 생성하는 것이 좋습니다.

Optional 객체 접근

Optional 객체에 접근하기 위해선 get() 메소드를 이용하면 됩니다. 만약 Optional 객체가 null을 가지고 있다면 get() 호출 시 NoSuchElementException이 발생합니다. 따라서, get()을 사용하려면 이 전에 Optional이 제공하는 Check 메서드들을 이용하여 접근하는 게 맞습니다.

Optional<String> optStr = Optional.ofNullable("This is Optional");

if(optStr.isPresent()) {
	System.out.println(optStr.get());
}

위 코드에서는 Optional 객체의 값이 존재하는 지 여부를 확인하기 위한 isPresent() 메서드를 이용했습니다. 이 외에도 Optional 객체 Check를 핸들링하는 메소드들이 있습니다.

  • orElse() : 저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 값을 반환함.
  • orElseGet() : 저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 람다 표현식의 결괏값을 반환함.
  • orElseThrow() : 저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 예외를 발생시킴.

Optional 활용

// 회사
@Getter @Setter
private class Company {
    private Long id;
    private Date date;
    private Employer employer;
}

// 고용주
@Getter @Setter
public class Employer {
    private Long id;
    private String name;
    private Address address;
}

//주소
@Getter @Setter
public class Address {
    private String street;
    private String city;
    private String zipcode;
}

만약 위와 같은 구조를 가진 데이터가 있다고 가정해봅시다. Company(회사)는 유일한 Employer(고용주)가 있으며 그 고용주는 이름과 Address(주소) 정보를 가지고 있습니다.

만약에 위 구조에서 회사 정보를 입력받아 고용주의 집 주소를 get하는 코드를 작성했다고 해봅시다. 아마 아래와 같이 작성할 수 있습니다.

public String getEmployerZipcode(Company company) {
	return company.getEmployer().getAddress().getZipcode();
}

여기서 발생할 수 있는 NPE는 다음과 같이 4가지가 있습니다.

  1.  Company 가 null 인 경우 
  2.  Company의 Employer 가 null 인 경우
  3.  Employer의 Address 가 null 인 경우
  4.  Address의 zipcode 가 null 인 경우

물론, 비즈니스에 따라서 이미 null check 되어 넘어오는 데이터가 있을 수 있지만, 최악의 시나리오를 생각하기 위해 예제를 들었습니다.

만약에 Optional이 없었던 예전이라면 저는 다음과 같이 처리했을 겁니다.

public String getEmployerZipcode(Company company) {
	if (company != null) {
		Employer employer = company.getEmployer();
		if (employer != null) {
			Address address = employer.getAddress();
			if (address != null) {
				String zipcode = address.getZipcode();
				if (zipcode != null) {
					return zipcode;
				}
			}
		}
	}
	return "04537"; // default
}

벌써 머리가 아프네요...

map() 사용하기

위의 복잡한 처리를 Optional의 map 체이닝과 람다를 이용하여 간결하게 표현할 수 있습니다. 먼저 리팩토링한 결과부터 보면

public String getEmployerZipcode(Company company) {
	return Optional.ofNullable(company)
		.map(company::getEmployer)
		.map(Employer::getAddress)
		.map(Address::getZipcode)
		.orElse("04537");
}

우선 인자로 입력받은 company 객체가 null일 수 있음에 ofNullable로 Optional 객체를 생성한 뒤 map 체이닝을 하면서 Optional한 객체들을 인자로 넘겨 수행할 메소드를 수행하였고, 마지막에 최종적으로 해당 값들 중 null이 발생한 경우 default값을 리턴하는 방식으로 변경하였습니다.

이 외에도 Optional의 사용법은 다양하게 존재합니다. 이렇게 null-safe한 코드를 작성하시려면 다양한 Optional의 예제를 익혀서 적용하시면 될 것 같습니다.

감사합니다.

참고  
https://www.daleseo.com/java8-optional-effective
http://tcpschool.com/java/java_stream_optional

'Programming > Java' 카테고리의 다른 글

static 과 static final의 차이  (0) 2020.04.25