지난 Comparable, Comparator 기록해서 부족한 점이 있었고, 스스로도 제대로 이해하지 못한것 같아 이번에 확실하게 이해한 내용을 바탕으로 기록해두겠다.
지난 번에 단순히 두 인터페이스의 차이는
- Comparable -> compareTo 메서드 구현
- Comparator -> compare 메서드 구현
이라고만 기억을 했었는데, 근본적으로 두 인터페이스의 역할에 대해 고민을 해보다, 사람의 언어로 생각해보니 이해하는데 더 도움이 됐다.
말 그대로,
- Comparable : 비교 가능한
- Comparator : 비교자
우리가 실생활에서 무언가를 비교하려할 때 분명히 비교 기준이 필요하다. 두 물체를 무게로 비교할 것인지, 크기로 비교할 것인지 등이 있겠다. 그런데, 이 두 물체가 비교가 가능한 것이라는 것은 어떻게 알고 있는가?
간단하다. 우리는 이 두 물체가 비교가능한 물체라는 것을 알고 있기 때문이다. 이 생각을 머리에 두고 위의 두 인터페이스를 살펴보자.
Comparable
우선 Comparable 부터 살펴보겠다.
우리가 실생활에서 비교할 수 있는 무언가를 알 수 있듯이, 프로그래밍에서도 비교가능한, 또는 비교하고 싶은 객체에 Comparable을 선언해주면 된다. 대표적인 예로, (Integer, String, Float, ...) 등 다양한 타입들이 있다.
Integer를 살펴보면 아래와 같이 Comparable 을 구현하고 있는 것을 볼 수 있다.
@jdk.internal.ValueBased
public final class Integer extends Number
implements Comparable<Integer>, Constable, ConstantDesc {
/**
* A constant holding the minimum value an {@code int} can
* have, -2<sup>31</sup>.
*/
@Native public static final int MIN_VALUE = 0x80000000;
...
그렇기 때문에, Comparable 인터페이스를 구현하는 compareTo 메서드를 볼 수 있다. 이때 compare 라는 메서드를 사용하는데, 이는 Integer 내부에 static 으로 정의된 메서드이다. (Comparator 인터페이스와는 무관 !) 이를 몰라서, 많이 헷갈렸는데 확실히 기억하자.
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);
}
예시로, Student 객체를 비교하려 하는데, 나이를 기준으로 삼고 싶다면 아래처럼 작성할 수 있을 것이다.
public class Student implements Comparable<Student> {
int age;
int classNumber;
Student(int age, int classNumber) {
this.age = age;
this.classNumber = classNumber;
}
@Override
public int compareTo(Student o) {
// 자기 자신의 age가 o의 age 보다 크다면 양수
if(this.age > o.age)
return 1;
// 자기 자신의 age와 o의 age가 같다면 0
else if(this.age == o.age)
return 0;
// 자기 자신의 age가 o의 age보다 작다면 음수
else
return -1;
}
}
정리하자면, Comparable 은 객체나 클래스가 비교가 가능한 녀석이라고 표시하고, 그 비교 기준을 만들어주는 역할이다.
Comparator
Comparator 는 비교자이다. 우선, Comparator 가 어떻게 정의되어있는지 살펴보겠다. 아래를 보면 한가지 의문점이 든다. 살펴보면 알겠지만, 메서드가 1개가 아닌데도 함수형 인터페이스(@FunctionaInterface) 어노테이션이 붙어있다.
이는 함수형 인터페이스의 정의가 단 하나의 "추상 메서드"만 가진 인터페이스를 의미하기 때문이다. 실제로, compare 을 제외한 나머지 메서드는 default 메서드로 정의되어 있고, 아래의 equals 메서드 역시 Object 의 메서드 이기 때문에 이미 구현이 되어 있는 상태이다.
@FunctionalInterface
public interface Comparator<T> {
...
int compare(T o1, T o2);
...
boolean equals(Object obj);
...
그렇다면 이 Comparator 는 어디에 사용할까?
아래와 같이 sort 함수를 오버라이딩 하는 상황을 예시로 들어보겠다.
@Override
public void sort(Comparator<? super T> comparator) {
}
만약, 이 sort 함수를 Integer 타입의 List에서 사용한다고 가정하자. 쉽게 말해 아래처럼 사용하겠다는 것이다.
List<Integer> list = new ArrayList<>();
list.sort( );
이때, sort의 매개변수로 Comparator 가 들어와야 한다는 것인데, 어떻게 해야할까?
우선, Comparator 는 compare 메서드를 구현해야 한다는 것을 알고 있다. 그리고 함수형 인터페이스는 매개변수로 사용가능하다.
Comparator<Integer> comparator = new Comparator<>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
list.sort(comparator);
이때, 익명 클래스는 람다 표현식을 활용하여 축약하여 작성할 수 있다. 줄이면 아래와 같을 것이다.
Comparator<Integer> comparator = (o1, o2) -> o1.compareTo(o2);
// Comparator<Integer> comparator = Integer::compareTo;
// list.sort(comparator);
list.sort(Integer::compareTo);
이렇게 되면, sort의 매개변수로 비교 기준인 Integer 클래스의 compareTo 값이 들어오는 것이다. (이때 compareTo 메서드는 Integer 클래스의 메서드이다.)
해당 값을 활용하여 아래와 같이 sort 함수를 구현할 수 있다.
@Override
public void sort(Comparator<? super T> comparator) {
Node a = new Node(1);
Node b = new Node(2);
if(comparator.compare((T)a.getData(), (T)b.getData()) > 0) {
}
}
정리하자면, Comparator 는 비교자로써, 익명 클래스와 함수형 인터페이스의 성질을 이용해 두 객체를 어떤 기준으로 비교할 것인지 정의하는 역할로 볼 수 있다.
'끄적이기' 카테고리의 다른 글
| [TIL - 11] 쓰레드(Thread) 정리 (0) | 2025.03.19 |
|---|---|
| [TIL - 10] 프로세스(Process) 정리 (0) | 2025.03.18 |
| [TIL - 8] 객체 설계 연습 : BrickBreaker 만들기 (JavaFX) (0) | 2025.03.07 |
| [TIL - 7] 객체 지향 설계를 어떻게 해야하나 (1) | 2025.03.06 |
| [TIL - 6] 웹 통신의 큰 흐름 (0) | 2025.03.05 |