Java Collections: 당신이 놓치고 있는 숨겨진 도구들
대부분의 개발자들은 매일 똑같은 방식으로 Java collections를 사용합니다. ArrayList나 HashSet을 사용하죠. 하지만 여러분은 코드를 더 빠르고 안전하게 만들어 줄 도구들을 놓치고 있을지도 모릅니다.
코드를 개선할 수 있는 세 가지 구체적인 도구를 소개합니다.
- Arrays.asList는 진정한 List가 아닙니다
Arrays.asList(array)를 사용하면 크기가 고정된 리스트를 얻게 됩니다. 이는 원본 배열과 직접 연결됩니다.
- 배열을 변경하면 리스트도 변경됩니다.
- 항목을 추가하거나 삭제하려고 하면 오류와 함께 코드가 중단됩니다.
이를 해결하려면 새로운 ArrayList로 감싸주세요:
List<String> list = new ArrayList<>(Arrays.asList(array));
이렇게 하면 수정 가능한 실제 독립적인 리스트가 생성됩니다.
- 성능을 위해 EnumSet을 사용하세요
Enum을 HashSet에 저장하고 계신가요? 이는 불필요한 오버헤드를 발생시킵니다. HashSet은 해시 테이블과 객체 헤더를 사용하기 때문입니다.
대신 EnumSet을 사용하세요. 내부적으로 비트 벡터(bit vector)를 사용합니다. 매우 빠르고 메모리 사용량도 거의 없습니다. 플레이어 능력치나 설정과 같은 플래그 기반 시스템에 완벽합니다.
- 타입 안정성(type-safe)을 보장합니다.
HashSet보다 훨씬 빠릅니다.- 비트 연산을 알아서 처리해 줍니다.
- 캐시를 위해 ConcurrentHashMap.computeIfAbsent를 사용하세요
캐시를 구축할 때, 여러 스레드가 동일한 값을 중복 계산하는 것을 방지하기 위해 복잡한 코드를 작성하는 경우가 많습니다. 이를 double-checked locking이라고 합니다. 이 방식은 읽기 어렵고 버그가 발생하기 쉽습니다.
대신, 단 한 줄이면 충분합니다:
cache.computeIfAbsent(key, this::expensiveCalculation);
이 메서드는 원자적(atomic)입니다. 여러 스레드가 동시에 동일한 키를 요청하더라도 계산이 단 한 번만 실행되도록 보장합니다. 이를 통해 버그를 줄이고 의도를 명확하게 전달할 수 있습니다.
이 도구들이 중요한 이유:
- 예측 가능한 성능: 숨겨진 메모리 비용을 방지할 수 있습니다.
- 버그 감소: 멀티스레드 코드에서의 경합 조건(race condition) 문제에서 벗어날 수 있습니다.
- 명확한 코드: 다른 개발자들이 여러분의 설계를 즉시 이해할 수 있습니다.
프레임워크와 싸우지 마세요. 프레임워크를 활용하여 더 가볍고 안전한 코드를 작성하기 시작하세요.