CORE
HOME > JAVA > J2SE > CORE
2019.05.29 / 24:52

[정렬] Comparable과 Comparator

탁돌이개발자
추천 수 3

자바에서 정렬을 하다 보면 Comparable과 Comparator를 자주 마주치게 된다. 본 게시글에서는 Comparable과 Comparator이 무엇이며 어떻게 다른지를 먼저 살펴보고, 각각의 사용법을 예제를 통해 알아보도록 하겠다.

1. Comparable과 Comparator란?
  1) Comparable 인터페이스
    - 정의: 정렬 수행시 기본적으로 적용되는 정렬 기준이 되는 메서드를 정의해 놓는 인터페이스이다.
    - 사용법: Comparable 인터페이스를 implements 한 뒤, 내부에 있는 compareTo 메서드를 원하는 정렬 기준대로 구현하여 사용할 수 있다.
    - 패키지: java.lang.Comparable
    - 자바에서 제공되는 정렬이 가능한 클래스들은 모두 Comparable 인터페이스를 구현하고 있으며, 정렬시에 Comparable의 구현 내용에 맞춰 정렬이 수행된다. 
예를들어 Integer, Double 등의 클래스의 경우 비 내림차순(오름차순과 유사), String 클래스의 경우 사전순으로 정렬되게 구현되어 있다.

  2) Comparator 클래스
    - 정의: 정렬 가능한 클래스(=Comparable이 구현된 클래스)들의 기본 정렬 기준과는 다른 방식으로 정렬하고 싶을 때 사용하는 클래스이다.
    - 사용법: Comparator 클래스를 생성하여내부에 compare메서드를 원하는 정렬 기준대로 구현하여 사용할 수 있다.
    - 패키지: java.util.Comparator
    - 주로 익명클래스(new Comparator(){ ... })로 사용되며, 기본적으로 오름차순이 정렬 기준인 것을 내림차순으로 정렬하는 등의 용도로 사용된다.



2. 사용 방법
  1) Comparable 

아래와 같이 대학생 클래스가 있다고 하자. 이 때, 5명의 대학생들의 정보를 관리하기 위해 길이 5짜리의 대학생 클래스 형태 배열을 생성하였고, 학번순으로 정렬해서 나타내고자 한다. 어떻게 하면 좋을까?

