Comparator와 Comparable 인터페이스
객체 정렬에 필요한 메서드를 정의한 인터페이스, 정렬 기준을 제공하는 것이 목적, 객체를 비교할 수 있도록 한다. 기본형 (Primitive Type)은 연산자를 통해 쉽게 비교가 가능하다. 하지만 객체를 비교하려 할때는 무엇을 기준으로 비교해야 하는지 명확하지 않다. 사용자가 기준을 정하지 않는 이상 어떤 객체가 높은 우선순위인지 판단할 수 없다는 것이다. 이런 문제점을 해결하기 위해 기준을 정한는데 사용되는 인터페이스가 Comparable/Comparator이다.
Comparable : 기본 정렬기준을 구현하는데 사용.
Comparator : 기본 정렬기준 외에 다른 기준으로 정렬하고자 할 때 사용
Comparable
- 객체의 기본 정렬기준을 구현하는데 사용된다.
- Comparable은 comparTo 한개의 추상 메서드를 가지고 있다.
- compareTo()은 한개의 파라미터를 가지며 자기 자신과 파라미터로 들어오는 객체를 비교한다.
public interface Comparable<T> {
public int compareTo(T o); // 주어진 객체(0)를 자신과 비교
}
- compare()와 compareTo()는 두 객체의 비교 결과를 반환하도록 작성(같으면 0, 오른쪽이 크면 음수(-) , 작으면 양수(+))
Comparable인터페이스의 compareTo() 추상 메서드를 구현할 때 객체를 비교하기 위한 기준을 정의한다.compareTo() 메서드는 반환타입이 int이다 이는 값을 비교해서 정수로 반환 해야 하는 것을 의미하는데 이 정수값은 자신을 기준으로 비교 값과의 대소관계를 나타낸다.
- Integer 클래스에서 구현된 compareTo()를 보면 삼항연산자와 같은 연산자를 통해 (1, 0, -1)를 반환하도록 구현해 놨다.
public final class Integer extends Number implements Comparable<Integer>, Constable, ConstantDesc {
//..............//
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
//같으면 0, 오른쪽 값이 크면 -1, 작으면 1을 반환
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
//..............//
}
- 아래와 같이 단순한 뺄셈을 해도 (1, 0, -1)을 반환하는 것과 같이 (양수, 0 음수)로 표현되야 하는 조건을 만족한다.
public final class Integer extends Number implements Comparable<Integer>, Constable, ConstantDesc {
//..............//
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
//같으면 0, 오른쪽 값이 크면 -1, 작으면 1을 반환
public static int compare(int x, int y) {
return return v1 - v2;
// return return v2 - v1;
// 내림 차순의 경우 반대로 뺄셈하면 된다.
}
//..............//
}
비교를 위해 편한 방법인 단순한 뺄셈을 사용한다면 마찬가지로 음수, 0 양수로 구분된 값을 구할 수 있다. 그렇다면 어째서 Integer와 같은 래퍼클래스에서 비교적 복잡한 방법인 연산자를 통한 방법을 사용 하였을까?
위의 예시를 보면 int형 변수의 뺄셈인 것을 알 수 있는데 int는 4Byte(32bit)자료형으로 뺄셈을 한다면 각 자리수를 전부 읽어 서 하나하나 뺄셈을 해야한다. 반면 비교 연산자를 사용한다면 앞자리의 크기가 다르다면 빠르고 비교를 끝낼 수 있는데 여기서 조금의 속도 차이가 생기게 된다. 그리고 만약 연산의 결과가 int형 표현범위의 최댓값/최소값을 넘어버리게 된다면 오버플로우(Overflow)가 발생하게 된다. 비교 연산을 사용하는 것이 뺄셈을 하는 것보다 빠르기도 하지만 위와 같은 예외가 발생할 우려가 있기 때문에 기본형 데이터에 대한 예외를 확인하기 어렵다면 대소비교 연산자와 같은 연산자를 사용하는 것이 권장되는 방식이다.
Comparator
- 객체의 기본 정렬기준 외에 다른 기준으로 정렬하고자 할 때 사용
- Comparator은 compare, equals 두개의 추상 메서드를 가지고 있다.
- compare()은 두개의 파라미터를 가지는데 파라미터로 들어오는 두 객체를 자기 자신의 상태와 상관 없이 비교한다.
public interface Comparator<T> {
int compare(T o1, T o2); // o1, o2 두 객체를 비교
// 반환형이 int형 인 것은 반환값이
// 양수일 경우 o1이 큰 것이고
// 0일 경우는 o1과 o2가 같고
// 음수일 경우는 o2가 큰것을 의미한다.
boolean equals(Object obj); // equals를 오버라이딩하라는 뜻
........
........
}
compare()와 compareTo()는 두 객체의 비교 결과를 반환하도록 작성한다. 하지만 compare()은 compareTo()와 다르게 자신이 아닌 두 객체를 비교하는 것으로 기본 정렬기준 외에 다른 기준으로 정렬하고자 할 때는 compare() 메서드를 사용해야 하는데 Comparator 인터페이스를 구현한 클래스의 인스턴스를 생성하여 compare()메소드를 사용한다면 파라미터로 들어오는 두 객체를 자기 자신(구현클래스)와 상관 없이 비교할 수 있다.
- compareTo()를 사용할 때와 마찬가지로 뺄셈을 통해 간략화 할 수 있지만 속도와 예외가 발생할 수있는 것도 동일하게 나타난다.
import java.util.Comparator;
public class Main {
public static void main(String[] args) {
Ob o1 = new Ob(1);
Ob o2 = new Ob(2);
if (o1.compare(o1, o2) > 0) {
System.out.println("o1의 num이 더 크다.");
}else if(o1.compare(o1, o2) < 0) {
System.out.println("o2의 num이 더 크다.");
}else {
System.out.println("o1와 o2의 num 값은 동일하다.");
}
}
}
class Ob implements Comparator<Ob>{
int num;
Ob(int num){
this.num = num;
}
public int compare(Ob o1, Ob o2) {
return o1.num - o2.num;
}
}
객체의 비교를 위해 여러 객체를 생성한다면 compare()를 호출하는데 어떤 객체의 메서드를 호출해야 하는지 일관성이 떨어지게 된다. 이를 보완하기 위해 비교만을 위한 객체를 생성할 수도 있지만 구현 클래스의 객체를 생성한다면 필요없는 부분까지 생성이되어 공간을 차지해버리게 된다. 이때는 익명 클래스를 활용해야 한다.
익명 클래스는 이름이 없는 일회용 클래스로 클래스의 선언과 생성을 동시에하므로, 단 하나의 객체만을 생성하는 일회용 클래스이다. 이름이 없기 때문에 조상 클래스또는 구현인터페이스 이름을 사용한다. (상속받는다)
new 조상클래스이름() {
// 멤버 선언
}
new 구현인터페이스이름() {
// 멤버 선언
}
- 익명 클래스를 활용한다면 Comparator 의 compare() 메서드 만을 구현하여 사용할 수 있게 된다.
- Comparator를 익명 클래스로 구현함으로 비교할 객체의 클래스 내부에 Comparator를 구현하지 않아도 된다.
Comparator와 Comparable의 정렬
정렬의 기준이 되기 위해 compare()와 compareTo()메서드는 두 객체를 정의된 기준으로 비교한뒤 (양수, 0, 음수)중 하나를 반환한다.
자바에서는 정렬을 하기 위한 기준으로 compare()와 compareTo()메서드의 반환값을 사용하는데 자바에서의 정렬은 특별하게 정의가 되어있지 않는이상 '오름차순'을 기준으로 한다. Ex: Arrays.sort(), Collections.sort()
배열을 정렬하고자 한다면 정렬은 두 대상을 비교하여 자리 바꿈을 반복하는 것이기 때문에 비교한뒤 비교 결과에 따라 자리를 바꾸는 것을 반복한다.
int []arr = {1, 3, 2};
public int compareTo(Integer anotherInteger) {
return this.value - anotherInteger.value;
}
- 예를 들어 arr[]를 compareTo()메서드를 기준으로 정렬하고자 한다.
(1) arr[0]와 arr[1]을 비교한다.
- 1이 3보다 작다. 1 - 3 = -2로 음수이다 : 위치를 바꾸지 않는다.
(2) arr[1]와 arr[2]를 비교한다.
- 3이 2보다 크다. 3 -2 = 1 로 양수이다. : 위치를 바꾼다.
위 예시를 통해 compare()와 compareTo()메서드의 반환 값에 따른 규칙을 일반화 할 수 있다.
음수 : 교환 X
양수 : 교환 O
private static void bubble_sort(int[] intArr) {
for(int i = 0; i < intArr.length; i++) {
for(int j = 0; j < intArr.length-1-i; j++) {
int tmp = 0;
if(intArr[j] > intArr[j + 1]) { // 정렬 기준
tmp = intArr[j]
intArr[j] = intArr[j+1];
intArr[j+1] = tmp;
}
}
}
}
많은 정렬 방법들이 있지만 결국 비교하여 자리 바꿈을 하는 것은 같지만 정렬 기준은 가변적이다. 위의 버블 정렬의 경우도 마찬가지로 기준을 통해 비교하여 교환을 할지 말지를 정하게 되며 가변적이지만 자리바꿈을 하는 방법은 바뀌지 않는다. 기본 자료형인 경우는 간단하게 연산자를 통해 비교가 가능하지만 객체 배열을 사용시 객체를 정렬하고자 한다면 비교를 위해 compare()와 compareTo()를 사용해야 한다.
- Comparator 인터페이스의 익명 객체를 통한 정렬
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int N = scan.nextInt();
int[][] arr = new int[N][2];
for(int i = 0; i < N ; i++) {
arr[i][0] = scan.nextInt();
arr[i][1] = scan.nextInt();
}
Arrays.sort(arr, new Comparator<int[]>() {
@Override
// 1 1
// 2 2
public int compare(int[] e1, int[] e2) {
if (e1[0] == e2[0]) {
return e1[1] - e2[1];
} else {
return e1[0] - e2[0];
}
}
});
int [] a = {1,2,3,4};
Arrays.sort(a);
for(int i = 0; i < N; i++) {
System.out.println(arr[i][0] + " "+arr[i][1]);
}
}
}
'Java' 카테고리의 다른 글
[JAVA] Map 인터페이스 (0) | 2023.11.11 |
---|---|
[JAVA] Set 인터페이스 (0) | 2023.11.10 |
[JAVA] 컬렉션 프레임웍(collections framework)와 핵심 인터페이스 (0) | 2023.11.08 |
[JAVA] List 인터페이스 (0) | 2023.11.07 |
[JAVA] 형식화 클래스 (0) | 2023.11.04 |