이 포스팅은 공부 목적으로 작성된 포스팅입니다. 왜곡된 내용이 포함되어 있을 수 있습니다.
54 null이 아닌, 빈 컬렉션이나 배열을 반환하라
다음 코드를 보자
private final List<Object> ObjectInStock = new ArrayList<>();
public List<Object> getObjects() {
return ObjectInStock.isEmpty() ? null
: new ArrayList<>(ObjectInStock);
}
Object들을 List Collection으로 가지고 있는 ObjectInStock에 대해 getObjects() 메소드는 Objects가 존재한다면 방어적 복사를 통해 ObjectInStock을 반환하고 Objects가 존재하지 않는다면 null을 반환한다.
이렇게 EmptyList가 아닌 null을 반환하는 경우, 해당 리스트를 사용하면서 NullPointException에 마주 할 수 있기 때문에 별도의 처리를 해줘야한다.
List<Object> objects = getObjects();
if (objects == null) {
System.out.println("Object가 존재하지 않습니다");
}
null을 굳이 반환하는 이유는 대부분 EmptyList로 인한 리소스 낭비라는 점에서 null을 반환하는데, 이러한 주장은 현명하지 못한데, EmptyList로 인한 리소스 낭비가 영향을 주지 못할 정도로 미미하고, 매번 EmptyList를 생성할 필요없이 불변 객체를 넘겨주는 방식으로 구현가능하기 때문이다.
실제로 Collections.emptyList라는 불변 객체 반환 메소드가 존재한다.(Set이나 Map도 존재)
List<Object> objects = Collections.emptyList();
/*
Collections.emptyList
*/
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}
배열의 경우에도 null을 반환하지 않고 길이가 0인 배열을 반환해야한다.
public Cheese[] getCheeses(){
return cheesesInStock.toArray(new Cheese[0]);
}
ToArray는 list에서 배열 변환을 지원한다.
https://bluesparrow.tistory.com/59
그러나 ToArray의 경우에는 파라미터에 인스턴스를 넣어 보내기 때문에 매번 인스턴스를 만들어야 한다는 점에서 성능에 영향이 갈 수 있다는 점에서 길이가 0인 배열을 미리 선언하여 처리하는 방법을 사용해야한다.
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
public Cheese[] getCheeses() {
return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}
55 옵셔널 반환은 신중히 하라
컬렉션의 경우 빈 컬렉션을 반환하면 되지만 단일 인스턴스의 경우 해당 인스턴스가 존재하거나 null인경우 두가지로 표현할 수 밖에 없다. 이는 null 에 대한 처리를 항상 염두해야함을 의미한다.
자바 8에 등장한 Optional<T>을 사용하여 타입 참조를 하나 담거나, 아무 타입도 담지 않을 수 있다.(비었다)
Optional은 원소를 최대 1개 가질 수 있는 불변 컬렉션이다.(실제로 컬렉션은 아니지만 개념적으로)
null의 경우가 존재할 수 있는 경우 Optional을 사용하면 효과적으로 대처할 수 있다.
public static <E extends Comparable<E>> E max(Collection<E> c) {
if(c.isEmpty()) {
throw new IllegalArgumentException("빈 컬렉션");
}
E result = null;
for (E e : c) {
if(result == null || e.compareTo(result) > 0) {
result = Objects.requireNonNull(e);
}
}
return result;
}
max함수에 대해서 만약 컬렉션이 비었다면 IllegalArgumentException을 발생시킨다.
이제 Optional을 적용해보자
public static <E extends Comparable<E>> Optional<E> max2(Collection<E> c) {
if(c.isEmpty()) {
return Optional.empty();
}
E result = null;
for (E e : c) {
if(result == null || e.compareTo(result) > 0) {
result = Objects.requireNonNull(e);
}
}
return Optional.of(result);
}
반환타입이 Optional로 바뀌어 사용자로 하여금 해당 메소드가 null값을 가질 수 있음을 효과적으로 보여준다.(에러 핸들링을 더욱 원활하게 처리할 수 있다.)
다만 Optional 내부에서도 null은 예외 없이 에러를 발생할 수 있다.
Optional.of(value)에서 value값이 null이면 이역시 NullPointerException을 발생시킨다. (Optional.ofNullable(value) 은 null을 허용한다.
Optional을 반환하는 메서드의 경우 null을 반환해선 안된다.(규칙을 어기는 행위)
이러한 Optional은 스트림 종단 연산에서도 사용되는데, max함수에서 확인할 수 있다.
Optional<T> max(Comparator<? super T> comparator);
그러면 Optional에서 null에 대한 대처 방법은 어떤게 있을까?
Optional에서는 다양한 예외처리 메소드를 지원하고 있다.
public T orElse(T other) {
return value != null ? value : other;
}
orElse를 통해 null 에대한 예외를 발생시킬수 있다.
Optional lastWordInLexicon = max(words).orElse("단어가 없습니다")
만약 Optional의 설정 비용이 부담된다면 orElseGet 메소드를 사용해보자
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
파라미터가 Supplier으로 Supplier의 Lazy Evaluation을 사용할 수 있다.
이밖에도 isPresent, map, flatMap을 적절히 사용하여 Optional을 핸들링 할 수 있다.
이러한 Optional 사용은 항상 득이 되는 것 같은데, 주의해서 사용해야한다.
먼저 컬렉션, 스트림, 배열, Optional 같은 컨테이너 타입을 Optional 타입 참조로 넣어선 안된다.
해당 컨테이너는 Optional을 사용하지 않고 자체적인 컨테이너로 충분히 관리될수 있기 때문이다.
또한 Optional을 결국 생성 비용이 존재한다.특히 박싱된 기본 타입을 참조하는 Optional의 경우(Optional<Integer>..)
의 경우 더 무거운데 포장을 2번이나 했기 때문이다. 따라서 자바에서는 OptionalInt, OptionalLong과 같은 기본 타입 전용 Optional들이 존재한다. 따라서 박싱된 기본 타입을 담은 Optional을 반환하지 않도록하자(근데 실제로는 다 사용하고 있다..)
'Java' 카테고리의 다른 글
자바 NIO (Buffer, Selector) (0) | 2024.12.19 |
---|---|
[이펙티브 자바] 아이템 64, 65, 66, 67, 6 (0) | 2024.06.18 |
List.toArray() (0) | 2024.05.19 |
[이펙티브 자바] 아이템 47, 48, 49 (2) | 2024.04.27 |
[이펙티브 자바] 아이템 39, 40, 41 (1) | 2024.03.24 |