[알고리즘] Comparable vs Comparator

나영·2023년 6월 25일

알고리즘

목록 보기
2/8
post-thumbnail

객체 비교 및 정렬에 자주 쓰이지만 헷갈리는 개념인 ComparableComparator 의 개념을 확실하게 정리해보자. 🫡

우선 ComparableComparator 는 모두 인터페이스이다. 즉, 이를 사용하기 위해서는 인터페이스 내에 선언된 메소드반드시 구현해야 한다는 것 !

  • Comparable : compareTo(T o)
  • Comparator : compare(T o1, T o2)

Comparable

특징

  • Comparable 은 메소드의 파라미터가 1개라는 것에서 알 수 있듯이, "자기 자신과 매개변수 객체를 비교" 한다.
  • Java.lang 패키지에 있어 import 해줄 필요가 없다.

예제

Student 클래스를 비교해보자. 우선 Student 클래스에 Comparable 을 implement 하고, compareTo 메소드를 구현해야 할 것이다.

먼저 나이 를 기준으로 비교해본다고 가정하자. 즉, 자기 자신의 나이를 기준으로 매개변수로 들어온 상대방인 o의 나이의 값을 비교하면 된다 !

코드는 다음과 같다.

class Student implements Comparable<Student> {
 
	int age;			// 나이
	int classNumber;	// 학급
	
	Student(int age, int classNumber) {
		this.age = age;
		this.classNumber = classNumber;
	}
	
    // 필수 구현 부분
	@Override
	public int compareTo(Student o) {
    
		// 자기 자신의 age가 o의 age보다 크다면 양수 (자리 바뀜)
		if(this.age > o.age) {
			return 1;
		}
		// 자기 자신의 age와 o의 age가 같다면 0
		else if(this.age == o.age) {
			return 0;
		}
		// 자기 자신의 age가 o의 age보다 작다면 음수 (자리 그대로)
		else {
			return -1;
		}
	}
}

compareTo 에서 대소 관계 파악의 과정은 자기 자신을 기준으로 상대방 (매개변수) 과의 차이를 리턴하는 것으로, 다음의 그림을 보면 이해가 잘 될 것이다.

이를 활용해 주로 1, 0, -1 로 표현한다.
즉, 양수가 리턴되면 자리가 바뀌는 것 !

그렇다면 정수가 아닌 두 값의 차를 바로 반환해 번거로운 조건식을 없애볼 수 있을 것이다.

코드는 다음과 같다.

class Student implements Comparable<Student> {
 
	int age;			// 나이
	int classNumber;	// 학급
	
	Student(int age, int classNumber) {
		this.age = age;
		this.classNumber = classNumber;
	}
	
	@Override
	public int compareTo(Student o) {
		return this.age - o.age;
	}
}

Comparator

특징

  • Comparator 은 메소드의 파라미터가 2개라는 것에서 알 수 있듯이, "두 매개변수 객체를 비교" 한다.
  • Java.util 패키지에 있어 import 가 필요하다.

예제

위와 마찬가지로 Student 객체를 비교해보는데, 이번에는 학급 을 기준으로 해보자.

Comparable 과는 다르게 두 객체를 비교하는 것이므로 o1o2 의 학급을 비교해주는 것이다. 즉, 객체 자체와는 상관 없이 독립적으로 매개변수로 넘겨진 두 객체를 비교하는 것이 포인트 !

import java.util.Comparator;	// import 필요
class Student implements Comparator<Student> {
 
	int age;			// 나이
	int classNumber;	// 학급
	
	Student(int age, int classNumber) {
		this.age = age;
		this.classNumber = classNumber;
	}
	
	@Override
	public int compare(Student o1, Student o2) {
    
		// o1의 학급이 o2의 학급보다 크다면 양수
		if(o1.classNumber > o2.classNumber) {
			return 1;
		}
		// o1의 학급이 o2의 학급과 같다면 0
		else if(o1.classNumber == o2.classNumber) {
			return 0;
		}
		// o1의 학급이 o2의 학급보다 작다면 음수
		else {
			return -1;
		}
	}
}

마찬가지로 두 객체의 차이를 바로 반환해볼 수 있다.

코드는 다음과 같다.

