이런 저런 프로그램을 만들다보면, 객체들의 순서를 정해야 하는 상황이 있다.
순서를 정하는 방법에 이런 저런 방법이 있겠지만, Comparable을 통한 방법이 가장 기본이라고 생각한다.
public interface Comparable<T> {
int compareTo(T t);
}
Comparable은 interface로 compareTo 메소드 하나만을 가진다.
이펙티브 자바에서 설명하는 Comparable의 개념을 살펴보면
자연적인 순서
를 가진다.권고
한다.순서라는 개념에 초점을 맞춰 머릿속으로 생각해보면 쉽게 이해할 수 있는 규약들이다.
기본적으로 반환되는 int 값
을 통해 순서를 메긴다.
주어진 객체와 자신을 비교해서 자신이 주어진 객체보다
자신을 주어진 객체보다 앞 순서
로 정렬시키려면 양수
를
자신을 주어진 객체보다 뒤 순서
로 정렬시키려면 음수
를
자신과 주어진 객체의 순서가 같다면 0
을 반환하도록 하면 된다.
사용 방법은 아래와 같이 논리적인 순서를 필요로 하는 객체가 Comparable의 compareTo 메소드를 구현하도록 하면 된다.
Comparable의 제네릭에는 비교하고자 하는 객체의 타입을 작성하면 된다. 보통 같은 객체 간의 순서를 정하기 때문에, Comparable을 구현하는 객체와 같은 타입을 넣으면 된다.
예시)
public class Car implements Comparable<Car> {
private final CarName carName;
private final int distance;
@Override
public int compareTo(final Car another) {
// 오름차순 정렬하기
return this.distance - another.distance;
}
}
위와 같이 compareTo를 구현한다면 another의 distance가 this의 distance보다 크다면 this가 앞 순서로 가기 때문에 distance가 작은 값부터 앞 순서로 정렬할 수 있다.
public class Car implements Comparable<Car> {
private final CarName carName;
private final Distance distance;
@Override
public int compareTo(final Car another) {
//오름차순 정렬하기
return this.distance.compareTo(another.distance);
}
}
위와 다르게 비교에 필요한 값이 원시 값이 아닌 객체
라면 아래와 같이
비교에 필요한 객체도 Comparable의 compareTo를 구현해주어야한다.
당연한 것이다. 순서를 메기기 위한 값에 순서가 없다면 말짱 도루묵이다.
public class Distance implements Comparable<Distance> {
private int distance;
@Override
public int compareTo(final Distance another) {
//오름차순 정렬하기
return this.distance - another.distance;
}
}
아래와 같은 코드는 위의 코드보다 덜 객체 지향적이기 때문에 피하는 것이 좋다.
값을 꺼내서 비교하기 보다는 Distance에게 메시지를 던지자.
public class Car implements Comparable<Car> {
private final CarName carName;
private final Distance distance;
@Override
public int compareTo(final Car another) {
//오름차순 정렬하기
return this.distance.getValue() - another.distance.getValue();
}
}
컴파일 시점에 오류
가 발생한다.null
값이 들어오는 경우자동차의 정렬 기준이 오름차순에서 내림차순으로 변경된다면 도메인이 변경에 일어난다.
도메인에서 한가지 방법으로 고정해두고 외부에서 조정하면 된다.
Comparator
를 통한 방법public class CarComparator implements Comparator<Car> {
@Override
public int compare(Car firstCar, Car secondCar) {
return secondCar.compareTo(firstCar);
}
}
return cars.stream()
.sorted(new CarComparator()) // 정렬 기준을 지정
.findFirst()
.orElseThrow(() -> new IllegalArgumentException());
sorted()의 인자
에 Comparator 생성하며 구현
해서 주입return cars.stream()
.sorted(new Comparator<Car>() {
@Override
public int compare(Car firstCar, Car secondCar) {
return secondCar.compareTo(firstCar)
}
})
.findFirst()
.orElseThrow(() -> new IllegalArgumentException());
//람다식으로 변경
return cars.stream()
.sorted((firstCar, secondCar) -> secondCar.compareTo(firstCar))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException());
sorted()의 인자
에 Comparator의 메소드 사용return cars.stream()
.sorted(Comparator.naturalOrder()) //오름차순
.findFirst()
.orElseThrow(() -> new IllegalArgumentException());
return cars.stream()
.sorted(Comparator.reverseOrder()) //내림차순
.findFirst()
.orElseThrow(() -> new IllegalArgumentException());
public class Person implements Comparable<Person> {
private static final Comparator<Person> PERSON_COMPARATOR =
Comparator.comparing((Person another) -> another.age)
.thenComparing((Person another) -> another.name);
private final int age;
private final String name;
public Person(final int age, final String name) {
this.age = age;
this.name = name;
}
@Override
public int compareTo(final Person another) {
if (this.age > another.age) {
return 1;
} else if (this.age < another.age) {
return -1;
}
return this.name.compareTo(another.name);
}
}
위처럼 compareTo에 복잡한 로직을 적기 보단 아래 처럼 Comparator의 comparing과 thenComparing 체이닝을 사용할 수 있다.
public class Person implements Comparable<Person> {
private static final Comparator<Person> PERSON_COMPARATOR =
Comparator.comparing((Person another) -> another.age)
.thenComparing((Person another) -> another.name);
private final int age;
private final String name;
public Person(final int age, final String name) {
this.age = age;
this.name = name;
}
@Override
public int compareTo(final Person another) {
return PERSON_COMPARATOR.compare(this, another);
}
}
순서를 매길 것이면 값을 꺼내서 비교해 정렬 시키는 것보다 객체에게 메시지를 보내는 형식이 훨씬 객체지향적인 방식이라고 생각한다.