자바에서 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