Java Comparable, Comparator
"caAbBC"와 같은 알파벳 문자열을 "aAbBcC"와 같은 알파벳 순서로 정렬하는 코드를 작성하려면 어떻게 해야할까요 ?
위 문제를 위한 코드를 자바로 작성한다면, 아마도 정렬을 실실하는 코드를 작성하되
그 안에서 문자들에 대한 비교 연산을 새로 정의하는 형태로 작성해야 할 것입니다.
위 문제에 핵심은 '비교연산' 을 구현하는데에 있습니다.
그냥 단순히 문자열을 char 배열로 바꾸고 각 char 형으로 정렬하고자 한다면 비교하는데 쓰이는 값이 ASCII값이므로
자연율로 정렬해버리게됩니다. ABCDabcd와 같은 형태가 되겠죠
위처럼 자연율(일반적인 정렬순서)가 아니라 사용자가 새롭게 정렬 순서를 정의하고 싶거나 오브젝트를 정렬할 때
오브젝트 정렬에 사용 될 값을 지정하는 역할을 하는 인터페이스가 바로 Comparable과 Comparator입니다.
Comparable vs Comparator
그렇다면 Comparable과 Comparator의 차이는 무었일까요 ??
우선 각 인터페이스를 알아보도록 하겠습니다.
Comparable 인터페이스의 원형
public interface Comparable<T> {
int compareTo(T var1);
}
수정 자 및 유형 | 방법 및 설명 |
int | compareTo(T o)
이 객체를 지정된 객체와 순서와 비교합니다. |
Comparator 인터페이스의 원형
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
default <U> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
return thenComparing(comparing(keyExtractor, keyComparator));
}
default <U extends Comparable<? super U>> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor)
{
return thenComparing(comparing(keyExtractor));
}
default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
return thenComparing(comparingInt(keyExtractor));
}
default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
return thenComparing(comparingLong(keyExtractor));
}
default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) {
return thenComparing(comparingDouble(keyExtractor));
}
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
return Collections.reverseOrder();
}
@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
}
public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(true, comparator);
}
public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(false, comparator);
}
public static <T, U> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (Comparator<T> & Serializable)
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
keyExtractor.apply(c2));
}
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}
public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
}
public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));
}
}
Comprator Interface는 추상메소드 compare()을 가지는 functional Interface 입니다.
compare() 메소드 외 에도 여러개의 default메소드와 static메소드를 가지고 있습니다.
수정 자 및 유형 | 방법 및 설명 |
int | compare(T o1, T o2)
순서에 대한 두 가지 인수를 비교합니다. |
static <T,U extends Comparable<? super U>> Comparator<T> |
comparing(Function<? super T,? extends U> keyExtractor)
Comparabletype에서 정렬 키 를 추출하고 해당 정렬 키로 비교하는를 T반환 하는 함수를 허용합니다 Comparator<T>. |
static <T,U> Comparator<T> | comparing(Function<? super T,? extends U> keyExtractor, Comparator<? super U> keyComparator)
형식에서 정렬 키를 추출 하고 지정된 정렬 키를 사용하여 해당 정렬 키 T와 Comparator<T>비교하는를 반환 하는 함수를 허용 합니다 Comparator. |
static <T> Comparator<T> | comparingDouble(ToDoubleFunction<? super T> keyExtractor)
doubletype에서 정렬 키 를 추출하고 해당 정렬 키로 비교하는를 T반환 하는 함수를 허용합니다 Comparator<T>. |
static <T> Comparator<T> | comparingInt(ToIntFunction<? super T> keyExtractor)
inttype에서 정렬 키 를 추출하고 해당 정렬 키로 비교하는를 T반환 하는 함수를 허용합니다 Comparator<T>. |
static <T> Comparator<T> | comparingLong(ToLongFunction<? super T> keyExtractor)
longtype에서 정렬 키 를 추출하고 해당 정렬 키로 비교하는를 T반환 하는 함수를 허용합니다 Comparator<T>. |
boolean | equals(Object obj)
다른 객체가이 비교기와 "동일한 지"여부를 나타냅니다. |
static <T extends Comparable<? super T>> Comparator<T> |
naturalOrder()
Comparable객체를 자연 순서대로 비교하는 비교자를 반환합니다 . |
static <T> Comparator<T> | nullsFirst(Comparator<? super T> comparator)
널 null이 아닌 것으로 간주되는 널 친화적 인 비교 자를 리턴합니다. |
static <T> Comparator<T> | nullsLast(Comparator<? super T> comparator)
널 null이 아닌 것보다 큰 것으로 간주되는 널 친화적 인 비교 자를 리턴합니다. |
default Comparator<T> | reversed()
이 비교 자의 역순을 부과하는 비교자를 반환합니다. |
static <T extends Comparable<? super T>> Comparator<T> |
reverseOrder()
자연 순서 의 역순을 부과하는 비교자를 반환합니다 . |
default Comparator<T> | thenComparing(Comparator<? super T> other)
다른 비교기와 함께 사전 순서를 비교합니다. |
default <U extends Comparable<? super U>> Comparator<T> |
thenComparing(Function<? super T,? extends U> keyExtractor)
Comparable정렬 키 를 추출하는 함수가 포함 된 사전 순서 순 비교자를 리턴합니다 . |
default <U> Comparator<T> | thenComparing(Function<? super T,? extends U> keyExtractor, Comparator<? super U> keyComparator)
지정된와 비교할 키를 추출하는 함수가 포함 된 사전 순서 비교기를 리턴합니다 Comparator. |
default Comparator<T> | thenComparingDouble(ToDoubleFunction<? super T> keyExtractor)
double정렬 키 를 추출하는 함수가 포함 된 사전 순서 순 비교자를 리턴합니다 . |
default Comparator<T> | thenComparingInt(ToIntFunction<? super T> keyExtractor)
int정렬 키 를 추출하는 함수가 포함 된 사전 순서 순 비교자를 리턴합니다 . |
default Comparator<T> | thenComparingLong(ToLongFunction<? super T> keyExtractor)
long정렬 키 를 추출하는 함수가 포함 된 사전 순서 순 비교자를 리턴합니다 . |
공통점
- 정렬의 기준을 정합니다.
공통점
- Comparable : 객체간의 일반적인 정렬이 필요할때 사용 , compareTo 메소드 사용
- Comparator : 객체간의 특정한 정렬이 필요할때 , compare() 메소드 사용
자세하게 예를 보고 알아보도록 하겠습니다.
Comparable Example
@Test
public void basicSortWithListDefaultMethod(){
List<String> list = new ArrayList<>();
list.add("E");
list.add("A");
list.add("C");
list.add("B");
list.add("D");
Collections.sort(list);
List<String> actual = Arrays.asList("A","B","C","D","E");
Assert.assertThat(actual, CoreMatchers.equalTo(list));
}
Collection.sort로 정렬한것을 문자리스트를 비교하는 예입니다.
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort((Comparator)null);
}
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, c);
ListIterator<E> i = this.listIterator();
Object[] var4 = a;
int var5 = a.length;
for(int var6 = 0; var6 < var5; ++var6) {
Object e = var4[var6];
i.next();
i.set(e);
}
}
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);
} else if (Arrays.LegacyMergeSort.userRequested) {
legacyMergeSort(a, c);
} else {
TimSort.sort(a, 0, a.length, c, (Object[])null, 0, 0);
}
}
여기서 Collection의 sort메소드의 제네릭 부분을 보게되면
<T extends Comparable<? super T>>
Comparable 을 구현한 클래스를 허용할 수 있는것을 볼 수있습니다.
Collection의 스테틱메소드 sort의 함수 body를 보게되면
List의 default 메소드 sort를 다시 호출하고(list.sort((Comparator)null);) 그안에서
다시 Arrays의 스테틱 메소드를 호출하게됩니다. ( Arrays.sort(a, c);)
그안에서는 Comparable이 정의되어있는지 아닌지를 따라 기본정렬을 할지
상용자가 정의한 대로 정렬을 할지 결정하여 정렬하는 것 입니다.
그렇다면 Comparable을 구현하는 Person클래스를 통해 정렬해보도록 하겠습니다.
class Person implements Comparable<Person> {
private int age;
private String name;
private int salary;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public Person(int age, String name, int salary) {
this.age = age;
this.name = name;
this.salary = salary;
}
@Override
public int compareTo(Person person) {
if (age == person.age)
return 0;
else if (age < person.age)
return -1;
else
return 1;
/*
아래와 같이 사용할 비교할 수도 있음
return Integer.compare(age, person.age);
return age - person.age;
*/
}
}
@Test
public void sortWithComparableImplPerson() {
List<Person> list = new ArrayList<>() {{
add(new Person(10, "a", 3000));
add(new Person(12, "b", 2000));
add(new Person(8, "c", 2500));
add(new Person(13, "d", 4000));
}};
Collections.sort(list);
List<String> actual = Arrays.asList("c", "a", "b", "d");
List<String> expect = list.stream()
.map(p -> p.name)
.collect(Collectors.toList());
Assert.assertThat(actual, CoreMatchers.equalTo(expect));
}
위 Person 을 age순으로 내림차순으로 정렬하고자 한다면
list.sort(Collections.reverseOrder());
혹은
@Override
public int compareTo(Person person) {
return person.age- age;
}
을 바꾸어 줄 수 있습니다.
Comparator Example
그렇다면 Comparator 는 언제 사용하는게 좋을까요 ??
Comparator는 일반적이지 않은 문자열의 길이 순으로 보고싶다던지 Comparable 로 구현한 것 말고
다른 기준으로 정렬하고싶을때 사용할 수 있습니다.
예를 들어 위의 예에서 정렬한대로 나이순으로 정렬해놓은 Person들을
연봉순으로 보고싶다던지 하는 경우가 있을 것입니다.
이럴 때마다 compareTo를 수정하게되면 비효율 적이게 되겠죠.
이럴때 Comparator를 사용하여 정렬이 정렬에 대한 기준을 정해줄 수 있습니다.
@Test
public void comparatorSort() {
List<Person> list = new ArrayList<>() {{
add(new Person(10, "a", 3000));
add(new Person(12, "b", 2000));
add(new Person(8, "c", 2500));
add(new Person(13, "d", 4000));
}};
List<String> expect = Arrays.asList("d", "a", "c", "b");
list.sort((p1, p2) -> p2.salary - p1.salary);
List<String> actual = list.stream()
.map(p -> p.name)
.collect(Collectors.toList());
Assert.assertThat(expect, CoreMatchers.equalTo(actual));
}
Person은 이미 Comparable을 구현하고 있는 클래스이지만,
Comparator를 이용하여 다른 기준으로 정렬을 할 수있습니다.
list.sort((p1, p2) -> p2.salary - p1.salary);
위 코드는 Compartor를 람다를통해 인스턴스로 만든것입니다.
정리
Comparable : 객체 간의 일반적인 정렬이 필요할 때, Comparable 인터페이스를 확장해서 정렬의 기준을 정의하는 compareTo() 메서드를 구현한다.
Comparator : 객체 간의 특정한 정렬이 필요할 때, Comparator 인터페이스를 확장해서 특정 기준을 정의하는 compare() 메서드를 구현한다.
'JAVA' 카테고리의 다른 글
Concrete class == 구상클래스, 구현클래스, 구체클래스?? (0) | 2019.10.18 |
---|---|
Java 8 - More Functional Interface!! (0) | 2019.10.13 |
Java 8 - Function Interface (3) | 2019.10.13 |
Java 8 - Interface바뀐점을 알아보기 (0) | 2019.10.11 |
equals,hashCode 알아보기 (2) | 2019.10.01 |
댓글