HOWTO · Java

Java에서 집합 정렬

이 자습서에서는 Java에서 집합을 정렬합니다.

이 페이지의 내용

HashSet는 순회 순서를 보장하지 않으므로 요소가 정렬되어 있다고 간주해서는 안 됩니다. 정렬된 값을 얻으려면 집합을 List에 복사해 그 복사본을 정렬하거나, 값을 TreeSet에 저장하거나, 출력할 때 스트림을 정렬하면 됩니다. 집합을 정렬하는 데 인덱스 기반 접근은 필요하지 않습니다. 정렬 작업은 컬렉션이나 그 요소를 대상으로 합니다. 해시 코드는 해시 기반 조회에 사용되며 구현 세부 사항에 따라 관찰되는 HashSet의 순회 순서에 영향을 줄 수 있지만, Java는 HashSet의 순회 순서를 보장하지 않습니다.

Set을 변환하거나 정렬된 저장소가 필요한 이유

Set 인터페이스가 나타내는 것은 특정 순서가 아니라 요소의 고유성입니다. 특히 HashSet는 삽입 순서와 다른 순서로 순회될 수 있고, 집합이 바뀌면 그 순서도 달라질 수 있습니다. 디버깅 중 우연히 보았거나 프로그램을 한 번 실행했을 때 나타난 순서에 의존하지 마세요.

정렬에는 순서 규칙이 필요합니다. 숫자와 문자열에는 Java의 자연 순서가 적합한 경우가 많습니다. 직접 만든 타입은 자연 순서를 정의하거나, 작업에 필요한 필드를 비교하는 Comparator를 제공할 수 있습니다. 필요한 순서에 따라 저장 방식을 선택하세요. 인덱스 접근이나 목록 연산에는 정렬된 목록이 유용하고, TreeSet는 집합의 특성을 유지하며, 정렬된 스트림은 처리 파이프라인의 일부로 활용하기 좋습니다.

Set을 List로 복사해 정렬하기

요소를 ArrayList에 복사한 다음 목록에 Collections.sort를 호출하세요. 이렇게 하면 원본 집합은 그대로 두면서 선택한 순서대로 순회되는 목록을 얻을 수 있습니다. 예제에서는 자연 순서가 있는 정수를 사용합니다. 다른 타입이라면 원하는 순서를 나타내는 비교자로 정렬하세요. 이 작성 단계에서는 예제를 실행하지 않았습니다. 아래 설명은 Java의 순서 지정 규칙에 따른 동작을 설명합니다.

이 예제를 실행하세요:

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Set<Integer> numbers = new HashSet<>(Arrays.asList(3, 6, 4));
        List<Integer> sorted = new ArrayList<>(numbers);
        Collections.sort(sorted);
        System.out.println(sorted);
    }
}

복사한 목록의 정수는 오름차순으로 정렬됩니다. 목록 연산이 필요하거나 원본 집합의 내용과 표현을 유지하려면 이 방법을 사용하세요.

TreeSet으로 요소를 정렬된 상태로 유지하기

TreeSet는 자연 순서나 지정된 비교자에 따라 고유한 요소를 저장하는 클래스입니다. 집합으로 인스턴스를 만들면 요소를 복사하고 순회가 해당 순서를 따르도록 합니다. 원본 집합은 변경되지 않습니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

public class Main {
    public static void main(String[] args) {
        Set<Integer> numbers = new HashSet<>(Arrays.asList(3, 6, 4));
        TreeSet<Integer> sorted = new TreeSet<>(numbers);
        System.out.println(sorted);
    }
}

순회는 숫자의 자연 순서를 따릅니다. 다른 순서가 필요하면 TreeSet 생성자에 Comparator를 전달할 수 있습니다. TreeSet는 순서 규칙을 기준으로 두 요소가 중복인지 판단합니다. 비교 결과가 0이면 두 요소를 같은 집합 요소로 취급합니다. 특히 순서 규칙이 equals와 일치하지 않을 때는 이 비교 방식이 의도한 고유성 규칙에 맞는지 확인하세요.

스트림에서 Set 요소 정렬하기

stream()를 호출한 뒤 sorted()를 호출하면 요소가 정렬된 순서로 다음 처리 단계에 전달됩니다. 이 작업은 원본 집합의 순서를 바꾸거나 집합을 수정하지 않습니다. 파이프라인의 나머지 단계에서 사용할 정렬된 스트림을 만듭니다. 자연 순서가 적합하지 않다면 sorted(comparator)에 비교자를 전달하세요. 이 방법을 사용하려면 프로젝트 런타임이 스트림을 지원해야 합니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Set<Integer> numbers = new HashSet<>(Arrays.asList(20, 4, 15));
        numbers.stream().sorted().forEach(System.out::println);
    }
}

스트림은 값을 오름차순으로 내보내며, numbers는 순회 순서가 보장되지 않는 집합으로 남습니다. forEach 같은 연산으로 정렬된 값을 바로 처리할 때 편리합니다. 이후에 재사용할 정렬된 목록이 필요하면 목록으로 수집하세요.

방식 선택과 한계 이해하기

다음 작업에 List가 필요하거나 목록 전용 연산을 사용하려는 경우, 또는 원본 집합을 별도로 유지하려는 경우에는 정렬된 복사본을 사용하세요. 이후 순회에서도 정렬된 상태를 유지하면서 집합의 고유성도 필요하다면 TreeSet를 사용하세요. 정렬이 파이프라인의 일부이고 그 안에서 정렬된 요소만 처리하면 된다면 stream().sorted()를 사용하세요.

세 방법 모두 요소끼리 비교할 수 있어야 합니다. 적절한 자연 순서를 사용하거나 비교자를 제공하세요. TreeSet에는 비교 결과가 어떤 값을 중복으로 취급할지도 결정한다는 추가 제약이 있습니다. 목록이나 스트림 정렬은 비교 결과가 같은 요소를 제거하지 않습니다. 이 작업들은 원본 HashSet를 정렬된 집합으로 바꾸지 않습니다. 한 번 보여 주기만 하면 된다면 다른 곳에서 사용하는 컬렉션을 변경하는 대신 복사본을 정렬하거나 값을 스트림으로 처리하세요.