st-lab님의 자바 [JAVA] - Comparable 과 Comparator의 이해를 보고 정리한 글입니다.
Comparable / Comparator는 객체를 비교할 수 있는 인터페이스
학생의 이름과 나이를 가지고 있는 클래스를 생성해본다고 가정.
public class Test {
public static void main(String[] args) {
Student s1 = new Student("홍길동" 20); // 학생1
Student s2 = new Student("김철수" 22); // 학생2
if (s1 ? s2) // s1, s2의 대소 비교는 어떻게 할까?
}
}
class Student {
String name;
int age;
Student(String name, int age) {
this.name = name;
this.age = age;
}
}
위의 코드에서, 학생1(s1), 학생2(s2)를 생성하였다. 이 코드를 보면 어떻게 학생들의 대소관계를 알아낼 수 있을까?
이렇게 여러가지 기준이 나올 수 있기 때문에, Comparable / Comparator 를 통해 특정 객체를 비교할 수 있는 기준을 정의하여 준다.
📌 자기 자신과 매개변수 객체를 비교
// Student 클래스는 Comparable<T> 인터페이스를 구현 받는다.
class Student implements Comparable<Student>{
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// Comparable<T>에서 정의된 compareTo를 재정의
@Override
public int compareTo(Student o) {
if (this.age > o.age)
return 1;
else if (this.age == o.age)
return 0;
else
return -1;
}
}
Student 객체를 비교compareTo() 매개변수로 들어온 객체를 비교this.age 가 o.age보다 크다면 양수this.age 가 o.age보다 같으면 0this.age 가 o.age보다 작으면 음수// Student 클래스는 Comparable<T> 인터페이스를 구현 받는다.
class Student implements Comparable<Student>{
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// Comparable<T>에서 정의된 compareTo를 재정의
@Override
public int compareTo(Student o) {
/*
아래 결과가 양수면 this.age가 o.age보다 크다.
음수이면 this.age가 o.age보다 작다.
if문을 사용하지 않고도 두 값의 차이를 통해
대소 관계를 반환할 수 있다.
*/
return this.age - o.age;
}
}
this.age를 기준으로 o.age를 빼주면 양수값이 나올거고, 그 말은 this.age는 o.age 보다 크다.뺄셈 과정에서 자료형의 범위를 넘어버리는 경우가 발생한다.
EX) o1 = 1, o2 = -2,147,483,648일 경우
권장사항은 IF 문을 통한 <, >, == 로 대소비교
import java.util.*;
public class Test {
public static void main(String[] args) {
Student hong = new Student("홍길동", 20);
Student kim = new Student("김철수", 22);
// 우리가 재정의 했던 compareTo() 메서드를 호출하고
// 매개변수로 kim 객체를 전달해주면
// compareTo() 의 내부에 작성한 비교 코드를 통해
// 양수, 음수, 0 값중 하나를 반환해준다.
// 1: 자기 자신, 즉 hong이 더 크다.
// 0: hong과 kim의 크기는 같다.
// -1: kim의 크기가 더 크다.
int compareValue = hong.compareTo(kim);
if (compareValue > 0)
System.out.println("hong 객체가 kim 객체보다 크다.");
else if (compareValue < 0)
System.out.println("hong 객체가 kim 객체보다 작다.");
else
System.out.println("hong 객체 kim 객체의 크기는 같다.");
}
}
class Student implements Comparable<Student>{
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student o) {
if (this.age > o.age)
return 1;
else if (this.age < o.age)
return -1;
else
return 0;
}
}
compareTo()의 매개변수로 받은 객체를 비교compareTo() 를 반드시 구현 📌 매개변수로 받은 두개의 객체를 서로 비교
// Student 클래스는 Comparator<T> 인터페이스를 구현 받는다.
class Student implements Comparator<Student>{
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// Comparator<T>에서 정의된 compare 재정의
@Override
public int compare(Student o1, Student o2) {
if (o1.age > o2.age)
return 1;
else if (o1.age == o2.age)
return 0;
else
return -1;
/*
return o1.age - o2.age;
*/
}
}
Comparable 과 비교 방법도 같다. 차이점은 자기 자신과의 비교compare() 또한 주석처리된 부분처럼 작성 가능import java.util.Comparator;
public class Main {
public static void main(String[] args) {
Student hong = new Student("홍길동", 20);
Student kim = new Student("김철수", 22);
int compareValue = hong.compare(hong, kim);
if (compareValue > 0)
System.out.println("hong 객체가 kim 객체보다 크다.");
else if (compareValue < 0)
System.out.println("hong 객체가 kim 객체보다 작다.");
else
System.out.println("hong 객체 kim 객체의 크기는 같다.");
}
}
class Student implements Comparator<Student>{
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compare(Student o1, Student o2) {
if (o1.age > o2.age)
return 1;
else if (o1.age == o2.age)
return 0;
else
return -1;
}
}
hong.compare(hong, kim) 을 보면 객체에 상관 없이 독립적은 두 객체를 매개변수로 전달에 비교한다.특정 클래스 내부에 Comparator인터페이스를 상속받아, compare() 를 구현 해준다면, 해당 compare()를 사용하기 위해선 해당 클래스 타입의 객체를 생성해 주어야한다.
좀 헷갈린다. 코드로 보자
public class Main {
public static void main(String[] args) {
Student hong = new Student("홍길동", 20);
Student kim = new Student("김철수", 22);
Student lee = new Student("이발수", 24);
int compareValue = hong.compare(hong, lee);
int compareValue = hong.compare(kim, kim);
int compareValue = hong.compare(kim, lee);
}
}
// 이하 생략
딱 봐도 코드가 좀 보기 불편하다. 독립된 객체 두개를 비교하는데, 굳이 객체의 메서드로 들어가 객체를 통해 호출하기에는 좋지 않아 보인다.
public class Main {
public static void main(String[] args) {
Student hong = new Student("홍길동", 20);
Student kim = new Student("김철수", 22);
Student lee = new Student("이발수", 24);
// 익명 클래스를 통해 특정 객체에 귀속되지 않아도 됨.
Comparator<Student> comp = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
/*
비교 조건 생략 . . .
*/
}
}
int compareValue = hong.compare(hong, lee);
int compareValue = hong.compare(kim, kim);
int compareValue = hong.compare(kim, lee);
}
public static Comparator<Student> comp2 = new Comparator<>() {
@Override
public int compare(Student o1, Student o2) {
/*
비교 조건 생략 . . .
*/
}
}
}
public class Main {
public static void main(String[] args) {
Student hong = new Student("홍길동", 20);
Student kim = new Student("김철수", 22);
Student lee = new Student("이문복", 28);
Student choi = new Student("최치김", 35);
Student jung = new Student("정순하", 50);
// 리스트에 학생 객체들 저장.
Student[] list = new Student[5];
list[0] = hong;
list[1] = kim;
list[2] = lee;
list[3] = choi;
list[4] = jung;
// 정렬 전
System.out.println(Arrays.toString(list));
// 정렬
Arrays.sort(list);
// 정렬 후
System.out.println(Arrays.toString(list));
}
}
class Student {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
위 코드를 실행하면 예외가 발생

Arrays.sort() 의 기본 메서드는 내부에서 인자로 받은 배열의 타입 (여기서는 Student)에 정의된 compareTo의 반환 값을 통해 정렬을 수행. Student 클래스에는 재정의된 compareTo() 가 존재하지 않아, 예외 발생Comparable<T> 구현 받아, compareTo() 재정의 해준다면 정상적인 배열의 오름차순 정렬 결과를 받을 수 있다.public class Main {
public static void main(String[] args) {
Student hong = new Student("홍길동", 20);
Student kim = new Student("김철수", 22);
Student lee = new Student("이문복", 28);
Student choi = new Student("최치김", 35);
Student jung = new Student("정순하", 50);
// 리스트에 학생 객체들 저장.
Student[] list = new Student[5];
list[0] = hong;
list[1] = jung;
list[2] = choi;
list[3] = lee;
list[4] = kim;
// 정렬 전
System.out.println(Arrays.toString(list));
// 정렬
Arrays.sort(list);
// 정렬 후
System.out.println(Arrays.toString(list));
}
}
class Student implements Comparable<Student> {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student o) {
if (this.age > o.age)
return 1;
else if (this.age == o.age)
return 0;
else
return -1;
}
@Override
public String toString() {
return this.name + " " + this.age;
}
}
public class Main {
public static void main(String[] args) {
Student hong = new Student("홍길동", 20);
Student kim = new Student("김철수", 22);
Student choi = new Student("최치김", 35);
Student lee = new Student("이문복", 28);
Student jung = new Student("정순하", 50);
// 리스트에 학생 객체들 저장.
Student[] list = new Student[5];
list[0] = hong;
list[1] = jung;
list[2] = choi;
list[3] = lee;
list[4] = kim;
// 정렬 전
System.out.println(Arrays.toString(list));
Comparator<Student> comp = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
if (o1.age > o2.age)
return 1;
else if (o1.age == o2.age)
return 0;
else
return -1;
}
};
// 정렬
Arrays.sort(list, comp);
// 정렬 후
System.out.println(Arrays.toString(list));
}
}
class Student {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return this.name + " " + this.age;
}
}
main() 내부에 Comparator<Student>를 구현한 익명 클래스를 생성한뒤 Arrays.sort()의 두번째 매개변수로 전달sort()를 통해 Student 클래스 내부에 정의된 Comparable<>의 추상 메서드 compareTo 존재 유무 상관 없이, Comparator 익명 클래스의 compare() 메서드를 통해 배열 요소의 정렬 수행.compareTo() / compare()에서 반환되는 양수, 음수, 0을 각 조건의 반대가 되도록해보자./*
생략 ...
*/
if (o1.age > o2.age)
return 1;
else if (o1.age == o2.age)
return 0;
else
return -1;
/*
생략
*/
/*
생략 ...
*/
if (o1.age > o2.age)
return -1;
else if (o1.age == o2.age)
return 0;
else
return 1;
/*
생략
*/
/*
생략 ...
*/
compare(Student o1, Student o2) {
return o2.age - o1.age
}
compareTo(Student o) {
return o2.age - this.age;
}
/*
생략
*/
Comparable<T>는 보통 클래스 내부에 한번만 재정의 한다. 그래서 보통 해당 클래스의 가장 기본(Default) 비교로 사용한다.
Comparator<T>는 특정 상황에 맞는 기준이 필요할때 익명클래스로 정의하여 비교할 수 있는 장점이 있다보니, 보통 Comparable을 사용하다가, 특별한 정렬이 필요하다면 그때 쓰이는 경우가 많다.