개발 & 데이터베이스/JAVA

자바 Comparable과 Comparator 차이점

K.두부 2023. 2. 8. 00:36
반응형

Comparable과 Comparator 은 인터페이스 (interface) 다.

그렇기 때문에 Comparable 과 Comparator 을 사용하기 위해서는 인터페이스 내에 선언된 메소드를 필수적으로 재정의해야한다.

 

두 개의 인터페이스는 알고리즘을 풀어보면서 자주 언급했던 부분이다. 

실제로 알고리즘과 별개로 꼭 알아두어야하는 인터페이스 중 하나이므로 기억해주는 게 좋다.

 

Comparable

해당 인터페이스에는 compareTo(T o) 메소드 하나가 선언되어있다.

즉, Comparable 인터페이스를 사용하기 위해서는 compareTo(T o) 메소드의 재정의가 필수다.

 

Comparator

해당 인터페이스에는 많은 메소드가 선언되어있다.

하지만 필수로 재정의 해주어야하는 건 compare(T o1, T o2) 메소드다.

 

 

객체를 정렬하기 위해선 피해갈 수 없는 두 개의 인터페이스 Comparable 과 Comparator 

사실 객체를 '정렬을 하기 위해서' 보다는 '비교할 수 있도록 만든다.' 라는 표현이 더 정확할지도 모른다.

public class Main {
    public static void main(String[] args) {
        int num = 1;
        int num2 = 2;
        
        if (num > num2) {
            // num 이 더 큰 경우
        }
    }
}

primitive 타입의 변수 (int, double, float ···)의 경우 부등호를 이용해서 쉽게 비교할 수 있다. 

 

하지만 클래스 객체를 생성했을 경우에는 얘기가 다르다. 예를 들어 몸무게와 키를 가진 Body 객체 클래스를 만들어보겠다.

public static Main {
    public static class Body {
        int w, h;
        
        public Body (int w, int h) {
            this.w = w;
            this.h = h;
        }
    }
    
    public static void main(String[] args) {
        Body bodyA = new Body(50, 163);
        Body bodyB = new Body(75, 178);
        
        // 비교...
        
        
        
    }
}

Body 클래스 객체를 선언 후에 BodyA 와 BodyB 라는 객체를 두 개 생성했다.

 

두 개의 객체를 어떻게 비교할까? 몸무게 (w), 키 (h) 중에 어떤 걸 기준으로 해야할지 도저히 감이 안잡힌다.

이 부분을 해결해주기 위해서 사용하는 것이 Comparable 과  Comparator 인터페이스다.

그렇기 때문에 '정렬을 하기 위해서' 사용한다기 보단 '비교할 수 있도록 만든다.'에 가깝다고 표현한 것이다.

 

Comparable

자기 자신과 매개변수 객체를 비교하는 인터페이스

public static class Body implements Comparable<Body> {
        int w, h;
        
        public Body (int w, int  h) {
            this.w = w;
            this.h = h;
        }
        
        // 키가 큰 순으로 정렬
        public int compareTo(Body o) {
            if (this.h > o.h) {
                return -1;
            } else {
                return 1;
            }
        }
    }
    
    public static void main(String[] args) throws IOException {  
        Body[] body = {new Body(50, 163), new Body(75, 178), new Body(64, 182)};
        
        Arrays.sort(body);
        
        for (Body b : body) {
            System.out.println(b.w + " :: " + b.h);
        }
    }
    
/*
64 :: 182
75 :: 178
50 :: 163
*/

인터페이스를 정의할 때 Comparable<T>, compareTo(T o) 라고 되어 있을텐데 여기서 T는 객체타입을 의미한다.

Comparable 인터페이스는 자기 자신 (Body 객체 클래스) 와 매개변수 객체를 비교한다고 했으므로 T에는 Body를 입력해주면 된다.

 

위 예제는 임의의 데이터 3개를 입력하고, 키가 큰 순으로 정렬을 했다. 

compareTo 메소드는 자기 자신을 기준으로 값을 비교해서 양수, 0, 음수를 반환해준다.

즉, 위 예제에서 -1 (음수) 와 1 (양수)를 반환했지만 사실상 -51234 과 43242로 변경해도 큰 문제가 없다.

 

Comparator

두 개의 매개변수 객체를 비교하는 인터페이스

public class Main {
    public static class Body implements Comparator<Body> {
        int w, h;
        
        public Body (int w, int  h) {
            this.w = w;
            this.h = h;
        }
        
        public int compare(Body o1, Body o2) {
            return o2.h - o1.h;
        }
    }
    
    public static void main(String[] args) throws IOException {  
        Body[] body = {new Body(50, 163), new Body(75, 178), new Body(64, 182)};
        
        int tmp = body[0].compare(body[1], body[2]);
        
        System.out.println(tmp); // 양수
    }
}

compare 메소드는 자기 자신과의 비교가 아닌 두 개의 객체를 비교하는 것이기 때문에 파라미터가 o1, o2로 두 개다.

 

위의 예제를 보면

body[0].compare(body[1], body[2]); 로 선언한 부분이 있다.

 

body[0] 객체의 compare 메소드를 통해서 두 개의 매개변수를 비교하지만 두 값을 비교하는 데에 body[0] 객체는 아무 관련이 없다. 오로지 body[1] 과 body[2] 를 비교해서 반환한다.

 

Java에서의 정렬

이 부분에 대해서도 알고리즘을 풀면서 자주 언급했던 부분이다.

 

기본적으로 Java에서의 정렬은 재정의를 해주지 않으면 '오름차순'으로 정렬한다.

즉, Arrays.sort(), Collections.sort() 는 모두 오름차순으로 정렬된다.

 

음수일 경우: 두 원소의 위치를 교환 X

양수일 경우: 두 원소의 위치를 교환 O

 

오름차순 정렬을 쉽게 풀어서 얘기해보면 '첫번째 값이 두번째 값보다 작다'를 의미한다.

그렇기 때문에 compare 혹은 compareTo 메소드를 사용했을 경우 음수가 나오면 두 개의 원소 위치를 바꾸지 않는 것이다.

 

즉, 내림차순 정렬로 재정의를 하기 위해선 '첫번째 값이 두번째 값보다 크다'를 의미하고 있어야하므로 양수 값이 나오게 하는 것이다.

 

Overflow or Underflow

compare 혹은 compareTo 메소드를 사용할 경우 대부분 아래와 같이 재정의 할텐데 한 가지 주의해야할 점이 있다.

public class Main {
    public static class Body implements Comparator<Body> {
        int w, h;
        
        public Body (int w, int  h) {
            this.w = w;
            this.h = h;
        }
        
        public int compare(Body o1, Body o2) {
            return o2.h - o1.h;
        }
    }
}

위와 같이 비교 후에 반환하게 되면 int 형의 범위를 벗어나는 경우가 생길 수도 있다. int 형의 범위를 벗어나게 되면 최솟값과 최대값으로 반환하게 된다.

 

해당 자료형의 최솟값을 벗어나게 되면 Underflow가 발생하고, 최대값을 벗어나게 되면 Overflow가 발생한다.

때문에 compare 혹은 compareTo 메소드를 구현하기 전에 해당 오류가 발생할 수 있는지 확인하고 사용하는 게 좋다.

반응형