자바에서 Arrays.sort()를 호출하면 알아서 배열을 정렬하는 것처럼 보이겠지만, 사실은 Comparable의 구현에 의해 정렬된 것입니다. Comparable와 Comparator는 모두 인터페이스로, 객체를 비교하는데 필요한 메서드를 정의하고 있습니다.
보통 기본형 타입의 변수들의 경우, 부등호를 이용해 쉽게 비교할 수 있었습니다. 새로운 클래스 객체를 만들어 비교한다면 어떻게 비교해야 할까요?
class Person {
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
이러한 Person 클래스를 만들었다고 가정해봅시다. 기본형 타입과는 달리 부등호로 비교할 수 없을 뿐만 아니라, name, age 중 어떤 것을 기준으로 비교해야 할지 모르겠습니다.
이를 위해 고안된 것이, Comparable과 Comparator입니다. 그렇다면 두 인터페이스는 어떤 차이점이 있는지 알아봅시다.
public interface Comparable<T> {
public int compareTo(T o);
}
Comparable 인터페이스에서는 compareTo라는 메서드를 이용하여 객체를 비교하고 있습니다. compareTo 메서드는 파라미터가 1개로, 자기자신과 파라미터 객체를 비교하는 메서드입니다. 메서드의 반환 값은 int이며 비교하는 두 객체가 같으면 0, 비교하는 값보다 작으면 음수, 크면 양수를 반환하도록 구현해야 합니다.
Person 클래스에서 Comparable 인터페이스를 상속 받아 compareTo 메서드를 구현해봅시다.
class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public int getAge(){
return this.age;
}
@Override
public String toString() {
return "[" + name + ": " + age + "]";
}
@Override
public int compareTo(Person o) {
// Integer.compare(a,b)
// a value less than 0 if x < y
// a value greater than 0 if x > y
return Integer.compare(this.age, o.getAge());
}
}
Comparable 인터페이스를 implements하고, compareTo 메서드를 오버라이딩 한 모습입니다. 두 객체의 값을 비교하여 같으면 0, 자기 자신이 더 크면 양수, 작으면 음수로 반환해주면 되기 때문에
return this.age - o.getAge();
와 같이 간편하게 사용할 수 있지만, underflow나 overflow의 문제가 발생할 수 있으니 직접적으로 비교하는 것이 좋습니다. 저는 Integer.compare() 메서드를 사용하여 비교하였는데요, if 문으로 비교하려 하였으나 친절한 intelliJ가 이 함수를 추천해주었습니다. Integer.compare() 메서드는 참고로 알고 있으면 좋을 것 같습니다!
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
public interface Comparator<T> {
int compare(T o1, T o2);
}
Comparable은 자기 자신과 다른 객체를 비교했다면, Comparator는 서로 다른 두 객체를 비교하고 있습니다. 또 Comparable은 java.lang 패키지에 존재하여 import하지 않아도 되었다면, Comparator는 java.util 패키지에 존재하여 import해주어야 합니다.
Person 클래스를 Comparator 인터페이스를 사용하여 구현해보겠습니다.
class Person implements Comparator<Person> {
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String getName(){
return this.name;
}
@Override
public String toString() {
return "[" + name + ": " + age + "]";
}
@Override
public int compare(Person o1, Person o2) {
return o1.getName().compareTo(o2.getName());
}
}
이런 식으로 Person클래스 내부에 compare 메서드를 구현하여 사용할 수도 있겠지만, 비교만을 위한 객체가 만들어진다는 단점이 있습니다.
public class ComparePractice {
public static void main(String[] args) {
Person a = new Person("mark", 24);
Person b = new Person("jeno", 23);
Person c = new Person("jisung", 21);
Person comp = new Person("name", 0); // 비교만을 위해 사용할 객체
// ⋁
int isBig = comp.compare(a, b);
// ⋁
int isBig2 = comp.compare(b, c);
// ⋁
int isBig3 = comp.compare(a, c);
}
//... 생략
}
이런 식으로 말이죠!
그래서 Comparator는 주로 비교 기능만 따로 구현할 수 있도록 익명 객체를 사용하거나, 비교 클래스를 만들어 사용합니다.
비교만을 위한 클래스를 만들어봅시다.
class nameSort implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
// a value less than 0 if this string is lexicographically less than the string argument;
// a value greater than 0 if this string is lexicographically greater than the string argument.
return o1.getName().compareTo(o2.getName());
}
}
sort()는 Comparator를 지정해주지 않으면 객체가 구현한 Comparable을 기준으로 정렬하며, Comparator를 지정해줌으로써 기본 정렬기준(Comparable) 외에 다른 기준으로 정렬할 수 있게 됩니다.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class ComparePractice {
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
personList.add(new Person("mark", 24));
personList.add(new Person("jeno", 23));
personList.add(new Person("jisung", 21));
Collections.sort(personList);
System.out.println(personList);
personList.sort(new nameSort());
System.out.println(personList);
}
}
class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public int getAge(){
return this.age;
}
public String getName(){
return this.name;
}
@Override
public String toString() {
return "[" + name + ": " + age + "]";
}
@Override
public int compareTo(Person o) {
// Integer.compare(a,b)
// a value less than 0 if x < y;
// a value greater than 0 if x > y
return Integer.compare(this.age, o.getAge());
}
}
class nameSort implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
// a value less than 0 if this string is lexicographically less than the string argument;
// a value greater than 0 if this string is lexicographically greater than the string argument.
return o1.getName().compareTo(o2.getName());
}
}
// 나이 기준 오름차순 정렬
[[jisung: 21], [jeno: 23], [mark: 24]]
// 이름 기준 오름차순 정렬
[[jeno: 23], [jisung: 21], [mark: 24]]
Java의 정석 3rd Edition
자바 [JAVA] - Comparable과 Comparator