null 을 안전하게 다루게 해주는 객체NullPointerException 을 방지할 수 있음Optional<T> 은 null 이 올 수 있는 값을 감싸는 Wrapper 클래스camp.getStudnet() 는 null 을 반환할 가능성이 있는 메소드null 을 반환하게 되고, NullPointerException 예외가 발생함public class Student {
// 속성
private String name;
// 생성자
// 기능
public String getName() {
return this.name;
}
}
public class Camp {
// 속성
private Student student;
// 생성자
// 기능: ⚠️ null 을 반환할 수 있는 메서드
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
public class Main {
public static void main(String[] args) {
Camp camp = new Camp();
// ⚠️ student 에는 null 이 담김
Student student = camp.getStudent();
// ⚠️ 아래 코드에서 NPE 발생!
// 런타임 예외이므로 컴파일러가 잡아주지 않음
String studentName = student.getName();
System.out.println("studentName = " + studentName);
}
}
public class Main {
public static void main(String[] args) {
Camp camp = new Camp();
Student student = camp.getStudent();
String studentName;
// ⚠️ 가능은하지만 현실적으로 어려움
if (student != null) {
studentName = student.getName();
} else {
studentName = "등록된 학생 없음"; // 기본값 제공
}
System.out.println("studentName = " + studentName);
}
}
물론 가능한 방법이지만
모든 코드에서 null 이 발생할 가능성을 미리 예측하고 처리하는 것은 현실적으로 어려움
null 이 반환될 가능성을 명시할 수 있음Optional.ofNullable() 을 사용해서 null 이 반환될 수 있는 객체를 감쌈isPresent() 와 같은 기능을 사용해서 안전하게 null 을 처리할 수 있음Optional 내부에 값이 존재할 때 true 반환Optional 내부에 값이 존재하지 않을 때, 즉 null 일 경우 false 반환public class Camp {
// 속성
private Student student;
// 생성자
// 기능
// ✅ null 이 반환될 수 있음을 명확하게 표시
public Optional<Student> getStudent() {
return Optional.ofNullable(student);
}
public void setStudent(Student student) {
this.student = student;
}
}
public class Main {
public static void main(String[] args) {
Camp camp = new Camp();
// isPresent() 활용시 true 를 반환하고 싶을때 활용
// Optional 객체 반환받음
Optional<Student> studentOptional = camp.getStudent();
// Optional 객체의 기능 활용
boolean flag = studentOptional.isPresent(); // false 반환
if (flag) {
// 존재할 경우
// ✅ 안전하게 Student 객체 가져오기
Student student = studentOptional.get();
String studentName = student.getName();
System.out.println("studentName = " + studentName);
} else {
// null 일 경우
System.out.println("학생이 없습니다.");
}
}
}
public class Camp {
// 속성
private Student student;
// 생성자
// 기능
// ✅ null 이 반환될 수 있음을 명확하게 표시
public Optional<Student> getStudent() {
return Optional.ofNullable(student);
}
}
public class Main {
public static void main(String[] args) {
Camp camp = new Camp();
// ✅ Optional 객체의 기능 활용 (orElseGet 사용)
Student student = camp.getStudent()
.orElseGet(() -> new Student("미등록 학생"));
System.out.println("studentName = " + student.getName());
}
}
isPresent() 와 비슷한 기능을 제공하지만
람다식을 활용하기 때문에 간결한 코드 작성 가능
어떤 데이터가 절대 null 이 아니라면 Optional.of() 로 생성할 수 있다.
이 경우에 데이터가 null 이 들어오면 NullPointerException 예외가 발생한다.
// Optional의 value는 절대 null이 아니다.
Optional<String> optional = Optional.of("MyName");
어떤 데이터가 null 일 수도 있고 아닐 수도 있으면 Optional.ofNullable() 로 생성할 수 있다.
orElse() 나 orElseGet() 메소드를 이용해서 데이터를 안전하게 가져올 수 있다.
// Optional의 value는 값이 있을 수도 있고 null 일 수도 있다.
Optional<String> optional = Optional.ofNullable(getName());
String name = optional.orElse("anonymous"); // 값이 없다면 "anonymous" 를 리턴
아래와 같이 우편번호를 조회하는 null 검사 코드가 있다고 하자.
위 코드는 null 검사 때문에 복잡하다.
public String findPostCode() {
UserVO userVO = getUser();
if (userVO != null) {
Address address = user.getAddress();
if (address != null) {
String postCode = address.getPostCode();
if (postCode != null) {
return postCode;
}
}
}
return "우편번호 없음";
}
Optional 을 사용하면 다음과 같이 표현할 수 있다.
public String findPostCode() {
// 위의 코드를 Optional로 펼쳐놓으면 아래와 같다.
Optional<UserVO> userVO = Optional.ofNullable(getUser());
Optional<Address> address = userVO.map(UserVO::getAddress);
Optional<String> postCode = address.map(Address::getPostCode);
String result = postCode.orElse("우편번호 없음");
String result = user.map(UserVO::getAddress)
.map(Address::getPostCode)
.orElse("우편번호 없음");
}
스트림(Stream) 과 메소드 참조 연산자(::) 를 함께 사용하면 코드가 많이 짧아지고, 코드 가독성이 높아진다.
이름을 대문자로 변경하는 코드에서 NullPointerException 을 처리해준다고 하자.
String name = getName();
String result = "";
try {
result = name.toUpperCase();
} catch (NullPointerException e) {
throw new CustomUpperCaseException();
}
Optional 을 사용하면 다음과 같이 표현할 수 있다.
Optional<String> nameOpt = Optional.ofNullable(getName());
String result = nameOpt.orElseThrow(CustomUpperCaseExcpetion::new)
.toUpperCase();
스트림(Stream) 과 메소드 참조 연산자(::) 를 함께 사용하면 코드가 많이 짧아지고, 코드 가독성이 높아진다.
Optional 은 null 또는 값을 감싸서 NullPointerException 로부터 부담을 줄이기 위해 등장한 Wrapper 클래스이다.
Optional 은 값을 Wrapping하고 다시 풀고, null 일 경우에는 대체하는 함수를 호출하는 등의 오버헤드가 있으므로
잘못 사용하면 시스템 성능이 저하된다.
그렇기 때문에 메소드의 반환 값이 절대 null이 아니라면 Optional을 사용하지 않는 것이 좋다.
즉, Optional은 메소드의 결과가 null이 될 수 있으며, null에 의해 오류가 발생할 가능성이 매우 높을 때 반환값으로만 사용되어야 한다.
orElse()와orElseGet()는 큰 차이가 있으니, 차이점을 정확히 이해하고 사용해야 한다.
자세한 정보는 참고자료를 참조하자.
Optional이란? Optional 개념 및 사용법 - (1/2)
언제 Optional을 사용해야 하는가? 올바른 Optional 사용법 가이드 - (2/2)
챕터 3-2 : Optional - null을 다루는 법