Comparable과 Comparator 클래스는 모두 컬렉션 프레임워크를 이용하여 데이터를 정렬할 때 어떻게 정렬할 것인가에 대한 기준을 정할 때 사용되는 클래스들입니다. 두 클래스는 비슷한 역할을 수행하는데 두 클래스의 차이를 이번 포스팅에서 알아보도록 하겠습니다.
우선, 각각의 클래스의 내부를 살펴보면 아래와 같습니다.
public interface Comparable<T>{
public int compareTo(Object o1);
}
public interface Comparator<T>{
int compare(Object o1, Object o2);
boolean equals(Object obj);
}
그럼, 두 인터페이스의 compare 관련 함수는 어떤 점에서 차이가 있을까요?
우선, 매개변수의 개수부터 다릅니다. Comparable의 compareTo() 메서드는 자기 자신과 매개변수로 넘어온 객체를 비교하고, Comparator의 compare() 메서드는 매개변수로 넘어온 2개의 객체를 서로 비교합니다.
따라서, Comparable의 메서드를 사용하려면 그것을 사용하려는 클래스가 Comparable 인터페이스를 무조건 구현해야 합니다.
[잠깐!]
Comparable 인터페이스를 상속받아 compareTo(Object o1) 메서드를 구현할 때는 규칙이 있습니다.
1. 오름차순 정렬 시
- 자기 자신(this)보다 o1의 값이 더 크다면 음수 반환
- 자기 자신(this)보다 o1의 값이 더 작다면 양수 반환
- 자기 자신(this)과 o1의 값이 동일하다면 0 반환
2. 내림차순 정렬 시
- 자기 자신(this)보다 o1의 값이 더 크다면 양수 반환
- 자기 자신(this)보다 o1의 값이 더 작다면 음수 반환
- 자기 자신(this)과 o1의 값이 동일하다면 0 반환
아래 예시를 보겠습니다.
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("Son", 10, 8, 5));
list.add(new Student("Kelly", 10, 9, 7));
list.add(new Student("Betty", 9, 6, 4));
list.add(new Student("Judi", 5, 7, 8));
Collections.sort(list);
System.out.println("Sorting : ");
for (Student student : list) {
System.out.println(student.getName() + " " +
student.getKorean());
}
}
}
@AllArgsConstructor
@Getter
class Student implements Comparable<Student> {
private String name;
private int korean, math, english;
@Override
public int compareTo(Student o) {
return this.korean - o.korean; // 국어 점수 오름차순 정렬
// 내림차순 정렬: (this.korean-o.korean)*(-1)
}
}
[결과]
Sorting :
Judi 5
Betty 9
Son 10
Kelly 10
하지만, 여기에서 상황에 따라 다른 정렬 조건을 사용하고 싶습니다. 이렇게 정렬 조건을 추가하고 싶을 때 (e.g. 이름 순 정렬)는 어떻게 해야 할까요? compareTo() 메서드는 한 번만 정렬 조건을 정할 수 있습니다 그래서 상황에 맞게 여러 정렬 조건을 적용하기에는 어렵습니다. 이때 사용할 수 있는 것이 Comparator입니다.
아래 코드를 살펴보겠습니다.
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("Son", 10, 8, 5));
list.add(new Student("Kelly", 10, 9, 7));
list.add(new Student("Betty", 9, 6, 4));
list.add(new Student("Judi", 5, 7, 8));
Collections.sort(list);
print("Korean Score", list);
NameComparator nameComparator = new NameComparator();
Collections.sort(list, nameComparator);
print("Name", list);
MathComparator mathComparator = new MathComparator();
Collections.sort(list, mathComparator);
print("Math Score", list);
EnglishComparator englishComparator = new EnglishComparator();
Collections.sort(list, englishComparator);
print("English Score", list);
}
static void print(String sorted, ArrayList<Student> list){
System.out.printf("Sorting by %s: \n", sorted);
for (Student student : list) {
System.out.println(student.getName() + " " +
student.getKorean() + " " +
student.getMath() + " " +
student.getEnglish());
}
System.out.println("---------------------------------");
}
}
@AllArgsConstructor
@Getter
class Student implements Comparable<Student> {
private String name;
private int korean, math, english;
@Override
public int compareTo(Student o) {
return this.korean - o.korean;
}
}
class NameComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.getName().compareTo(o2.getName()); // String 클래스가 구현한 Comparable의 compareTo 함수 사용
}
}
class MathComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.getMath()-o2.getMath();
}
}
class EnglishComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.getEnglish()-o2.getEnglish();
}
}
[결과]
Sorting by Korean Score:
Judi 5 7 8
Betty 9 6 4
Son 10 8 5
Kelly 10 9 7
---------------------------------
Sorting by Name:
Betty 9 6 4
Judi 5 7 8
Kelly 10 9 7
Son 10 8 5
---------------------------------
Sorting by Math Score:
Betty 9 6 4
Judi 5 7 8
Son 10 8 5
Kelly 10 9 7
---------------------------------
Sorting by English Score:
Betty 9 6 4
Son 10 8 5
Kelly 10 9 7
Judi 5 7 8
---------------------------------
여기서 또 확인할 수 있는 부분이 있습니다.
위 코드에서 NameComparator 클래스를 보면 아래와 같이 구현되어 있습니다.
class NameComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.getName().compareTo(o2.getName()); // String 클래스가 구현한 Comparable의 compareTo 함수 사용
}
}
Name으로 Sorting 할 때, String 클래스에 이미 정의되어 구현되어 있는 Comparable의 compareTo() 함수를 사용하고 있습니다. 이는 Intger나 Double과 같은 Wrapper 클래스들과 String, Date, File 등의 클래스들이 Comparable 인터페이스를 구현하고 있기 때문입니다!
즉, 같은 타입을 가진 인스턴스끼리 서로 비교할 수 있는 클래스들은 기본적으로 Comparable 인터페이스를 상속받고, compareTo() 메서드를 구현하고 있는데 기본적으로 오름차순으로 정렬이 되도록 구현되어 있습니다.
예를 들어, Comparable 인터페이스를 구현한 클래스 중 Integer 클래스를 살펴보면, 아래 코드와 같이 compareTo 메서드를 Override 해서 사용하고 있습니다. 이때, 구현 내용을 보면, 비교하는 두 객체가 같으면 0, 나의 값이 비교하는 값보다 작으면 음수, 비교하는 값보다 나의 값이 크면 양수를 반환하도록 되어 있습니다. 이와 마찬가지로 Comparator의 compare() 메서드도 객체를 비교해서 각 경우마다 음수, 0, 양수를 반환할 수 있도록 구현해야 합니다.
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
즉, Comparable과 Comparator의 차이를 정리하면 아래와 같습니다.
Comparable: 기본 정렬기준을 구현하는 데 사용 (자기 자신과 매개변수 객체 비교)
==> 기본적으로 1번만 정렬 기준을 정할 수 있다.
Comparator: 기본 정렬기준 외에 다른 기준으로 정렬하고자 할 때 사용 (2개의 매개변수 객체를 서로 비교)
==> 여러 개의 커스텀 정렬 기준을 정할 수 있다.
참고자료
https://www.geeksforgeeks.org/comparable-vs-comparator-in-java/
'Java' 카테고리의 다른 글
-Xlint 옵션 (0) | 2023.10.21 |
---|---|
열거형(enums) (2) | 2023.10.21 |
다형성(Polymorphism) (2) | 2023.10.15 |
추상클래스(Abstract)과 인터페이스(Interface) (0) | 2023.10.15 |
제어자 (static과 final) (0) | 2023.10.15 |