Object.requireNonNull(), Map - getOrDefault (), compute(), computeIfAbsent()
// 개선 전
// friend는 [["json", "john"], ["john", "mike"]...] 이런 데이터가 담긴 List이다.
private static Map<String, List<String>> makeFriendRelation(List<List<String>> friends) {
HashMap<String, List<String>>friendsRelation = new HashMap<>();
for (List<String> friendPair:friends) {
Objects.requireNonNull(friendsRelation.put(friendPair.get(FRIEND_A),new ArrayList<>()))
.add(friendPair.get(FRIEND_B));
Objects.requireNonNull(friendsRelation.put(friendPair.get(FRIEND_B),new ArrayList<>()))
.add(friendPair.get(FRIEND_A));
}
return friendsRelation;
}
makeFriendRelation()
메서드는 friends
리스트에 담긴 친구관계를 Map<String, List<String>>
타입으로 담아서 반환하려는 목적으로 만든 메서드이다. friends
의
요소 friendPair
에서 두 친구를 꺼내어 Map
에 양방향으로 저장하는 로직을 생각하고 Objects.requireNonNull()
를 사용했는데 내가 생각한 동작과 전혀 다른 메서드였다.
Objects.requireNonNull()
Java의 Objects.requireNonNull
메서드는 객체 참조가 null인지 확인하는데 사용된다. 이 메서드는 주어진 객체가 null이 아닌 경우 그대로 반환하며, 만약 null인 경우에는
NullPointerException을 발생시킨다.
Objects.requireNonNull(T obj)
: 이 형태의 메서드는 인자로 전달된 객체가 null인 경우 NullPointerException을 발생시킨다.Objects.requireNonNull(T obj, String message)
: 이 형태의 메서드는 인자로 전달된 객체가 null인 경우 지정된 에러 메시지와 함께 NullPointerException을 발생시킨다.
Objects.requireNonNull
은 일반적으로 입력 인수를 검증하는데 사용되며, 이를 통해 개발자들은 런타임에 적절한 예외 처리를 할 수 있다.
public void setName(String name) {
this.name=Objects.requireNonNull(name,"Name cannot be null");
}
위 코드에서 setName 함수는 name 매개변수가 null일 때 “Name cannot be null”라는 메세지와 함께 NullPointerException을 발생시킨다.
다시 본론으로 돌아와서 내가 원한 동작은 Map
인터페이스에서 제공하는 getOrDefault()
를 사용하여 간단하게 구현할 수 있었다.
getOrDefault()
friendsRelation
에서 getOrDefault(Object key, V defaultValue)
를 사용하면 key
로 해당 Map
을 검색하여 key
가 존재하지 않을
경우 defaultValue
를 반환한다. 만약 해당 key
가 존재한다면 key
의 value
를 반환한다. 따라서 getOrDefault()
를 사용하면 키가 없을 때 null
대신 원하는 기본값을
받을 수 있어서 NullPointerException
을 예방할 수 있다.
private static Map<String, List<String>> makeFriendRelation(List<List<String>> friends) {
HashMap<String, List<String>> friendsRelation = new HashMap<>();
for (List<String> friendPair : friends) {
String friendA=friendPair.get(FRIEND_A);
String friendB=friendPair.get(FRIEND_B);
// friendsRelation에 friendA가 없다면 new ArrayList<>()를 반환.
// friendA가 있다면 해당 value를 반환.
List<String> listA=friendsRelation.getOrDefault(friendA,new ArrayList<>());
List<String> listB=friendsRelation.getOrDefault(friendB,new ArrayList<>());
listA.add(friendB);
listB.add(friendA);
friendsRelation.put(friendA,listA);
friendsRelation.put(friendB,listB);
}
return friendsRelation;
}
주의할 점은 getOrDefault()
메서드의 key
가 존재하지 않을 경우 해당 키에 defaultValue
를 저장하는 건 아니라는 점이다.
getOrDefault()
는 단순히 주어진 키에 대응하는 값을 찾거나 없다면 기본값을 반환할 뿐, 키가 없을 때 해당 키에 기본값을 저장할 수는 없었다. 하지만 Map
인터페이스에 compute()
, computeIfAbsent()
는 Map
내의 특정 키에 대한 값을 계산하고 저장할 수 있다.
compute()
Map<String, Integer> map = new HashMap<>();
map.put("Apple", 10);
map.compute("Apple", (k, v) -> v == null?42:v+5);
// "Apple" 키의 현재 값은 10이므로 새로운 값은 15가 된다.
map.compute("Banana", (k, v) -> v == null?42:v+5);
// "Banana" 키는 현재 map에 없으므로 새로운 "Banana" 키에 42가 map에 저장된다.
map.compute("Apple", (k, v) -> v < 10 ? v : null);
// "Apple" 키의 현재 값은 15이므로 15 < 10 == false가 되고
// 3항 연산자 결과가 null이 되어 "Apple" 키와 그 value인 15 가 map에서 삭제된다.
compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
이 메서드는 주어진 키와 현재 매핑된 값을 사용해 새로운 값을 계산하고 저장한다. 만약 remappingFunction
이 null
을 반환하면 해당 키-값 쌍은 Map
에서 제거된다. 이 메서드 역시
최종적으로 계산된 값을 반환한다.
private static void computeScore(Map<String, Integer> recommendFriends, String target, int x) {
// compute()를 사용하여 recommendFriends 맵에서 target을 key로 찾는다.
// remappingFunction을 (key, value) -> value == null ? x : value + x 로 하여
// key target의 value를 얻고 그 value가 null이라면 x를 저장,
// null이 아니라면 저장되어있던 value에 x를 더하여 저장한다.
recommendFriends.compute(target, (key, value) -> value == null ? x : value + x);
}
compute()
를 사용하여 Map
에 키가 존재할 때 연산과, 존재하지 않을 때 기본값을 바로 저장할 수 있었다.
computeIfAbsent()
Map<String, Integer> map = new HashMap<>();
Integer value = map.computeIfAbsent("Apple", k -> 10);
// "Apple" 키가 없으므로 10을 계산하여 저장하고 반환합니다.
map.computeIfAbsent("Apple", k -> 20);
// "Apple" 키가 10으로 저장되어있어서 null이 아니기 때문에 computeIfAbsent()가 수행되지 않는다.
// 결과적으로 "Apple"의 키로 10이 저장되어있다.
map.put("Banana", null); // "Banana" 키로 null값이 저장되었다.
map.computeIfAbsent("Banana", k -> 30);
// "Banana" 키는 존재하지만, 값이 null인 상태이므로 computeIfAbsent() 메서드가 수행된다.
// 결과적으로 "Banana" 키에 값이 30으로 저장된다.
computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
이 메서드는 주어진 키가 Map
에 존재하지 않거나 그 값이 null
인 경우에만 mappingFunction
을 사용해 값을 계산하고 저장한다. 이미 값이 있는 경우에는 기존의 값을 그대로 반환한다. 이
메서드는 계산된 값(또는 기존의 값)을 반환한다.
이 메서드를 사용하여 위의 getOrDefault()
를 사용하여 구현한 코드를 최적화했다.
private static Map<String, List<String>> makeFriendRelation(List<List<String>> friends) {
HashMap<String, List<String>> friendsRelation = new HashMap<>();
for (List<String> friendPair : friends) {
String friendA = friendPair.get(FRIEND_A);
String friendB = friendPair.get(FRIEND_B);
// friendsRelation에 friendA 키가 없으면 new ArrayList<>()를 반환하고
// add(friendB); 하여 빈 리스트에 friendB를 넣게된다.
// friendsRelation에 friendA가 있다면 computeIfAbsent()는 수행되지 않고,
// 기존 friendA 키에 들어있는 List에 .add(friendB);가 수행된다.
friendsRelation.computeIfAbsent(friendA, key -> new ArrayList<>()).add(friendB);
friendsRelation.computeIfAbsent(friendB, key -> new ArrayList<>()).add(friendA);
// getOrDefault() 사용하여 구현했던 코드
/**
List<String> listA = friendsRelation.getOrDefault(friendA, new ArrayList<>());
List<String> listB = friendsRelation.getOrDefault(friendB, new ArrayList<>());
listA.add(friendB);
listB.add(friendA);
friendsRelation.put(friendA, listA);
friendsRelation.put(friendB, listB);
*/
}
return friendsRelation;
}
getOrDefault()
를 사용하여 friendA
의 값이 null
이면 빈 리스트를 받아서 friendB
를 저장하고, 그 리스트를 firendsRelation
에 put
하는
과정을 computeIfAbsent()
한 줄로 줄일 수 있었다.
댓글남기기