Language/Java

[JAVA] 자바의 정렬 인터페이스 Comparable과 Comparator

leegeonwoo 2024. 7. 4. 20:52

자바에서 배열에 대해 정렬을 하고싶다면 어떻게해야할까?
코드를 통해 살펴보자

import java.util.Arrays;  

public class SortMain1 {  
    public static void main(String[] args) {  
        Integer[] array = {3, 2, 1};  
        System.out.println(Arrays.toString(array));  

        System.out.println("기본 정렬 후");  
        Arrays.sort(array);  
        System.out.println(Arrays.toString(array));  
    }  
}

실행결과

[3, 2, 1]
기본 정렬 후
[1, 2, 3]

util패키지의 Arrays.sort를 사용하면 배열의 데이터를 정렬할 수 있다.
정렬을 할 때 사용되는 정렬알고리즘은 다양하게 있지만 자바는 데이터가 32개 이하일 때는 듀얼 피벗 퀵소트를 사용하고, 데이터가 많을 때는 팀소트를 사용한다. 이런 알고리즘의 성능은 평균 O(log N)의 시간복잡도로 제공된다.

만약 배열을 정렬하는데 1->2->3의 순서가아닌 3->2->1과 같이 역순으로 정렬하고싶다면 어떻게 해야할까?
이런 경우에 사용되는 인터페이스가 바로 Comparator이다.

 public interface Comparator<T> {
     int compare(T o1, T o2);

}

Comparatorcompare라는 메서드를 제공하는데 이 메서드는 int타입을 반환한다.
만약 첫번째 인수가 작으면 음수를 반환하고, 두 값이 같다면 0, 첫번쨰 인수가 더 크다면 양수를 반환하게된다. 이 반환값을 기준으로 원하는대로 배열을 정렬시킬 수 있다.

Comparator를 구현하여 오름차순, 내림차순 구현하기


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

public class SortMain2 {  
    public static void main(String[] args) {  
        Integer[] array = {3, 2, 1};  
        System.out.println(Arrays.toString(array));  
        System.out.println("Comparator 비교");  

        Arrays.sort(array, new AscComparator());  
        System.out.println(Arrays.toString(array));  
        System.out.println();  

        Arrays.sort(array, new DescComparator());  
        System.out.println(Arrays.toString(array));  
        System.out.println();  

        Arrays.sort(array, new AscComparator().reversed());  
        System.out.println(Arrays.toString(array));  
    }  

    static class AscComparator implements Comparator<Integer> {  
        @Override  
        public int compare(Integer o1, Integer o2) {  
            System.out.println("o1 = " + o1 + " o2=" + o2);  
            return (o1 < o2) ? -1 : ((o1 == o2) ? 0 : 1);  
        }  
    }  

    static class DescComparator implements Comparator<Integer> {  

        @Override  
        public int compare(Integer o1, Integer o2) {  
            System.out.println("o1 = " + o1 + " o2=" + o2);  
            return (o1 < o2) ? -1 : ((o1 == o2) ? 0 : 1) * -1;  
        }  
    }  
}

Arrays.sort()메서드의 매개변수로 Comparator를 구현한 클래스를 넣어줄 수 있다.
이 때 compare()메서드를 재정의하여 원하는대로 정렬의 기준을 바꿀 수 있다.

참고로 이중 삼항연산자가 어색할 수 있는데 if문으로 변경하면 아래와 같이 구현된다.(오름차순 기준)


int result;
if (o1 < o2) {
    result = -1;
} else if (o1 == o2) {
    result = 0;
} else {
    result = 1;
}

이렇게 Comparator(비교자)를 사용하여 정렬의 기준을 자유롭게 변경할 수 있다.

