우테코 프리코스 미션인 숫자야구 게임에서 볼과 스트라이크를 카운트하는 기능을 구현하고 있었다.
우선 아래와 같이 볼과 스트라이크 각각의 개수를 저장할 Map을 가진 BallCounter 클래스를 작성했다.
public class BallCounter {
private static final Map<PitchResult, Integer> ballCounter = new HashMap<>();
이때 초기값으로 BALL과 STRIKE를 Key로 가지고 있으면 어떨까.. 라는 생각을 했고, Map을 초기화하는 방법을 찾아봤다.
Map을 초기화하는 방법에는 여러가지가 존재했는데, 그중 몇 가지만 가져왔다.
가장 먼저 Steam API를 활용하는 방법을 만나게 되었다.
이 친구는 이차원 배열을 스트림으로 변환한 후 Map으로 변환하는 과정을 거치는 방식이다. 나는 key와 value의 타입이 다른 관계로 Object 타입의 이차원 배열을 사용했다.
private static final Map<PitchResult, Integer> ballCounter = Stream.of(new Object[][]{
{BALL, 0},
{STRIKE, 0}
}).collect(Collectors.toMap(item -> (PitchResult) item[0], item -> (Integer) item[1]));
public static void countBall() {
ballCounter.put(BALL, ballCounter.getOrDefault(BALL, 0) + 1);
}
보다시피 굉장히 복잡한데, 두번의 변환 과정을 거치니 당연했다.
특히 가독성이 좋지 않아 패스했다.
다음은 Java9에서 처음 등장한 Map.of()
를 활용하는 방식이다.
간단한 테스트 코드를 작성하는 등의 이유로 List.of()
를 많이 사용해서 그런지 익숙하게 작성할 수 있었다.
private static final Map<PitchResult, Integer> ballCounter = Map.of(
BALL, 0,
STRIKE, 0
);
이 친구는 Collectors.toMap()
보다 간결해서 마음에 들었다.
하지만,,, List.of()
에 대해서 알아봤다면 눈치 챘을 것이다.
"immutable 객체가 반환된다는 사실을..."
쉽게 말해서 초기화는 했는데 put()
, remove()
등 데이터를 변경할 수 없다.
나는 왜 여태 몰랐을까... 아무튼 그래서 다음 방식으로 넘어갔다.
다시 블로그를 열심히 돌아다니다가 좋은 친구를 발견했다.
아래처럼 이중 중괄호를 쓰는 방식인데, 이중 중괄호는 처음 보는 것 같다.
private static final Map<PitchResult, Integer> ballCounter = new HashMap<>() {{
put(BALL, 0);
put(STRIKE, 0);
}};
보이는 것과 같이 나름 깔끔한 편이라 이 친구로 정했다. 아니 정했었다.
안티 패턴이라는 소리를 듣기 전까지는...
이중 중괄호 초기화는 익명 내부 클래스를 생성하고 초기화 블록을 사용하여 데이터를 채운다. 이 과정에서 더 많은 메모리를 사용하게 되고, 성능 저하를 발생시킬 수 있다고 한다.
물론 내가 진행중인 미션은 복잡하지 않은 프로그램이라서 문제가 발생할 일이 거의 없겠지만, 가독성이 좋지 않기도 해서 다른 패턴을 사용하기로 했다.
그냥 Collectors.toMap()을 사용해야지 하면서 아쉬운 마음에 Java Map을 보고 있었다.
그런데.. 여기서 처음 보는 메서드를 발견했다.
설명을 읽어보니 "찾는 key가 존재하면 그 key의 값을 반환하고, 존재하지 않는다면 기본값을 반환한다."라고 나와 있었다.
기본값이라... 내가 원하던거 아닌가?
내가 Map을 초기화하려던 이유는 다음과 같다.
볼이 나왔을 때 key가 BALL인 값을 1 증가시켜야 하는데, 초기값이 설정되어 있지 않아 NPE가 발생하기 때문이었다.
그런데 찾는 key가 없는 경우 NULL이 아닌 기본값을 반환한다니, 문제가 깔끔히 해결되는 것 아닌가?
무엇보다 코드의 의도가 명확해지고, 메서드명을 통해 각각의 행동이 무엇을 하는지 이해하기 쉬워지는 장점이 내게 큰 매력으로 다가왔다.
private static final Map<PitchResult, Integer> ballCounter = new HashMap<>();
public static void countBall() {
ballCounter.put(BALL, ballCounter.getOrDefault(BALL, 0) + 1);
}
public static void countStrike() {
ballCounter.put(STRIKE, ballCounter.getOrDefault(STRIKE, 0) + 1);
}
위와 같이 코드를 수정했고, 테스트도 정상적으로 실행되는 것을 확인할 수 있었다.