Comparable VS Comparator

Gooder·2021년 8월 11일
0

객체지향

목록 보기
2/2

Java에서 비교가 필요한 기능을 구현(ex. 정렬)할 때 반드시 Comparable이나 Comparator를 사용해야합니다.
레퍼런스로 따라간 객체들이 있는 경우에 어떤 기준으로 판단해야하는지 비교 대상이 되는 객체에 직접 물어보거나(comparable) 제 3의 객체에게 물어보는 방법(comparator)이 있습니다.
이를 비교하기위해 메서드를 재정의한 후에 호출해줘야합니다.
단, 크기 비교를 할 때 다음을 따라서 리턴 값을 정해줘야합니다.

리턴값의 의미

  • 음수 - 내가 작다.
  • 0 - 둘이 같다.
  • 양수 - 내가 크다.

Comparable?

객체 스스로가 다른 객체와 비교하게 만드는 방법

Comparable을 구현한 클래스는 다양한 제네릭 알고리즘 및 Comparable 인터페이스를 이용하도록 작성된 컬렉션 구현체와도 전부 연동이 가능합니다.

비교할 때에 comparable로 형변환 해서 비교가 들어가기 때문에, comparable을 구현한 객체들간의 비교만 가능하다. 그렇지않으면 ClassCastException을 던집니다.
-> comparable한 애들끼리만 비교가 가능하다.

자바 플랫폼 라이브러리에 포함된 거의 모든 value class 들은 Comparable 인터페이스를 구현한다.

compareTo를 구현할 때 반드시 지켜야하는 사항

cf) sgn(expression)은 수학에서의 signum 함수로 -1,0,1 가운데 한 값을 반환
어떤 값이 반환될지는 expression의 값이 음수인지 0인지 양수인지에따라서 결정

  • compareTo를 구현할 때에는 도는 x,와 y에 대해서 sgn(x.compareTo(y)) == - sgn(y.compareTo(x))가 만족되도록 해야한다.
    -> 둘 중 하나가 예외를 발생시킨다면 다른 하나도 예외를 발생시켜야한다.
  • compareTo를 구현할 때는 transitivity를 만족하도록 해야한다.
    (x.compareTo(y) > 0 && y.compareTo(z)>0) 이면 x.compareTo(z)>0이 되어야한다.

이를 요약하면 x>y면 y<x다 라는 뜻이고 x>y이고 y>z이면 x>z가 성립해야한다는 뜻이다.
위의 모든 사항은 x.compareTo(y) == 0 일 때도 성립해야한다.

compareTo는 동치성 검사가라기보다는 순서 비교이기 때문에
추가적으로 (x.compareTo(y)==0) == (x.equals(y))를 만족할 수 있게 구현하는 것이 중요하다.

이렇게 comparable은 Java에서 내장함수로 사용하는 정렬에대한 기준을 제공합니다.

그 기준은 Comparable 인터페이스의 공식문서를 확인하면 알 수 있습니다.

이 인터페이스는 클래스의 자연스러운 순서를 따르고 그 클래스의 compareTo 메서드 역시 자연스러운 비교 메서드로 되어있어야한다.

정말 자연스러운 순서(원문 natural ordering)이 애매모호합니다.
과연 자연스럽다는 것은 무엇일까요?

Comparable에서의 natural ordering

이에대한 답은 예시를 몇 개 보면 감이 올 것이다.

  • 문자는 사전순으로 나열
  • 숫자는 오름차순 정렬

이런 규약에 따라서
규약을 지키지않는 순서(ex. 역순정렬 등)는 Java의 입장에서는 natural한 것이 아니기 때문에, 정해진 규약 이외의 다른 조건으로 정렬을 하고싶으면 comparator를 사용해야한다.

Comparator

comparator를 사용하는 경우
Comparable을 구현하지않고 있거나 조금 특이한 순서 관계를 사용하는 경우에 사용가능.
다중조건으로 정렬하려면 comparator에서 조건문으로 처리하면 된다.
comparator를 사용하면 comparable의 compareTo는 무시되기 때문에, 비교하려는 객체가 comparable해도 comparator를 사용하면 comparable에서 정의한 compareTo가 아닌 compare을 보면서 확인합니다.
compareTo를 재정의하지않고도 원하는 기준으로 정렬을 할 수 있다는 점이 가장 큰 장점인 것 같습니다.

요약

상점가에서 우리 집이 더 싸요 라고 말하는게 compareTo
가격 비교 사이트에서 비교해주는게 comparator

정렬을 하는 알고리즘 자체를 바꾸는 것이 아닌 비교하는 방식을 바꿔 순서를 결정하는 판단을 바꾸는 것이다.

구현

Comparable

다음과 같이 클래스를 만들고 comparable을 구현할 수 있습니다.

public class Student implements Comparable<Student>//이걸 막으면 compareTo를 만들어놔도 안된다.
{
    int no, score;

    public Student(int no, int score) {
        this.no = no;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "no=" + no +
                ", score=" + score +
                '}';
    }
    //  음수  내가 작다 --> 둘을 그대로
    //  0    둘이 같다 --> 둘을 그대로
    //  양수  내가 크다 --> 서로 바꿈
    //@Override
    public int compareTo(Student o) {
        if(this.score==o.score){
            if(this.no == no) return 0;
            return this.no>o.no?1:-1;
        }
        return this.score>o.score?-1:1;
    }
    @Override
    public int compareTo(Student o){
        return this.no - o.no;
        return o.no - this.no;
        return Integer.compare(this.no,o.no);
        return Integer.compare(o.no,this.no);
    }
}

cf) Integer은 자체적으로 compare의 기능을 하는 메서드를 구현해놨습니다.

Comparator

public class ComparatorTest {
    static class StudentComparator implements Comparator<Student>{

        @Override
        public int compare(Student o1, Student o2) {
            return 0;
        }
    }
    public static void main(String[] args) {
        Student[] students = {
                new Student(1, 10),
                new Student(3, 50),
                new Student(2, 80),
                new Student(4, 10)
        };
        System.out.println("정렬 전 : " + Arrays.toString(students));
        Arrays.sort(students,new StudentComparator());
        System.out.println("점수기준 오름차정렬 후 : " + Arrays.toString(students));
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o2.score-o1.score;
            }
        });
        Arrays.sort(students,
            //타입 유추가 가능하면 타입 생략 가능.
            (Student o1, Student o2)-> {

            ( o1,  o2)-> {
                return o2.score-o1.score;
            }
            (o1,  o2)-> o2.score-o1.score
        );
        //다중 조건 정렬
        //block을 줘도 좋지만 삼항연산자로 잡고 가도 좋다.
        Arrays.sort(students,(o1,o2)->o2.score != o1.score? o2.score-o1.score:o2.no-o1.score);
        System.out.println("점수 기준 내림차순 정렬 후 :"+Arrays.toString(students));

        int[][] studentsArray = {
                {1,10},
                {3,50},
                {2,80},
                {4,10}
        };
        Arrays.sort(studentsArray, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[0]-o2[0];
            }
        });
        Arrays.sort(studentsArray,(o1, o2)->o1[0]-o2[0]);

    }
}
profile
세상을 변화시킬 신스틸러 서비스를 만들고싶은 개발자 Gooder 입니다.

0개의 댓글