자바 HashMap 값으로 정렬 — value 기준 오름차순·내림차순 정렬
HashMap은 저장 순서를 보장하지 않기 때문에 값(value)으로 정렬하려면 Map 자체를 정렬하는 게 아니라, entrySet()을 스트림으로 정렬한 뒤 순서를 지키는 LinkedHashMap에 다시 담아야 합니다. 핵심은 세 줄입니다: Map.Entry.comparingByValue()로 값 기준 정렬, 내림차순은 .reversed(), 결과는 Collectors.toMap(..., LinkedHashMap::new)로 수집. (Java 21 기준, 이 API는 Java 8부터)
HashMap은 왜 값으로 정렬이 안 되나
HashMap은 내부적으로 해시 버킷에 저장하므로 키의 순서도, 값의 순서도 정의되지 않습니다. 그래서 "HashMap을 값으로 정렬한다"는 말은 사실 "정렬된 순서로 담은 새 Map을 만든다"는 뜻입니다.
흔한 오해가 TreeMap을 쓰면 되지 않느냐는 것인데, TreeMap은 키(key) 기준으로만 정렬합니다. 값 기준 정렬에는 쓸 수 없습니다. 정렬 결과의 순서를 유지하려면 LinkedHashMap이 필요합니다.
값 오름차순·내림차순 정렬 (Stream)
가장 표준적인 방법은 스트림입니다. Map.Entry.comparingByValue()가 값 비교자를 만들어 줍니다.
Map<String, Integer> scores = new HashMap<>();
scores.put("kim", 80);
scores.put("lee", 95);
scores.put("park", 70);
scores.put("choi", 95);
// 값 오름차순 → 순서 유지 위해 LinkedHashMap
Map<String, Integer> asc = scores.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue,
(a, b) -> a, LinkedHashMap::new));
// 값 내림차순
Map<String, Integer> desc = scores.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue,
(a, b) -> a, LinkedHashMap::new));
System.out.println(asc);
System.out.println(desc);
실제 실행 결과(Java 21):
{park=70, kim=80, choi=95, lee=95}
{choi=95, lee=95, kim=80, park=70}
.reversed() 앞의 Map.Entry.<String, Integer>comparingByValue()처럼 타입을 명시하는 이유는, 제네릭 타입 추론이 reversed() 체이닝에서 막히기 때문입니다. 타입을 안 적으면 컴파일 에러가 납니다.
값이 같을 때 키로 2차 정렬
위 결과에서 값이 95로 같은 choi와 lee의 순서는 보장되지 않습니다. 동점일 때 순서를 확정하려면 thenComparing으로 2차 기준(키)을 답니다.
Map<String, Integer> tie = scores.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed()
.thenComparing(Map.Entry.comparingByKey()))
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue,
(a, b) -> a, LinkedHashMap::new));
System.out.println(tie);
// {choi=95, lee=95, kim=80, park=70} ← 값 내림차순, 동점은 키 오름차순
toMap 함정 — LinkedHashMap을 빼면 순서가 사라진다
가장 자주 하는 실수는 Collectors.toMap에 맵 공급자(LinkedHashMap::new)를 빼먹는 것입니다. 인자 2개짜리 toMap은 결과를 HashMap에 담기 때문에, 애써 정렬해도 순서가 흐트러집니다.
Map<String, Integer> m = new HashMap<>();
m.put("banana", 30); m.put("apple", 50); m.put("cherry", 10);
m.put("date", 40); m.put("fig", 20);
// ❌ toMap 기본형 → HashMap 반환, 정렬 순서 무의미
Map<String, Integer> bad = m.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
// ✅ 4번째 인자로 LinkedHashMap 지정 → 순서 유지
Map<String, Integer> good = m.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
(x, y) -> x, LinkedHashMap::new));
System.out.println(bad);
System.out.println(good);
실행 결과(Java 21) — bad는 값 순서와 무관하게 흐트러집니다:
bad = {date=40, banana=30, apple=50, cherry=10, fig=20}
good = {apple=50, date=40, banana=30, fig=20, cherry=10}
(x, y) -> x는 키 충돌 시 병합 함수입니다. 4번째 인자로 맵 종류를 지정하려면 toMap 규칙상 병합 함수를 반드시 함께 넘겨야 합니다. 값 정렬 상황에선 키가 중복될 일이 없어 아무거나 반환하면 됩니다.
Java 8 이전 스타일 — List로 정렬
스트림 대신 엔트리를 List로 꺼내 정렬하는 방식도 있습니다. 순서만 필요하고 다시 Map으로 담을 필요가 없을 때 간단합니다.
List<Map.Entry<String, Integer>> list = new ArrayList<>(scores.entrySet());
list.sort(Map.Entry.comparingByValue()); // 오름차순
// list.sort(Map.Entry.<String,Integer>comparingByValue().reversed()); // 내림차순
for (Map.Entry<String, Integer> e : list) {
System.out.println(e.getKey() + " = " + e.getValue());
}
// park = 70 / kim = 80 / choi = 95 / lee = 95
참고로 Map에 값이 있을 때만 계산해 넣는 상황이라면 정렬과 별개로 자바 computeIfAbsent vs getOrDefault 차이도 함께 알아두면 Map 다루기가 훨씬 수월해집니다.
자주 묻는 질문 (FAQ)
Q. 정렬 결과를 왜 다시 HashMap에 담으면 안 되나요?
HashMap은 순서 개념이 없어, 담는 순간 정렬이 무의미해집니다. 순서를 유지하려면 LinkedHashMap에 담아야 합니다.
Q. TreeMap으로 값 정렬을 할 수는 없나요?
TreeMap은 키 기준 정렬 전용입니다. Comparator를 넘겨도 비교 대상은 키입니다. 값 기준 정렬에는 위의 스트림 방식이나 List 방식을 씁니다.
Q. 값이 같은 항목의 순서가 매번 다릅니다.
값만으로 정렬하면 동점 항목의 순서는 정의되지 않습니다. thenComparing(Map.Entry.comparingByKey())로 2차 기준을 주면 결과가 고정됩니다.
마무리
정리하면, HashMap 값 정렬의 공식은 ① entrySet().stream() → ② sorted(Map.Entry.comparingByValue())(내림차순은 .reversed()) → ③ Collectors.toMap(..., LinkedHashMap::new) 세 단계입니다. LinkedHashMap과 동점 처리(thenComparing) 두 가지만 놓치지 않으면 됩니다. 위 예제는 모두 Java 21에서 실행해 출력까지 확인한 코드입니다.
📚 참고 출처 (2026년 7월 21일 확인)
· Oracle Java SE 21 API — Map.Entry
· Oracle Java SE 21 API — Collectors.toMap
· 예제 코드는 Java 21.0.1에서 직접 실행해 출력 확인

COMMENTS