class Student{ String name; //이름 int id; //학번 double score; //학점 public Student(String name, int id, double score){ this.name = name; this.id = id; this.score = score; } public String toString(){ //출력용 toString오버라이드 return "이름: "+name+", 학번: "+id+", 학점: "+score; } } public class Main{ public static void main(String[] args) { Student student[] = new Student[5]; //순서대로 "이름", 학번, 학점 student[0] = new Student("Dave", 20120001, 4.2); student[1] = new Student("Amie", 20150001, 4.5); student[2] = new Student("Emma", 20110001, 3.5); student[3] = new Student("Brad", 20130001, 2.8); student[4] = new Student("Cara", 20140001, 4.2); } }

Student 클래스에 기본 정렬 기준이 없으므로 우선 java.lang.Comparable 패키지를 import 한 다음 Comparable을 implements 해준다. 
이 때, 주의해야 할 것은 Comparable의 제너릭스인데, 우리는 Student 클래스 끼리 비교하는 것이므로 제너릭스 타입도 Student가 된다.
Comparable을 implements하고 나면 구현하지 않은 메서드를 구현하라고 나오는데, 이 메서드는 compareTo메서드로 경고 메세지를 클릭하거나 직접 오버라이딩 해 주면 된다.

unimplemented methods = compareTo()
import java.lang.Comparable; //패키지 import class Student implements Comparable<Student> { //제너릭스 주의! String name; //이름 int id; //학번 double score; //학점 public Student(String name, int id, double score){ this.name = name; this.id = id; this.score = score; } public String toString(){ //출력용 toString오버라이드 return "이름: "+name+", 학번: "+id+", 학점: "+score; } @Override public int compareTo(Student anotherStudent) { //오버라이딩 // TODO Auto-generated method stub return 0; } }


이제 compareTo 메서드 내용을 우리가 원하는대로 구현해야 한다. 우리가 원하는 정렬 기준은 '학번 오름차순'이므로 아래와 같이 구현해주면 된다.

@Override public int compareTo(Student anotherStudent) { // TODO Auto-generated method stub return Integer.compare(id, anotherStudent.id); }

Integer.compare은 단순히 첫번째 매개변수와 두번째 매개변수가 오름차순으로 유지될 수 있도록 값을 비교해 주는 메서드이다. Integer클래스에서 함수 원형을 찾아보면 아래와 같다.

Integer클래스 내부에 정의된 compare 메서드

이 int compare 메서드를 간단히 설명하자면, 정렬이 진행될 때 자리바꿈(=정렬) 여부를 결정하는 값을 넘겨주는 역할을 한다.
만약 return값이 0이나 음수이면 자리바꿈을 하지 않고양수이면 자리바꿈을 수행한다.
만약 오름차순이 아니라 내림차순으로 정렬하고 싶다면 매개변수의 순서를 바꿔주면 된다.

따라서 Integer.compare를 사용하지 않고 아래와 같이 직접 구현해도 상관 없다.

@Override public int compareTo(Student anotherStudent) { // TODO Auto-generated method stub return (id<anotherStudent.id)?-1:((id==anotherStudent.id)?0:1); }


여기까지 했으면 기본 정렬 기준이 완성된 것이다. 이를 바탕으로 main() 메서드에서 자바에서 기본적으로 제공하는 퀵소트 메서드인 Arrays.sort()를 이용하여 정렬을 수행해보면 학번 오름차순으로 잘 정렬된 것을 확인할 수 있다.

import java.lang.Comparable; import java.util.Arrays; //퀵소트 사용하기 위해 import class Student implements Comparable<Student> { String name; //이름 int id; //학번 double score; //학점 public Student(String name, int id, double score){ this.name = name; this.id = id; this.score = score; } public String toString(){ //출력용 toString오버라이드 return "이름: "+name+", 학번: "+id+", 학점: "+score; } /* 기본 정렬 기준: 학번 오름차순 */ public int compareTo(Student anotherStudent) { return Integer.compare(id, anotherStudent.id); } } public class Main{ public static void main(String[] args) { Student student[] = new Student[5]; //순서대로 "이름", 학번, 학점 student[0] = new Student("Dave", 20120001, 4.2); student[1] = new Student("Amie", 20150001, 4.5); student[2] = new Student("Emma", 20110001, 3.5); student[3] = new Student("Brad", 20130001, 2.8); student[4] = new Student("Cara", 20140001, 4.2); Arrays.sort(student); //퀵소트 for(int i=0;i<5;i++) //toString에 정의된 형식으로 출력 System.out.println(student[i]); } }
기본 정렬기준: 학번 오름차순 정렬 완료

여담으로 만일 Comparable이 구현되지 않은 상태에서 Arrays.sort()를 이용하여 정렬을 수행하려 하면 아래와 같은 에러가 발생하게 된다.

이유는 바로 Arrays.sort()등 정렬 메서드들은 정렬 시 자동으로 Comparable에 구현되어 있는 compareTo() 메서드를 호출해서 사용하기 때문이다.



2) Comparator
Comparable을 이용해 학번 오름차순대로 정렬을 하였다. 이제 이 5명의 학생들 중에 성적우수자 2명을 선정하여 장학금을 지급하려 한다. 이 때, 성적이 같은 학생이 여러 명이라면 학번이 빠른 순서대로 정하려고 한다.
우리는 성적이 높은 학생 2명을 편하게 찾기 위해서 학생들을 "성적이 높은 순으로 정렬한 뒤, 앞에서 2명을 선택"하려고 한다. 어떻게 할 수 있을까?

성적이 높은 순서대로 정렬하는 것이 우선이다. 
그런데 Student클래스의 기본 정렬 기준은 "학번 오름차순"이므로 이 기본 정렬 기준과 다른 새로운 정렬 기준을 세워야 한다. 이 때 이용되는 것이 바로 Comparator이다.
Comparator는 java.util.Comparator패키지에 있고, 주로 익명 클래스로 사용되며, 이를 Arrays.sort()내부에 정렬 기준으로 구현하면 된다.

Arrays.sort() 내부에 Comparator를 구현할 수 있도록 정의되어 있다
Arrays.sort(student, new Comparator<Student>(){ //익명클래스 사용, 제너릭스 주의 @Override public int compare(Student s1, Student s2) { // TODO Auto-generated method stub return 0; } });

이제 정렬 조건을 다시한번 확인해보면 아래와 같다.
  1) 학점이 높은 순서대로(학점 내림차순)
  2) 학점이 같다면 학번이 빠른 순서대로(학번 오름차순)

이를 바탕으로 학점 비교 식을 Comparator 내부에 구현하면 아래와 같다. 위의 Integer.compare 설명에서 잠깐 언급했듯이 내림차순 정렬을 하려면 매개변수의 순서를 바꿔주면 된다.

Arrays.sort(student, new Comparator<Student>(){ @Override public int compare(Student s1, Student s2) { double s1Score = s1.score; double s2Score = s2.score; return Double.compare(s2Score, s1Score);//학점 내림차순 } });

근데 이렇게만 끝나면 안된다. 왜냐하면 4.5점 학생이 1명, 4.2점 학생이 2명이므로, 학번을 통해 2순위와 3순위를 구별해야 하기 때문이다.

4.2점 학생이 2명 존재 -> 2순위, 3순위 구별 필요


따라서 2번째 조건인 '학점이 같으면 학번 오름차순' 정렬 조건을 Comparator 내부에 구현해주면 아래와 같다.

Arrays.sort(student, new Comparator<Student>(){ @Override public int compare(Student s1, Student s2) { double s1Score = s1.score; double s2Score = s2.score; if(s1Score == s2Score){ //학점이 같으면 return Double.compare(s1.id, s2.id); //학번 오름차순 } return Double.compare(s2Score, s1Score);//학점 내림차순 } });


여기까지 완성된 Comparator를 바탕으로 정렬을 수행한 뒤 결과를 출력하면 아래와 같이 정상 출력되는 것을 볼 수 있다.

import java.lang.Comparable; import java.util.Arrays; import java.util.Comparator; //Comparator 사용하기 위한 import class Student implements Comparable<Student> { String name; //이름 int id; //학번 double score; //학점 public Student(String name, int id, double score){ this.name = name; this.id = id; this.score = score; } public String toString(){ return "이름: "+name+", 학번: "+id+", 학점: "+score; } /* 기본 정렬 기준: 학번 오름차순 */ public int compareTo(Student anotherStudent) { return Integer.compare(id, anotherStudent.id); } } public class Main{ public static void main(String[] args) { Student student[] = new Student[5]; //순서대로 "이름", 학번, 학점 student[0] = new Student("Dave", 20120001, 4.2); student[1] = new Student("Amie", 20150001, 4.5); student[2] = new Student("Emma", 20110001, 3.5); student[3] = new Student("Brad", 20130001, 2.8); student[4] = new Student("Cara", 20140001, 4.2); Arrays.sort(student, new Comparator<Student>(){ public int compare(Student s1, Student s2) { double s1Score = s1.score; double s2Score = s2.score; if(s1Score == s2Score){ //학점이 같으면 return Double.compare(s1.id, s2.id); //학번 오름차순 } return Double.compare(s2Score, s1Score);//내림차순 } }); for(int i=0;i<5;i++) System.out.println(student[i]); } }
새로운 정렬기준: 학점 내림차순 & 같으면 학번 오름차순

여기까지 해서 Comparable과 Comparator에 대해 알아보았다. 상당히 자주 사용되는 개념들이므로 스스로 다양하게 사용해보면서 익히는 것이 중요하다.


※ 보너스 문제
Comparator를 이용해 학생들을 이름의 오름차순(=사전 순)으로 정렬하는 코드를 짜 보도록 하자
(Hint: String클래스도 Comparable이 구현되어 있다. 즉 내부에 compareTo가 구현되어 있....)

작성자 답안: