본문 바로가기

개발새발 개발자/Java

[Java] 데이터 정렬 - Comparable, Comparator

애플리케이션을 만들거나 알고리즘 문제를 풀다보면 데이터를 순서대로 정렬할 일이 많아요. 물론 직접 알고리즘을 구현해도 되겠지만 자바에서 제공하는 기능을 편리하게 이용할 수도 있답니다. 이때 자주 사용하는 Comparable, Comparator에 대해 알아볼게요!

 

1. Comparable: 오름차순으로 정렬하기

Comparable은 기본적인 정렬을 수행합니다. 즉, 숫자라면 오름차순, 문자라면 알파벳 순으로 정렬합니다. Primitive 타입이나 Integer, String, List 등등 정렬이 필요한 데이터는 모두 Comparable 인터페이스를 implement합니다. 즉, Comparable이 제공하는 정렬 기능을 사용할 수 있다는 거죠!

 

1) Arrays.sort(): 기본 타입 배열

먼저, 기본 타입 배열 byte[], char[], double[], float[], int[], long[], short[]을 정렬하는 방법입니다.

 

import java.util.Arrays;

public class SortingWithArrays {
    public static void main(String[] args) {
        String[] s = {"b", "A", "D", "C", "a", "F"};
		
        /* 대소문자 구분한 정렬 */
        Arrays.sort(s);
        System.out.println(Arrays.toString(s));		// [A, C, D, F, a, b]
    }
}

숫자라면 1, 2, 3...순으로, 문자열이라면 알파벳 순으로 정렬되는 걸 볼 수 있습니다.

 

import java.util.Arrays;

public class SortingWithArrays {
    public static void main(String[] args) {
        String[] s = {"b", "A", "D", "C", "a", "F"};

        /* 대소문자 구분 없이 정렬 */
        Arrays.sort(s, String.CASE_INSENSITIVE_ORDER);
        System.out.println(Arrays.toString(s));     // [a, A, b, C, D, F]
    }
}

만약 대/소문자 구분없이 출력하고 싶다면, String 클래스의 CASE_INSENSITIVE_ORDER를 사용합니다.

 

import java.util.Arrays;

public class SortingWithArrays {
    public static void main(String[] args) {
        String[] s = {"b", "A", "D", "C", "a", "F"};

        /* 역순으로 정렬 */
        for(int i=0; i<s.length/2; i++){
            String tmp = s[i];
            s[i] = s[(s.length - 1) - i];
            s[(s.length - 1) - i] = tmp;
        }
        System.out.println(Arrays.toString(s));     // [b, a, F, D, C, A]
    }
}

거꾸로 정렬하고 싶다면 for 문으로 배열 안에서 위치를 바꿔준 뒤 출력합니다.

 

 

2) Arrays.sort(): 객체 타입 정렬

public class SortingWithComparable {
    public static void main(String[] args) {
        Actor[] arr = new Actor[] {
                new Actor("박보검", 1993),
                new Actor("유승호", 1991),
                new Actor("차은우", 1996),
                new Actor("서강준", 1994)
        };

        Arrays.sort(arr);
        for(Actor result:arr){
            System.out.println(result);
        }
    }

객체를 위와 같이 기본 타입 배열을 다루듯 sort() 한다면 어떻게 될까요?

 

Exception in thread "main" java.lang.ClassCastException: Actor cannot be cast to java.lang.Comparable
	at java.util.ComparableTimSort.countRunAndMakeAscending(ComparableTimSort.java:320)
	at java.util.ComparableTimSort.sort(ComparableTimSort.java:188)
	at java.util.Arrays.sort(Arrays.java:1246)
	at UsefulFunctions.Sorting.SortingWithComparable.main(SortingWithComparable.java:18)

슬프게도 이렇게 Exception이 발생합니다 ㅠㅠ 내용을 읽어보면 Actor라는 객체를 Comparable에 적용할 수 없다고 하네요. 분명 정렬이 필요한 데이터들은 모두 Comparable 인터페이스를 implement한다고 했는데 말이죠!

 

public class SortingWithComparable {
    public static void main(String[] args) {

        Actor[] arr = new Actor[] {
                new Actor("박보검", 1993),
                new Actor("유승호", 1991),
                new Actor("차은우", 1996),
                new Actor("서강준", 1994)
        };

        Arrays.sort(arr);
        for(Actor result:arr){
            System.out.println(result);
        }
    }

    static class Actor implements Comparable<Object>{
        String name;
        int birthYear;

        // Constructor
        public Actor(String name, int birthYear) {
            this.name = name;
            this.birthYear = birthYear;
        }

        public String toString() {
            return name + " " + birthYear + "년생";
        }