문자도 숫자를 정렬하는 개념과 같이 ABC(오름차순), CBA(내림차순)과 같이 정렬할 수 있으며 자바에서 Integer클래스와 String클래스에 이미 구현을 해놓았다.
하지만 만약 내가 정의한 클래스 예를들면 Member, Order와 같은 객체들은 어떻게 정렬할 수 있을까?
Member클래스를 정렬한다고하면 id값으로 정렬을 할수도있고, name값으로 정렬을 할수도있을 것이다.
이 때는 Comparable인터페이스를 구현하면 객체를 비교할수 있도록하여 자유롭게 정렬할 수 있도록 한다.

 public interface Comparable<T> {
     public int compareTo(T o);

}

Comparable인터페이스는 compareTo()라는 메서드를 제공한다. 이 메서드를 내가 만든 객체(Member)에서 재정의하여 정렬의 기준을 정할 수 있다.
compareTo메서드도 int타입으로 반환하는데, 현재 객체가 인수로 주어진 객체보다 더 작으면 음수를, 두 객체의 크기가 같다면 0, 현재 객체가 인수로 주어진 객체보다 더 크면 양수를 반환한다.

회원의 나이를 정렬의 기준값으로 정의

public class MyMember implements Comparable<MyMember>{  
    private String id;  
    private int age;  

    public MyMember(String id, int age) {  
        this.id = id;  
        this.age = age;  
    }  

    public String getId() {  
        return id;  
    }  

    public int getAge() {  
        return age;  
    }  


    @Override  
    public int compareTo(MyMember o) {  
        return this.age < o.age ? -1  : ((this.age == o.age) ? 0 : 1);  
    }  

    @Override  
    public String toString() {  
        return "MyMember{" + "id='" + id + '\'' + ", age=" + age + '}';  
    }  
}

compareTo()메서드만 살펴보자
위에서 정수값을 정렬할 때와 같으며 단지 비교하는 대상을 객체자신의 age필드와 파라미터의 age로 변경했을 뿐이다.
이렇게 Comparable을 통해 구현한 순서를 자연순서라고 한다.

import java.util.Arrays;  

public class SortMain3 {  
    public static void main(String[] args) {  
        MyMember aMember = new MyMember("a", 30);  
        MyMember bMember = new MyMember("b", 20);  
        MyMember cMember = new MyMember("c", 10);  

        MyMember[] members = {aMember, bMember, cMember};  
        System.out.println(Arrays.toString(members));  
        System.out.println();  

        System.out.println("재정의한 Comparable로 정렬");  
        Arrays.sort(members);  
        System.out.println(Arrays.toString(members));  
    }  
}

실행결과

[MyMember{id='a', age=30}, MyMember{id='b', age=20}, MyMember{id='c', age=10}]

재정의한 Comparable로 정렬
[MyMember{id='c', age=10}, MyMember{id='b', age=20}, MyMember{id='a', age=30}]

실행결과를 살펴보면 나이순서대로 데이터가 정렬된 것을 확인할 수 있다.

여기서 핵심은 MyMember클래스 안에 Comparable을 구현한 재정의 메서드 compareTo는 해당 클래스타입의 배열을 정렬할 때 기본값으로 사용된다.

만약 특별한 경우에 나이의 역순으로 정렬하고싶다거나, id를 기준으로 정렬을 하고싶다면 Comparator를 구현하는 구현클래스를 따로 만들어주어야한다.

정리하자면

  • Comparable인터페이스는 클래스 자체에 기본 정렬 기준을 정의하며 compareTo메서드를 재정의 하면 Arrays.sort()메서드를 호출할 때의 기준을 정하게된다.
  • Comparator인터페이스는 기본 정렬 기준과는 다른 기준으로 정렬을 할 때 사용한다. compare메서드를 재정의한 뒤, Arrays.sort(정렬하고자하는 배열, new compare()를 재정의한 정렬기준 클래스)와 같이 인자를 주면 원하는 정렬기준으로 정렬할 수 있다.

멤버별 나이의 역순으로 정렬

public class DescAgeComparator implements Comparator<MyMember> {  

