2 분 소요

Comparable 과 Comparator

Steram 정리를 하다가 Comparator를 보게 됐는데 생각해 보니 정렬을 할 때 자주 사용되지만 자세히는 잘 모르고 있다.

사용자가 정의한 정렬 기준에 맞춰 정렬해야 하는 경우가 있다.

  • ex) 좌표를 x좌표가 증가하는 순, x좌표가 같다면 y좌표가 감소하는 순으로 정렬
  • ex) 국어 점수는 증가하는 순, 수학 점수는 감소하는 순으로 정렬

객체의 정렬 기준을 명시하는 두 가지 방법으로 Comparable 과 Comparator가 있다.



Interface Comparable

정렬 수행 시 기본적으로 적용되는 정렬 기준이 되는 메소드를 정의하는 인터페이스이다.

Java에서 제공되는 정렬이 가능한 클래스(Interger, Double, String)들은 모두 Comparable 인터페이스를 구현하고 있으며 정렬을 할 때 이에 맞게 정렬이 수행된다.

//package: java.lang.Comparable
//Integer, Double 클래스는 오름차순 정렬
//String 클래스는 사전 순으로 정렬
public final class Integer extends Number implements Comparable<Integer>{...}
public final class String implements java.io.Serializable, Comparable<String>,CharSequence{...}

정렬할 객체에 Comparable interface를 implemets 후, compareTo() 메소드를 오버라이드하여 구현한다.



compareTo() 메소드는

  • 현재 객체 < 파라미터로 넘어온 객체 : 음수 리턴
  • 현재 객체 == 파라미터로 넘어온 객체 : 0 리턴
  • 현재 객체 > 파라미터로 넘어온 객체 : 양수 리턴
  • 음수 or 0 이면 객체의 자리가 그대로 유지되며 양수인 경우에는 두 객체의 자리가 바뀐다.
//x좌표가 증가하는 순, x좌표가 같으면 y좌표가 감소하는 순으로 정렬
class Point implements Comparable<Point>{
    int x, y;
    
    @Override
    public int compareTo(Point p){
        if(this.x > p.x){
          return 1; //x에는 오름차순
        }
        else if(this.x == p.x){
            if(this.y < p.y){
                return 1;
            }
        }
        return -1;
    }
}

List<Point> pointList = new ArrayList<>();
pointList.add(new Point(x,y));
Collection.sort(pointList);

사용 예시

  • Arrays.sort(array)
  • Collections.sort(list)



Arrays.sort() 와 Collections.sort() 차이가 헷갈려서 정리해야 한다.

1. Arrays.sort()

  • Object[], T[]등 Object Array에서는 TimSort(Merge sort + Insertion Sort)를 사용한다.
//Object[] 예시
public static void sort(Object[] a){
  if(LegacyMergeSort.userRequested){
    lagacyMergeSort(a)
  }
  else{
    ComparableTimeSort.sort(a, 0, a.length,null,0,0 );
  }
}

TimSort는 부분적으로 정렬되어 있을 때 더욱 효과적이다. 거의 정렬이 다 되어있다면 O(N)의 성능을 보여주고 정렬 되어있지 않아도 O(NlogN)의 성능을 보장한다.



  • byte[], char[], double[], int[] 등에 대한 배열 Primitive Array에서는 Dual Pivot QuickSort(Quick Sort + Insertion Sort)를 사용 한다.

    Primitive Array : 기본 자료형에 대한 배열

//int[] 예시
public static void sort(int[] a){
  DualPivotQuicksort.sort(a,0,a.length-1,null,0 ,0);
}

기존 퀵 정렬이 최악일 때 시간 복잡도로 O(N^2)을 갖는데 DualPivotQuicksort.sort()는 최악의 데이터 세트에 대해서도 O(nlogn)을 갖는다.

  • 배열이 크기가 286 이상인 경우 MergeSort 수행
  • 배열이 크기가 286 보다 작은 경우 QuickSort를 MergeSort보다 우선 수행한다.
  • 배열이 크기가 47 보다 작은 경우 InsertionSort를 QuickSort보다 우선 수행한다.
  • byte 배열의 크기가 29보다 큰 경우 CountingSort를 InsertionSort보다 우선 수행한다.
  • short, char 배열의 크기가 3200보다 큰 경우 CountingSort를 QuickSort보다 우선 수행한다.

CountingSort의 시간 복잡도는 O(N)으로 가장 빠르지만 추가적인 배열을 만들어야 하기 때문에 좀 더 효율적인 상황에 따라 바꿔쓴다.

2. Collections.sort()

List Collection 정렬의 경우 사용되고 ArrayList, LinkedList, Vector 등 내부적으로 Arrays.sort()를 사용한다.

Collections.sort에 들어가보면 list.sort를 사용하고 list.sort에서는 Arrays.sort를 호출한다.




Interface Comparator

정렬 가능한 클래스(Comparable 인터페이스를 구현한 클래스)들의 기본 정렬 기준과 다르게 정렬하고 싶을 때 사용하는 인터페이스이다.

주로 익명 클래스로 사용되고 기본적인 정렬 방법인 오름차순 정렬을 내림차순으로 정렬할 때 많이 사용한다.

Comparator interface를 implements 후 compare() 메소드를 오버라이드한 myComparator class를 작성한다.



compare()메소드는

  • 첫 번째 파라미터로 넘어온 객체 < 두 번째 파라미터로 넘어온 객체 : 음수 리턴
  • 첫 번째 파라미터로 넘어온 객체 == 두 번째 파라미터로 넘어온 객체 : 0 리턴
  • 첫 번째 파라미터로 넘어온 객체 > 두 번째 파라미터로 넘어온 객체 : 양수 리턴
  • 음수 or 0 이면 객체의 자리가 그대로 유지되며 양수인 경우에는 두 객체의 자리가 바뀐다.

오름차순 일 때는 Integer.compare(x, y) 내림차순 일때는 Integer.compare(y,x);

//x좌표가 증가하는 순, x좌표가 같은면 y좌표가 감소하는 순으로 정렬
Comparator<Point> myComparator = new Comparator<Point>(){
    @Override
    public int compare(Point p1, Point p2){
        if(p1.x > p2.x){
            return 1;
        }
        else if(p1.x == p2.x){
            if(p1.y <p2.y){
                return 1;
            }
        }
        return -1;
    }
};

List<Point> pointList = new ArrayList<>();
pointList.add(new Point(x,y));
Collections.sort(pointList, myComparator);



사용 예시

  • Arrays.sort(array,myComparator)
  • Collections.sort(list,myComparator)

업데이트: