Java Comparable

수박참외메론·2023년 1월 15일
0

개요

Comparable 인터페이스는 안에 compareTo 함수를 가지고 있는 interface 로 이를 구현하면 많은 Collection들에 sort 함수를 인자로 받아 사용할 수 있어서 유용하다.

간단하게 구현방식은 아래와 같다. (아래는 책에서 권장하지 않는 방식이었다..)

class Test implements Comparable<T> {
	int a;
    int b;
	@Overrides
    public int compareTo(T other){
    	if(this.a < other.a) return 1;
        else if(this.a > other.a) return -1;
        else if(this.b > other.b){
        	
        }
    }
}

Comparable 인터페이스의 compareTo() 메서드는 equals 메서드와 다른 두가지 특징을 가지고 있다.

  • 단순 동치성 비교(equals도 마찬가지) + 순서까지 비교
  • Comparable를 구현했다는 것은 그 클래스의 인스턴스들은 순서가 있음을 의미

그래서 Comparable을 구현한 클래스는 간편하게 정렬이 가능하다.

Arrays.sort(a);

아래는 내가 코딩테스트를 대비할 때 사용했던 실제 코드이다.

class Node implements Comparable<Node>{
    public int x;
    public int y;
    public int index;
    public Node left;
    public Node right;
    public Node parent;
    public Node(int a, int b, int c)
    {
        x = a;
        y = b;
        index = c;
    }
    @Override
    public int compareTo(Node a)
    {
        if(this.x > a.x)
            return 1;
        else
            return -1;
    }
}
class Solution {
    public void dfs(int current, int start, int end)
    {
        ...
    }
    public int[][] solution(int[][] nodeinfo) {
        int[][] answer = new int[2][nodeinfo.length];
        nodes = new Node[nodeinfo.length];

        for(int i=0; i<nodeinfo.length; i++)
            nodes[i] = new Node(nodeinfo[i][0], nodeinfo[i][1], i+1);
        Arrays.sort(nodes);
        ...
    }
}

이런식으로 bfs 같은 탐색을 해야할때 유리하게 시작하거나, 여러 그래프 탐색 문제에서 정렬할 일이 많으므로 Comparable 인터페이스를 자주 구현해주곤 했다.

compareTo 의 일반규약

정의

해당 객체와 주어진 객체의 순서를 비교하는 메서드이다. 이 객체보다 작으면 음의 정수를, 같으면 0, 크면 양의 정수를 반환한다.
그리고, 만약 서로 비교하지 못하는 클래스를 비교한다면 단순히 ClassCastException을 던진다.

규약들

  • Comparable 을 구현한 클래스는 모든 x, y에 대해 sgn(x.compareTo(y)) == - sgn(y.compareTo(x)) 여야 한다. (대칭성)
  • Comparable을 구현한 클래스는 추이성을 보장해야 한다. (추이성)
  • Comparable을 구현한 클래스는 모든 z에 대해 x.compareTo(y) == 0 이면 sgn(x.compareTo(z)) == sgn(y.compareTo(z)) 다. (추이성)
  • (x.compareTo(y)) == 0) == (x.equals(y)) 여야 한다. (꼭 안지켜도 되지만 클래스의 순서가 equals 메서드와 동일하지 않다는 것을 알아야 함)

특징

  • 타입을 인수로 받는 제네릭 인터페이스이므로 compareTo 메서드의 인수타입은 컴파일 타임에 정해진다.

작성요령

  • 기본적으로 객체 참조 필드를 비교하려면 compareTo 메서드를 재귀적으로 호출
  • Comparable을 구현하지 않은 필드나 표준이 아닌 순서로 비교해야 한다면 Comparator을 대신 사용한다.
  • 정수나 실수나 상관없이 박싱된 기본타입들에 정적메서드인 compare을 사용하자.
  • 핵심 필드가 여러개라면 가장 핵심적인 필드부터 비교해나가자.
public int compareTo(PhoneNumber pn) {
	int result = Short.compare(areaCode, pn.areaCode);
    if (result == 0) {
    	result = Short.compare(prefix, pn.prefix);
        if (result == 0)
        	result = Short.compare(lineNum, pn.lineNum);    
    }
    return result;
}
  • 자바 8부터는 comparator construction method와 팀을 꾸려 메서드 연쇄 방식으로 비교자를 생성할 수 있다.

Comparator

이 비교자들을 Comparable 인터페이스가 원하는 compareTo 메서드를 구현하는데 간결하게 코드를 짤 수 있다. 하지만 약간의 성능 저하가 발생하기도 한다.(책의 저자는 10%정도 느려졌다고 한다)

위의 PhoneNumber 클래스를 Comparator을 사용해서 구현한 모습이다.

private static final Comparator<PhoneNumber> COMPARATOR = 
	comparingInt((PhoneNumber pn) -> pn.areaCode)
    	.thenComparingInt(pn -> pn.prefix)
        .thenComapringInt(pn -> pn.lineNum);
        
    public int compareTo(PhoneNumber pn) {
    	return COMAPRATOR.compare(this, pn);
    }

여기서 comparingInt 는 키 추출 함수(lambda로) 를 인수로 받아 그 키를 기준으로 순서를 정하는 comparator를 반환하는 정적 메서드이다.
그 다음 두번째 비교자 생성 매서드인 thenComparingInt가 수행되어 int 키 추출자 함수를 입력받아 비교자를 반환하여 계속 비교를 진행하게 된다.

주의할 점

  • "값의 차"를 기준으로 첫번째 값이 두번째 값보다 작으면 음수를, 두 값이 같으면 0, 첫번째 값이 크면 양수를 반환하는 방식을 사용하면 안된다.
  • 정수 오버플로를 일으키거나 부동소수점 계산방식에 따른 오류를 발생.
profile
하루하루는 성실하게 인생전체는 되는대로

0개의 댓글