[Java] 기본형 배열과 컬렉션 자료형 정렬하기

Jiwoo Kim·2021년 3월 18일
1

머리에 자바 넣기

목록 보기
1/4
post-thumbnail
post-custom-banner

정렬

직접 정렬 알고리즘을 구현하여 정렬할 수도 있지만, 자바에서 기본적으로 제공하는 정렬 기능을 활용하면 아주 편하게 코드를 작성할 수 있다. 여기저기 자유자재로 쓸 수 있도록 방법을 쫙 정리해보았다.


배열 정렬

Arrays.sort()

배열은 Arrays 클래스의 static 메서드 중 sort()를 사용하여 쉽게 정렬할 수 있다.

static void sort(Object[] a)

배열만을 인자로 넘겨 sort()를 사용하는 경우, Comparable interface가 구현된 Object의 natural ordering에 따라 정렬된다. Comparable interface를 구현하지 않은 객체는 이 메서드를 사용할 수 없으며, Comparable을 구현하거나 아래의 메서드를 사용해야 한다.

Number, Boolean, Character, String 등 우리가 자주 사용하는 기본 자료형들은 기본적으로 Comparable을 구현하여 compareTo()를 통해 오름차순으로 정렬하도록 정의되어 있다. Primitive type역시 오토 박싱 & 언박싱을 통해 정렬이 된다.

int[] arr = {1,3,2,4,5};
Arrays.sort(arr);	// [1, 2, 3, 4, 5]
static <T> void sort(T[] a, Comparator<? super T> c)

Comparable이 구현되지 않은 클래스를 정렬하거나, natural ordering이 아닌 별도의 기준으로 배열을 정렬하려면 두 번째 인자로 Comparator 구현체를 넘겨 사용해야 한다. 직접 구현체를 작성하여 인자로 넘기거나, lambda expression으로 인자를 줘야 하는데, 람다식이 훨씬 가독성이 좋고 간결하기 때문에 현업에서 많이 사용한다. Comparator에 관한 내용은 여기서 확인할 수 있다.

Integer[] arr = {1, 3, 5, 2, 4};

// 1. Comparator 구현체
Arrays.sort(arr, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
    	return o2 - o1;
    }
});

// 2. Lambda Expression
Arrays.sort(arr, (o1, o2) -> o2 - o1);
static void sort(Object[] a, int fromIndex, int toIndex)

범위를 지정하여 객체를 정렬하고 싶을 때는 index를 인자로 넘겨 정렬할 수 있다. 주어진 배열 afromIndex element부터 toIndex - 1까지의 element를 정렬한다. 즉, toIndex는 배열하고자 하는 element의 index + 1으로 인자를 넘겨야 한다는 것이다.

int[] arr = {99, 3, 2, 1, 100};
Arrays.sort(arr, 1, 4);		// [99, 1, 2, 3, 100]


컬렉션 정렬

Collections & Collection

collection의 element를 정렬하기 위해서는, 우선 대상 타입이 List interface를 구현한 ordered collection 타입이어야 한다. Map이나 Set같이 순서가 없는 타입은 아래의 방식으로는 정렬할 수 없고, LinkedHashMap, SortedMap과 같은 별도의 자료형을 활용해야 한다.

방법은 두 가지가 있는데, Collections의 static 메서드를 사용하거나, List interface의 sort() 인스턴스 메서드를 사용하면 된다.

Collections

This class consists exclusively of static methods that operate on or return collections.

설명에서 알 수 있듯이, Collections는 컬렉션 타입 대상의 여러가지 편리한 동작들을 static 메서드로 모아 놓은 클래스다. 인터페이스가 아니고 클래스이기 때문에 인자로 정렬하고자 하는 컬렉션을 넘겨 사용한다.

static <T extends Comparable<? super T>> void sort(List<T> list)

Comparable interface가 구현된 클래스의 경우 별도의 Comparator 구현체 없이 바로 list만 인자로 넘겨 정렬할 수 있다. 사용 예는 아래와 같다.

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(5);
list.add(3);
Collections.sort(list);	// [1, 3, 5]