    @Override  
    public int compare(MyMember o1, MyMember o2) {  
        return (o1.getAge() < o2.getAge()) ? -1 : ((o1.getAge() == o2.getAge()) ? 0 : 1) * -1;  
    }  
}
import java.util.Arrays;  

public class SortMain3 {  
    public static void main(String[] args) {  
        MyMember aMember = new MyMember("a", 30);  
        MyMember bMember = new MyMember("b", 20);  
        MyMember cMember = new MyMember("c", 10);  

        MyMember[] members = {aMember, bMember, cMember};  
        System.out.println(Arrays.toString(members));  
        System.out.println();  

        System.out.println("재정의한 Comparable로 정렬");  
        Arrays.sort(members);  
        System.out.println(Arrays.toString(members));  

        Arrays.sort(members, new DescAgeComparator());  
        System.out.println("나이의 역순으로 정렬");  
        System.out.println(Arrays.toString(members));  
    }  
}

실행결과

나이의 역순으로 정렬
[MyMember{id='a', age=30}, MyMember{id='b', age=20}, MyMember{id='c', age=10}]

멤버의 아이디를 기준으로 오름차순정렬

import java.util.Comparator;  

public class IdComparator implements Comparator<MyMember> {  

    @Override  
    public int compare(MyMember o1, MyMember o2) {  
        return o1.getId().compareTo(o2.getId());  
    }  
}

이미 재정의 된 MyMembercompareTo메서드를 사용하여 간단하게 정렬기준을 재정의할 수 있다.
또한 역순정렬을 원할 때 따로 클래스를 정의할 필요없이 reverse()메서드를 사용하여 간단하게 역순으로 정렬할 수 있다.

Id로 오름차순/내림차순

import java.util.Arrays;  

public class SortMain3 {  
    public static void main(String[] args) {  
        MyMember aMember = new MyMember("a", 30);  
        MyMember bMember = new MyMember("b", 20);  
        MyMember cMember = new MyMember("c", 10);  

        MyMember[] members = {aMember, bMember, cMember};  
        System.out.println(Arrays.toString(members));  
        System.out.println();  

        Arrays.sort(members, new IdComparator());  
        System.out.println("아이디순으로 오름차순");  
        System.out.println(Arrays.toString(members));  

        System.out.println("아이디순으로 내림차순");  
        Arrays.sort(members, new IdComparator().reversed());  
        System.out.println(Arrays.toString(members));  
    }  
}

실행결과

아이디순으로 오름차순
[MyMember{id='a', age=30}, MyMember{id='b', age=20}, MyMember{id='c', age=10}]
아이디순으로 내림차순
[MyMember{id='c', age=10}, MyMember{id='b', age=20}, MyMember{id='a', age=30}]

참고로 Arrays.sort를 호출할 때 Comparator가 구현되어있지 않으면 자동적으로 클래스 내부에 기본으로 정의된 Comparable을 사용하는데 Comparable도 정의되어있지 않다면 예외가 발생한다.


#### 컬렉션프레임워크의 정렬

컬렉션프레임워크도 배열과 같은 개념으로 정렬기준을 제공한다.
물론 순서가 있는 List같은 자료구조에만 제공되며 Map, Set과 같은 자료구조는 순서가 없기때문에 정렬을 제공하지 않는다.
Tree자료구조 같은경우에는 데이터를 보관할 때 데이터를 정렬하면서 보관하기때문에 정렬기준을 반드시 명시해야한다.

  • Collections.sort(list)
    • 기본 정렬을 제공한다.
    • 객체지향을 강조하기위해서는 잘 사용되지 않는다.
  • list.sort(null)
    • 별도의 비교자가 없기때문에 Comparable로 비교해서 정렬한다
  • Collecions.sort(list, new IdComparator)
    • 별도의 비교자로 비교할 수 있지만 이 메서드 또한 잘 사용되지 않고 list.sort()를 사용한다.
  • list.sort(new IdComparator())
    • 전달한 비교자로 비교한다.
728x90