Java8에서 Collectors.toMap()을 사용할 경우 NPE 발생에 대한 주의가 필요합니다. 개발 및 테스트 환경에서는 이상이 없었는데 운영에서 오류가 발생한 경험에 대한 내용입니다.

아래와 같이 리스트 형태의 Item을 Map 형식으로 변경 하려고 할 경우 toMap()에서 NPE가 발생합니다. “Mango”의 가격이 null 이기 때문입니다.

List<Item> items = Arrays.asList(
		new Item("Apple", 1800),
		new Item("Banana", 900),
		new Item("Grape", 3100),
		new Item("Mango", null));

Map<String, Integer> itemMap = items.stream()
		.collect(Collectors.toMap(Item::getName, Item::getPrice));	// NPE

Collectors.toMap()의 내용을 살펴보면 Java8에서 추가된 Map 인터페이스의 merge() 메소드를 사용하는데 HashMap의 merge() 에서는 value가 null인 경우를 하용하지 않습니다. HashMap 자체는 key와 value 모두 null을 허용하는데, 왜 merge()에서는 NPE를 발생시킬까요?

그 이유는 Map의 key와 value 중에서 value가 merge() 대상이인데, value가 null이면 merge를 할 수가 없기 때문에 NPE가 발생하게 됩니다.

이 문제를 해결하기 위해서는 stream의 filter를 통해서 value로 지정될 값이 null인 경우를 대상에서 제외하는 방법과

Map<String, Integer> itemMap = items.stream()
		.filter(i -> Objects.nonNull(i.getPrice()))
		.collect(Collectors.toMap(Item::getName, Item::getPrice));

Collector를 직접 지정하는 방법이 있습니다.

Map<String, Integer> itemMap = items.stream()
		.collect(HashMap::new, (m1, m2) -> m1.put(m2.getName(), m2.getPrice()), HashMap::putAll);

추가로 만약에 아래와 같이 “Apple”이라는 동일한 key가 존재할 경우에는 IllegalStateException()이 발생하게 됩니다.

List<Item> items = Arrays.asList(
		new Item("Apple", 1800),
		new Item("Banana", 900),
		new Item("Grape", 3100),
		new Item("Apple", 110));

Map<String, Integer> itemMap = items.stream()
		.collect(Collectors.toMap(Item::getName, Item::getPrice));	// 예외발생

이것은 BinaryOperator 타입의 mergeFunction 을 지정하는 방법으로 해결할 수 있습니다. key가 중복될 경우 어떻게 처리할지를 전달하는 것이죠.

value가 Integer 타입이므로 sum()을 mergeFunction으로 지정했습니다. String 타입이라면 String::concat, List 타입이라면 List::addAll로 하거나 직접 구현할 수도 있습니다.

Map<String, Integer> itemMap = items.stream()
				.collect(Collectors.toMap(Item::getName, Item::getPrice, Integer::sum));

결론

Collectors.toMap()을 사용할 때는 NPE가 발생할 수 있으므로 사용하기 전에 객체 내용중 value로 지정될 필드가 null일 가능성이 존재하는지 꼭 확인할 필요가 있습니다.

끝.

태그:

카테고리:

업데이트:

댓글남기기