class Student implements Comparator<Student> {
 
	int age;			// 나이
	int classNumber;	// 학급
	
	Student(int age, int classNumber) {
		this.age = age;
		this.classNumber = classNumber;
	}
	
	@Override
	public int compare(Student o1, Student o2) {
		return o1.classNumber - o2.classNumber;
	}
}

활용 (2차원 배열 정렬)

2차원 배열을 바로 Arrray.sort()를 통해 정렬하려고 하면 java.lang.ClassCastException: I cannot be cast to java.lang.Comparable 오류가 발생한다. 원인은 비교 기준이 구현되어 있지 않기 때문에 캐스팅에 실패했기 때문 !

1. Comparator 익명 클래스 구현

  • compare() 메서드를 Override 하여 원하는 정렬 기준 명시
  • 오름차순, 내림차순 외에 원하는 조건으로도 구현 가능
  • 2차원 배열 뿐 아니라 비교 가능한 모든 클래스에서 사용 가능
int[][] arr = new int[][]{{5,40},{3,50},{1,30},{4,20},{2,10}};

// 1. Comparator 익명 클래스 구현
Arrays.sort(arr, new Comparator<int[]>() {
    @Override
    public int compare(int[] o1, int[] o2) {
        return o1[0] - o2[0]; // 첫번째 숫자 기준 오름차순 {1,30}{2,10}{3,50}{4,20}{5,40}
        //return o2[0] - o1[0]; // 첫번째 숫자 기준 내림차순 {5,40}{4,20}{3,50}{2,10}{1,30}
        //return o1[1] - o2[1]; // 두번째 숫자 기준 오름차순 {2,10}{4,20}{1,30}{5,40}{3,50}
        //return o2[1] - o1[1]; // 두번째 숫자 기준 내림차순 {3,50}{5,40}{1,30}{4,20}{2,10}
    }
});

// 다중 조건 
int[][] arr2 = new int[][]{{5,40},{3,50},{1,30},{4,20},{2,10},{6,40},{6,50},{6,10},{6,20},{6,30}};

Arrays.sort(arr2, new Comparator<int[]>() { 
    @Override
    public int compare(int[] o1, int[] o2) {
        return o1[0] != o2[0] ? o1[0] - o2[0] : o1[1] - o2[1]; // 첫번째 기준 오름차순, 같을 땐 -> 두번째 기준 오름차순  : {1,30}{2,10}{3,50}{4,20}{5,40}{6,10}{6,20}{6,30}{6,40}{6,50}
        //return o1[0] != o2[0] ? o1[0] - o2[0] : o2[1] - o1[1]; // 첫번째 기준 오름차순, 같을 땐 -> 두번째 기준 내림차순  : {1,30}{2,10}{3,50}{4,20}{5,40}{6,50}{6,40}{6,30}{6,20}{6,10}
    }
});

// 다중 조건 (if문 사용, 의미는 위와 같음.)
Arrays.sort(arr2, new Comparator<int[]>() { 
    @Override
    public int compare(int[] o1, int[] o2) {
    	if (o1[0] == o2[0]) { // 첫번째 기준 오름차순, 같을 땐 -> 두번째 기준 오름차순  : {1,30}{2,10}{3,50}{4,20}{5,40}{6,10}{6,20}{6,30}{6,40}{6,50}
        	return o1[1] - o2[1]; 
        }
        else { // 첫번째 기준 오름차순, 같을 땐 -> 두번째 기준 내림차순  : {1,30}{2,10}{3,50}{4,20}{5,40}{6,50}{6,40}{6,30}{6,20}{6,10}
        	return o2[1] - o1[1];
        }
});

2. Lambda 사용

  • Java 8 버전 이상부터는 람다식을 활용해 더욱 더 간결하게 표현 가능
int[][] arr = new int[][]{{5,40},{3,50},{1,30},{4,20},{2,10}};

// 2. Lambda 사용 - Java 8이상
Arrays.sort(arr, (o1, o2) -> {
    return o1[0] - o2[0]; // 첫번째 숫자 기준 오름차순 {1,30}{2,10}{3,50}{4,20}{5,40}
});

💡 참고한 글

0개의 댓글