        // Comparable 인터페이스의 compareTo를 overriding
        @Override
        public int compareTo(Object o) {
            // Actor 객체의 name을 비교
            return name.compareTo(((Actor) o).name);
        }
    }
}

객체를 정렬하려면 Comparable 인터페이스에 있는 compareTo() 메서드를 다시 정의해줘야 해요. 일반적인 객체 뿐만 아니라 Integer, Double 같은 Wrapper Class도 여기 해당됩니다.

 

우리는 Actor 객체의 name으로 비교해달라고 해봅시다.

 

박보검 1993년생
서강준 1994년생
유승호 1991년생
차은우 1996년생

짠! Actor 객체가 이름을 기준으로 오름차순 정렬되었어요. 

 

 

3) Collections.sort(): List 타입 정렬

Arrays.sort()와 마찬가지로 ArrayList, LinkedList, Vector에 있는 요소를 오름차순으로 정렬합니다.

 

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SortingWithCollections {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("z");
        list.add("a");
        list.add("A");
        list.add("c");

        System.out.println(list);   // [z, a, A, c]
    }
}

List를 만들고 바로 출력하면 그냥 add한 순서대로 나오는 게 기본이에요.

 

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SortingWithCollections {
    public static void main(String[] args) {

        List<String> list = new ArrayList<>();
        list.add("z");
        list.add("a");
        list.add("A");
        list.add("c");
        
        /* 대소문자 구분해서 정렬 */
        Collections.sort(list);
        System.out.println(list);       // [A, a, c, z]
    }
}

Collections.sort()를 사용하면 대/소문자를 구별하면서 오름차순 정렬을 할 수 있습니다.

 

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SortingWithCollections {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("z");
        list.add("a");
        list.add("A");
        list.add("c");

        /* 대소문자 구분 없이 알파벳 순 정렬 */
        Collections.sort(list, String.CASE_INSENSITIVE_ORDER);
        System.out.println(list);    // [a, A, c, z]
    }
}

CASE_INSENSITIVE_ORDER도 마찬가지로 사용할 수 있어요.

 

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SortingWithCollections {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("z");
        list.add("a");
        list.add("A");
        list.add("c");

        /* 대소문자 구분해서 역순으로 정렬 */
        Collections.sort(list, Collections.reverseOrder());
        System.out.println(list);     // [z, c, a, A]
    }
}

Arrays.sort()와는 달리, Collections에서 제공하는 reverseOrder()를 사용하면 역순도 쉽게 정렬 가능합니다!

 

2. Comparator: 다른 기준으로 정렬하기

만약 오름차순이 아닌, 다른 방식으로 정렬하고 싶다면 어떻게 해야할까요? 예를 들어 Actor 객체의 배우를 나이가 어린 순서로 즉, 년도를 내림차순으로 정렬해야 한다면요!

 

그 '기준'을 담당하는 것이 바로 Comparator 인터페이스입니다. 정렬하기 전에 기준을 세우는 건 딱 한 번만 일어나는, 일회성인 활동이기 때문에 익명 클래스 형태로 많이 사용해요. 

 

import java.util.Arrays;
import java.util.Comparator;

public class SortingWithComparator {
    public static void main(String[] args) {

        Actor[] arr = new Actor[] {
                new Actor("박보검", 1993),
                new Actor("유승호", 1991),
                new Actor("차은우", 1996),
                new Actor("서강준", 1994)
        };

	// Comparator 구현
        Arrays.sort(arr, new Comparator<Actor>(){
            @Override
            public int compare(Actor o1, Actor o2) {
                int by1 = ((Actor) o1).birthYear;
                int by2 = ((Actor) o2).birthYear;
                return by1 > by2 ? -1 : (by1 == by2 ? 0 : 1);
            }
        });
        
        for(Actor result:arr){
            System.out.println(result);
        }
    }
 }
 // Actor 구현은 생략. 이전 코드를 참고하세요.

구현한 내용을 보면 by1 > by2일 경우 음수를, 같다면 0을, 크다면 1을 리턴하고 있어요. compare()는 리턴값이 양수면 두 객체의 자리를 바꿔주는 역할을 합니다. 즉, 년도가 더 클 수록 맨 처음으로 오게 되는거죠!

 

차은우 1996년생
서강준 1994년생
박보검 1993년생
유승호 1991년생

결과가 잘 출력되는 것을 볼 수 있습니다 :)

 

3. 정리

그럼 지금까지 설명한 내용을 총 정리 해볼게요.

 

인터페이스 목적 메서드 사용법
Comparable 데이터를 비교할 때 Arrays.sort() Primitive 타입
    Arrays.sort(), compareTo() Object, Wrapper Class 타입
compareTo() 오버라이딩
    Collections.sort() ArrayList, LinkedList, Vector 타입
Comparator 비교 기준을 세울 때 compare() compare() 오버라이딩

 

Comparable과 Comparator는 스펠링이 비슷해서 헷갈리기 쉬운 인터페이스인데요, 영어에서 접미사 -or는 무언가를 하는 주체, 행위자를 나타내잖아요! 그러니까 비교를 직접적으로 하도록 도와주는 '기준'을 정의하는 것이라고 생각하니까 쉽게 기억에 남더라구요. 도움이 되셨으면 좋겠어요 :)