이렇게 list만 단일 인자로 넘기면, Collections는 내부적으로 instance 메서드인 list.sort(null)을 호출하여 정렬한다. list.sort()에 대한 내용은 아래에서 확인할 수 있다.

static <T> void sort(List<T> list, Comparator<? super T> c)

Comparable interface를 구현하지 않은 클래스이거나, 구현했더라도 다른 정렬 기준을 부여하고 싶은 경우 두 번째 인자로 Comparator 구현체를 넘겨 element를 정렬할 수 있다. 이 메서드 역시 위의 메서드와 마찬가지로 list.sort()를 내부적으로 호출하여 element를 정렬한다. 사용 예는 아래와 같다.

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(5);
list.add(3);
Collections.sort(list, ((o1, o2) -> o2 - o1));	// [5, 3, 1]

Collection

The root interface in the collection hierarchy. A collection represents a group of objects, known as its elements.

Collection은 collection 구조의 가장 최상위 interface로서, element를 다루기 위한 여러 인스턴스 메서드를 제공한다. 그 중 ordered collectoin을 의미하는 List interface에서는 정렬을 위한 instance 메서드를 제공한다.

default void sort(Comparator<? super E> c)

List의 메서드가 Collections의 static 메서드와 다른 점은 Comparator 구현체를 필수 인자로 넘겨줘야 한다는 것이다.

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(5);
list.add(3);
list.sort(Comparator.naturalOrder());	// [1, 3, 5]

하지만, 인자에 null을 넘겨 줄 수 있는데, 이 경우 자동으로 Comparable을 구현한 클래스의 natural ordering으로 정렬된다.

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(5);
list.add(3);
list.sort(null);	// [1, 3, 5]


Comparator

Comparator@FuntionalInterface로, compare()equal()을 abstract 메서드로 갖고 있다. 주로 compare() 메서드를 구현한 구현체를 정렬 메서드의 인자로 넘겨 기준으로 활용한다. 참고로, Comparator의 generic type <T>는 primitive type을 허용하지 않는다.

또한, Comparator 클래스는 몇 가지 유용한 static 메서드를 제공한다. 이들을 활용하면 직접 구현체나 람다식을 작성하지 않고도 보다 간결하게 객체를 정렬할 수 있다.

Comparable이 구현되어 있지 않은 클래스는 직접 Comparator 구현체를 인자로 추가로 넘겨 주지 않는 이상, 아래 메서드들을 활용할 수 없다. natural ordering이 정의되어 있어야 이를 기반으로 변환된 Comparator을 생성할 수 있기 때문이다.

static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor)

Integer를 오름차순으로 정렬하는 Comparator 구현체를 반환한다. 이 때 정렬에 사용하는 int 값은 인자로 받은 ToIntFunction에 따라 객체에서 추출된다.

일례로, 아래의 costs와 같은 int 2차원 배열을 특정 index 값을 기준으로 정렬하려면 comparingInt()를 이렇게 사용하면 된다.

int[][] costs = {{0,1,4}, {0,2,2}, {1,2,5}};
Arrays.sort(costs, (Comparator.comparingInt(o -> o[2])));	
				// [[0,2,2], [0,1,4], [1,2,5]]

comparingInt 말고도 double, long 타입도 위의 예시처럼 간단하게 구현 가능하다. 그 외의 자료형 Comparable interface가 구현되어 있는 경우에 한해 comparing()을 활용할 수 있다.

static <T extends Comparable<? super T>> Comparator<T> reverseOrder()

Comparable이 구현된 클래스의 인스턴스를 natural ordering의 reversed, 즉 내림차순으로 정렬하는 Comparator 구현체를 반환한다.

아래와 같이 Comparator 구현체를 인자로 요구하는 메서드에 이를 활용하면 굳이 구현체나 람다식을 작성할 필요 없이 내림차순으로 간단하게 정렬할 수 있다.

Integer[] arr = {99, 3, 5, 1, 1000};
Arrays.sort(arr, Comparator.reverseOrder());	// [1000, 99, 5, 3, 1]

참고

본 글은 Java SE 14 & JDK 14 공식 문서를 기준으로 작성되었습니다.

post-custom-banner

0개의 댓글