JAVA - 심화 : Comparable 과 Comparator

Seok-Hyun Lee·2021년 8월 4일
0

JAVA

목록 보기
20/21

Comparable & Comparator

  • Comparable< T >과 Comparator< T > 는 둘 다 Interface
    • 둘 다 제네릭, 객체 명시 필요
  • Comparable의 compareTo()는 자기 자신을 기준으로 매개 객체 1개와 비교
  • Comparator의 compare()는 자신과 상관없이 매개 객체 2개를 비교
  • 두 비교 메소드는 int를 반환
  • 비교 메소드의 반환값이 양수면 비교의 선행 객체가 크고, 음수면 후행 객체가 크다, 0 이면 동일

Comparable

Comparable< T > 는 아래와 같이 정의해서 사용할 수 있다.

class Student implements Comparable<Student>{
    String name;
    int age;
    
    Student(String name, int age){
        this.name = name;
        this.age = age;
    }
    
    @Override
    public int compareTo(Student o) {
        if ( this.age > o.age) return 1;
        else if (this.age < o.age) return -1;
        else return 0;
    }
}

Comparable 인터페이스의 compareTo 메소드를 구현해주어야 하며, 자신을 기준으로 매개 객체와 비교를 한다. 이때, 자신의 값이 크면 양수, 작으면 음수, 동일하면 0 을 반환하게 만든다.

그리고 위의 메소드를 간단하게 아래와 같이 만들 수도 있다.

    @Override
    public int compareTo(Student o) {
        return this.age - o.age;
    }

그리고 이 compareTo 는 아래와 같이 사용하면 된다.

Student a = new Student("a",12);
Student b = new Student("b",21);

int isOlder = a.compareTo(b);
System.out.println(isOlder);

위의 결과는 아래와 같다.

-1

우리가 생각한대로 b 가 a 보다 나이가 많다는 결과를 얻을 수 있다.

Comparator

Comparator< T > 의 경우는 Comparable 과 다르게 자신과 상관없는 두 매개 객체를 비교하기 위해 사용한다. 그리고 인터페이스이기 때문에 Comparable과 같이 implements 할 수 있지만 주로 아래와 같이 익명 구현 객체 방식으로 활용된다. 이때 compare() 메소드를 Override 해서 필요한 비교 연산을 수행해주면 된다.

    static Comparator<Student> comp1 = new Comparator<Student>(){
        public int compare(Student s1, Student s2) {
            return s1.age - s2.age;
        }
    };

    static Comparator<Student> comp2 = new Comparator<Student>() {
        @Override
        public int compare(Student o1, Student o2) {
            return o1.name.compareTo(o2.name);
        }
    };

이렇게 사용하는 이유는 자신과 상관없는 객체에 대한 비교 연산을 수행하기 때문이다. 그리고 main 메소드에서 아래와 같이 사용할 수 있다.

comp1.compare(a,b);
comp2.compare(a,b);

이렇게 익명 구현 객체로 Comparator 를 사용하면 다양한 비교가 가능하다는 장점을 가지고 있다. 위의 예시에도 있지만, 나이를 비교하기 위한 comparator 도 있지만, 이름을 비교하기 위한 comparator 도 있다.

정렬

Java에서 정렬은 기본적으로 오름차순이다. 즉, 비교 과정에서 선행 객체가 후행 객체보다 작다면 swap이 일어나지 않아도 된다.

그리고 Arrays.sort()나 Collections.sort() 에선 Comparator 의 익명 구현 객체를 2번째 매개로 가실 수 있으며 위의 논리가 적용된다.

Arrays.sort(arr,comp1); // 이름 오름차순 정렬
Arrays.sort(arr,(o1,o2)->o2.age-o1.age); // 이름 내림차순 정렬

그래서 위와 같이 원래 음수가 나와야 하는 값을 양수가 되게 만들면 내림차순 정렬이 된다.

주의 사항

비교를 할 때 매우 주의해야 하는 것들이 있다. 바로 Underflow 와 Overflow 이다.

Underflow & Overflow

Underflow는 데이터 타입이 가질 수 있는 값의 하한을 넘어가게 되는 경우이고, Overflow는 데이터 타입이 가질 수 있는 값의 상한을 넘어가게 되는 경우이다.

두 에러가 발생하는 경우는 유사하기 때문에 Underflow의 경우만 설명하고자 한다. 들예를 들어, 두 정수 a와b가 있고, a 가 b보다 크지만 값을 아래와 같이 설정하였다고 생각해보자.

int a = 1;
int b = Integer.MIN_VALUE;

System.out.println(b); 
System.out.println(b - a);

위의 결과는 아래와 같다.

-2147483648
2147483647

우리는 1 이 빠지면서 -2147483649가 될것이라 무심코 생각할 수 있다. 하지만, 절대값에서 1이 빠지고 부호가 바뀌었다. 즉, 양수가 되었다.

여기서 유추할 수 있는 점은 Underflow가 발생하면 선행과 후행의 비교로 양수나 음수를 반환하는 compareTo() 나 compare() 에서 에러가 생길 수 있다. 아래의 예시를 보자.

Student s1 = new Student("a",1,1);
Student s2 = new Student("a",Integer.MIN_VALUE,1);

int is_s2_Older_than_s1 = s2.compareTo(s1);

if (is_s2_Older_than_s1>0) {
    System.out.println("Yes, S2 is Older than S1");
}
else if (is_s2_Older_than_s1<0) {
    System.out.println("No, S2 is Younger than S1");
}

아래는 그 결과이다.

Yes, S2 is Older than S1

우리가 예상한 결과와는 완전히 다르게 나왔다. 이런 이유로 비교 메소드를 작성할 때는 데이터 타입의 상한과 하한 값에 대해 유념해야 한다.

이를 피하기 위한 좋은 방법은 아래와 같이 < > == 와 같은 비교 연산자를 사용하는 것도 좋다.

    public int compareTo(Student o) {
        if ( this.age > o.age) return 1;
        else if (this.age < o.age) return -1;
        else return 0;
    }
profile
Arch-ITech

0개의 댓글