1. NullPointerException
null처리가 취약한 코드
- 예시
/* 주문 */
public class Order {
private Long id;
private Date date;
private Member member;
// getters & setters
}
/* 회원 */
public class Member {
private Long id;
private String name;
private Address address;
// getters & setters
}
/* 주소 */
public class Address {
private String street;
private String city;
private String zipcode;
// getters & setters
}
/* 주문을 한 회원이 살고 있는 도시를 반환한다 */
public String getCityOfMemberFromOrder(Order order) {
return order.getMember().getAddress().getCity();
}
- 위 코드가 NPE에 취약한 이유
- order 파라미터에 null 값이 넘어오면 order.getMember() , order.getMember().getAddress(), order.getMember().getAddress().getCity() 의 함수 호출부에서 null 처리를 하지 않으면 NPE 발생 가능성 농후
기존 NPE 방지 방식
- 중첩 null 체크
if( order != null ) {
//코드
if(member !=null ) {
//코드
if(address != null) {
//코드
}
}
}
- return 잔뜩 !
if(order == null) return;
//코드
if(member == null) return;
//코드
if(address == null) return;
//코드
- null의 고질적인 문제
- NPE 예외 발생 가능
- NPE 방지를 위한 null 체크 로직 때문에 코드 가독성과 유지보수성 저하
Optional의 등장
<aside> 💡 함수형 언어들은 존재할지 안 할지 모르는 값을 표현할 수 있는 별개의 타입을 가짐 ⇒ Java8은 이런 함수형 언어의 접근방식을 적용하여, java.util.Optional<T>클래스 도입
</aside>
Optional이란?
존재할 수도 있지만 안할 수도 있는 객체. 즉, null이 될 수 있는 객체를 감싸고 있는 래퍼 클래스
- 원소가 최대 하나밖에 없는 Collection 이나 Stream 으로 생각해도 됨
- 효과
- NPE 유발 가능한 null을 직접 다루지 않아도 됨
- 수고롭게 null 체크를 하지 않아도 됨
- 명시적으로 해당 변수가 null일 수 있다는 가능성을 표현 가능
Optional의 사용법
- 변수 선언하기
- 제네릭 제공 ⇒ 변수 선언시 명시한 타입 파라미터에 따라 감쌀 수 있는 객체가 결정됨
- 변수명에 “maybe” 또는 “opt”와 같은 접두사를 붙여 Optional 타입의 변수라는 것을 좀 더 명확히 나타내기도 함
- 객체 생성하기
- Optiona.empty()
- null을 담고 있는 빈 Optional 객체 생성
- Optional을 내부적으로 미리 생성해놓는 싱클턴 인스턴스
- Optional.of(value)
- null이 아닌 객체를 담고 있는 Optional 객체 생성
- null이 넘어올 경우 NPE 발생하므로 주의해서 사용할 것
- Optional.ofNullable(value)
- null인지 아닌지 확인할 수 없는 객체를 담고있는 Optional 객체 생성
- 위 두 메서드를 합처놓은 메서드
- Optiona.empty()
- 객체 접근하기
- get()
- 비어있는 객체면 NoSuchElementException 던짐
- orElse(T other)
- 비어있는 Optional 객체에 대해 넘어온 인자 반환
- orElseGet(Supplier<? extends T> other)
- 비어있는 Optional 객체에 대해, 함수형 인자를 통해 생성된 객체 반환
- 비어있는 경우에만 함수가 호출됨 → orElse 대비 성능상 이점을 가짐
- orElseThrow(Supplier<? extends X> exceptionSupplier)
- 비어있는 Optional 객체에 대해, 예외 던짐
- get()
Stream처럼 사용하기
- Optional : 최대 1개의 원소를 가지고 있는 특별한 Stream이라 생각하자
- Stream이 가진 map(), flatMap(), filter() 를 Optional도 가짐
- map()
- null-safe한 코드 구현 가능해짐
- map() 을 활용한 개선 코드
- /* 주문을 한 회원이 살고 있는 도시를 반환한다 */ public String getCityOfMemberFromOrder(Order order) { return Optional.ofNullable(order) .map(Order::getMember) //null 또는 Member객체 반환 .map(Member::getAddress) //null 또는 Address객체 반환 .map(Address::getCity) //null 또는Address 객체가 가지는 String city값 반환 .orElse("none"); //null이 반환될 때 default 값 설정 }
- filter()
- if조건문 없이 메서드 연쇄 호출만으로 읽기 편한 코드 작성 가능해짐
- 함수형 인자의 리턴 값이 false인 경우 Optional을 비워버림. 그 이후 메소드 호출 의미 없어짐 !
- Optional이 비어있거나 filter조건을 만족하지 않으면 연산 후에도 Optional.empty()가 됨
- Optional이 값으로 채워져 있거나, 그 값이 filter조건을 만족하면 Optional은 그 값을 계속 가짐
- filter() 를 활용하여 개선한 코드
- order 가 null 이면 Optional.empty() 반환
- filter(o -> o.getDate().getTime() > System.currentTimeMillis() - min * 1000): filter 메소드는 주어진 조건을 만족하는지 확인
- map(Order::getMember) : filter를 통과한 Order객체가 있으면 그 Order의 Member를 가져와서 Optional로 감싸기
- public Optional<Member> getMemberIfOrderWithin(Order order, int min) { return Optional.ofNullable(order) .filter(o -> o.getDate().getTime() > System.currentTimeMillis() - min * 1000) .map(Order::getMember); }
- map()
'Learning-log > JAVA' 카테고리의 다른 글
Intellij 단축키 모음 (0) | 2023.07.05 |
---|---|
[Java] 람다식(Lamda) 개념 및 사용 정리 (0) | 2023.06.20 |
패키지, import, 캡슐화, 접근제한자, 접근자, 설정자, static, JVM 메모리 구조 (Java) (0) | 2023.04.01 |
[Java] 상속과 다형성 (0) | 2023.04.01 |
객체지향 프로그래밍 정리(Java, Class, 생성자, 인스턴스, 변수, 메서드, this) (0) | 2023.03.23 |