본문 바로가기
Java

Java Comparable vs Comparator

by 에어컨조아 2022. 6. 15.

우리가 사용하고 있는 자바언어에서는 다양한 인터페이스들을 제공해주고 있는데요!
얼마전에 개인 공부를 하던 중 객체를 정렬해야하는 경우가 있었습니다.
대략적으로 알지만 Comparable와 Comparator 인터페이스를 이번 기회에 확실하게 정리하고 싶어 작성하게 되었습니다.

Java Docs

일단 JAVA에서 제공하는 기본적인 인터페이스 임으로 공식 문서를 확인해 보겠습니다.

공식 문서를 확인해보니 Comparable 인터페이스는 해당 인터페이스를 구현하는 객체들의 순서를 적용한다고 합니다.

스크린샷 2022-06-10 오전 9 19 24

Comparator에서도 객체들의 전체 순서를 적용한다고 하는데요!

스크린샷 2022-06-10 오전 9 13 02

일단 Comparator는 명시적으로 @FunctionalInterface가 적용되어 있는 것을 확인하실 수 있습니다.

즉 Lamdba를 사용하여 처리를 할 수 있을 것 같습니다.

그럼 공식문서를 토대로 이 둘의 차이점과 기능에 초점을 맞춰 알아보겠습니다.

Comparable vs Comparator

우선 둘의 공통점은 객체들을 비교할 때 사용한다는 것 입니다.

그럼 JAVA에서는 왜 2가지 인터페이스를 통해 객체 비교를 제공하고 있을까요?

이를 위해 사용성의 측면에서 확인해 보겠습니다.

Comparable 인터페이스는 단순 인터페이스 이기 때문에 객체가 구현만 한다면 바로 Collections.sort, Arrays.sort 를 사용하여 비교를 할 수 있습니다.

//Coin.java
public class Coin implements Comparable<Coin> {

    private final int number;

    public Coin(int number) {
        this.number = number;
    }

    @Override
    public int compareTo(Coin compare) {
        return number - compare.number;
    }

    @Override
    public String toString() {
        return "coin : " + number;
    }
}

//Main.java
class Main{
    public static void main(String[] args) {
        List<Coin> coins = Arrays.asList(
            new Coin(3),
            new Coin(6),
            new Coin(1)
        );

        System.out.println("정렬 전");
        coins.forEach(System.out::println);
        Collections.sort(coins);

        System.out.println("정렬 후");
        coins.forEach(System.out::println);
    }
}

//Result
정렬 전
coin : 3
coin : 6
coin : 1
정렬 후
coin : 1
coin : 3
coin : 6

하지만 Coin이라는 Class가 외부 라이브러리에 있는 클래스라면 커스텀할 수 없습니다. (Warpping하여 처리할 수 있지만.. 기능대비 클래스만 많아질 것 같습니다.)

따라서 이럴 경우에 사용하라고 만든 것이 Comparator 입니다.

public class Coin {

    private int number;

    public Coin(int number) {
        this.number = number;
    }

    public int getNumber() {
        return number;
    }
    @Override
    public String toString() {
        return "coin : " + number;
    }
}

class CoinAsc implements Comparator<Coin> {
    @Override
    public int compare(Coin coin1, Coin coin2) {
        return coin1.getNumber() - coin2.getNumber();
    }
}

class Main {

    public static void main(String[] args) {
        List<Coin> coins = Arrays.asList(
            new Coin(3),
            new Coin(6),
            new Coin(1)
        );

        System.out.println("정렬 전");
        coins.forEach(System.out::println);
        Collections.sort(coins, new CoinAsc());

        System.out.println("정렬 후");
        coins.forEach(System.out::println);
    }
}

Comparator를 implements한 CoinAsc가 추가된 것을 확인하실 수 있습니다.

CoinAsc를 사용하여 Collections.sort의 인자로 전달하면 compare메서드를 통해 오름차순 정렬을 진행하는 것을 확인할 수 있습니다.

정리하자면 Comparator는 아래 2가지 경우일 때 사용해야 합니다.

  1. 클래스를 조작하기 힘들 경우
  2. 객체의 정렬 조건을 외부에서 결정할 경우

마지막으로 CoinAsc 클래스처럼 별도의 클래스를 만들지 않고 Lamdba를 사용하여 구현해보도록 하겠습니다.

// 변경 전
Collections.sort(coins, new CoinAsc());

// 변경 후 
Collections.sort(coins, Comparator.comparingInt(Coin::getNumber));

별도의 클래스를 사용하지 않고 깔끔하게 동일한 결과를 반환하는 것을 확인하실 수 있습니다.

Comparable, Comparator 둘다 사용할 경우 우선순위!

조금만 생각하면 이는 고민할 필요가 없는 문제입니다.

만약 Coin 클래스에 Comparable을 오름차순으로 implements 하고 있고, Collections.sort의 매개변수로 Comparator를 주입하여 내림차순으로 설정했다면 최종 결과는 오름차순일까요? 내림차순일까요?

이는 당연히 외부에서 주입해주는 것으로 동작하는 것이 의도 상 맞을 것 입니다.

따라서 최종 결과는 내림차순으로 정렬됩니다.!

Collections.sort vs Arrays.sort

둘다 정렬해주는 알고리즘인데… 이 둘은 무슨 차이일까요?

이름만 보면 Arrays는 배열에 최적화된 정렬을 제공하고 Collections은 Collection인터페이스를 구현한 클래스들을 정렬할때 최적화된 것인가? 하고 생각할 수 있습니다.

조금 더 Code단으로 들어가 보겠습니다.

Arrays.sort는 크게 Primitive Type, Reference Type 2가지를 받을 수 있는데요!

Primitive Type같은 경우 QuickSort를 사용하는 것을 확인할 수 있습니다.

Untitled

그렇다면 Reference Type은 어떤 알고리즘을 사용할까요?

Untitled (1)

MergeSort, TimeSort를 사용하는 것을 확인할 수 있는데요!

MergeSort는 이후 버전에 제거될 예정이라고 하니 TimeSort만 확인하면 될 것 같습니다.(Java 8기준)

그렇다면 Collections.sort는 어떤 알고리즘을 사용하는지 확인해 보겠습니다.

Untitled (2)

Untitled (3)

Arrays.sort를 사용하는 것을 확인하실 수 있습니다.

정리하자면 Collections.sort는 TimeSort를 사용하고 Arrays.sort는 매개변수 타입에 따라 QuickSort, TimeSort를 결정하는 것을 확인했습니다.

알고리즘을 확인했으니 가장 중요한 시간복잡도를 알아보겠습니다.

Untitled (4)

QuickSort보단 TimeSort가 조금 더 나은 성능을 제공하는 것을 확인할 수 있었습니다.

따라서 Arrays.sort를 사용할 경우 타입을 확인하여 대응하는 것이 조금 더 성능 상에 좋을 것 같습니다.

댓글