실무에서는 데이터 정렬 시 주로 쿼리에서 진행하지만,
최근 정렬 알고리즘 문제를 풀던 중 Comparable, Comparator을 사용하여 정렬을 하게 되어 비교 인터페이스에 대해 정리해보자 한다.
비교 인터페이스를 사용하는 이유
일반적인 숫자의 경우 기본적인 비교 연산자를 통하여 대소비교를 진행할 수 있다.
하지만 객체의 경우를 보자,
만일 String name, int age 두 개의 값을 가지고 있는 Person 객체를 비교한다해보자.
Person 객체는 나이의 크기 통하여 대소비교를 진행할 지 이름값의 알파벳 순으로 대소비교를 진행할 지를 알 수 없으니 일반적인 방식으로는 대소비교가 불가능하다.
이 때 우리는 해당 객체에 비교 인터페이스를 상속시켜 해당 객체를 어떤값을 통하여 대소 비교를 진행할 지 정의해줄 수 있다.
우선 자바에서 비교를 위한 인터페이스에는 Comparable과 Comparator가 있다.
Comparable - compareTo(T o)
https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html#method.summary
Comparable의 공식 문서를 확인해보면 compareTo(T o)라는 메소드가 선언되어있다.
compareTo 메소드의 경우 자기 자신과 파라미터로 전달받은 객체를 비교하는 것이다.
compareTo 메소드를 사용하기 위해서는 Comparable<T> 인터페이스를 상속받아야 한다.
해당 메소드의 return 값은 객체의 특정값을 기준으로 오름 차순으로 정렬할 경우
return this.값 - o(파라미터).값 으로 작성하며
파라미터로 전달받은 객체의 값이 더 작을 경우 양수를 return
파라미터로 전달받은 객체의 값이 더 클 경우 음수를 return한다.
자바의 sort 메소드(Collections.Sort, Arrays.sort)는 해당 메소드의 내용을 참조하여 정렬한다.
예시
아래 예시는 백준10814번 문제를 풀면서 사용한 예시이다.
https://www.acmicpc.net/problem/10814
해당 문제에서는 나이순으로 정렬 중 나이가 동일할 경우 가입 순서를 기준으로 정렬하는 문제이다.
public static class User implements Comparable<User> {
int age;
String name;
// 가입 순서
int idx;
public User(int age, String name, int idx) {
this.age = age;
this.name = name;
this.idx = idx;
}
@Override
public int compareTo(User user) {
if (this.age == user.age) {
// 나이가 같을 경우 가입 순으로 정렬
return this.idx - user.idx;
} else {
return this.age - user.age;
}
}
}
위와 같이 객체에 compareTo 메소드를 구현한 후
객체들을 담아놓은 배열을 Arrays.sort()를 통하여 정렬할 경우 아래와 같은 결과가 나온다.
입력값 | 출력값 |
3 21 Junkyu 21 Dohyun 20 Sunyoung |
20 Sunyoung 21 Junkyu 21 Dohyun |
Comparator - compare(T o1, T o2)
https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html
Comparator는 해당 인터페이스의 compare 메소드를 통하여 객체 비교를 진행할 수 있다.
compare 메소드는 compareTo 메소드와 다르게 파라미터로 객체 두 개를 받는다.
전달받은 두 객체의 값을 비교하여
첫번째 파라미터로 전달받은 값이 더 작을 경우 음수를 return
첫번째 파라미터로 전달받은 값이 더 클 경우 양수를 return한다.
객체의 클래스에 Comparator를 상속받아 compare메소드를 구현하더라도 sort 메소드 사용 시 해당 메소드를 참조하여 정렬을 진행하지 못한다.
그럼 정렬 시 compare 메소드는 어떻게 사용할까??
답은 sort메소드에 두번째 파라미터로 compare를 구현한 클래스를 전달해주는 것이다.
아래 예시를 통하여 더 자세히 알아보자
예시
Comparable에서 예시로 사용했던 동일한 문제에서 Comparator를 사용해보자.
User 클래스는 Comparator를 상속받아 아래와 같아졌다.
추가로 아래 Arrays.sort 메소드 호출 시 두번째 파라미터로 편리하게 전달하기 위해 빈 생성자를 추가해주었다.
public static class User implements Comparator<User> {
int age;
String name;
// 가입 순서
int idx;
public User(int age, String name, int idx) {
this.age = age;
this.name = name;
this.idx = idx;
}
// Arrays.sort 시 파라미터로 전달하기 위한 빈 생성자 추가
public User(){
}
@Override
public int compare(User user1, User user2) {
if (user1.age == user2.age) {
// 나이가 같을 경우 가입 순으로 정렬
return user1.idx - user2.idx;
} else {
return user1.age - user2.age;
}
}
}
또한 정렬 시에 Arrays.sort 메소드도 아래와 같이 변경된다.
Arrays.sort(userArr, new User());
결과값을 확인해보면 Comparable을 사용했을 때와 동일한 결과가 나온다.
익명 클래스, 람다 표현식을 활용한 Comparator 정렬
Comparator를 사용하여 정렬하는 경우 비교 대상 객체의 compare메소드를 사용하는 것이 아닌 두번째 파라미터로 전달받은 객체의 compare메소드를 통하여 정렬을 진행한다.
따라서 해당 객체에 compare메소드를 무조건 구현할 필요 없이 두번째 파라미터로 compare메소드를 구현한 익명 클래스를 전달하면 더 편리하게 정렬할 수 있다.
익명 클래스를 사용하여 Arrays.sort 메소드를 사용하면 아래와 같다.
Arrays.sort(userArr, new Comparator<User>() {
@Override
public int compare(User user1, User user2) {
if (user1.age == user2.age) {
// 나이가 같을 경우 가입 순으로 정렬
return user1.idx - user2.idx;
} else {
return user1.age - user2.age;
}
}
});
하지만 여기서 람다 표현식을 사용한다면 더 간단하게 구현할 수 있다.
Arrays.sort(userArr, (user1, user2) -> {
if (user1.age == user2.age) {
// 나이가 같을 경우 가입 순으로 정렬
return user1.idx - user2.idx;
} else {
return user1.age - user2.age;
}
});
'BackEnd > Java' 카테고리의 다른 글
DTO 그렇게 쓰는 거 아닌데 ㅋㅋ (0) | 2024.03.17 |
---|---|
Java HashMap이 List보다 contains의 성능이 더 좋은 이유 (2) | 2023.11.26 |
JAVA에 Call by refernce는 없다. (0) | 2023.07.03 |
JAVA equals, hashCode 메소드 (0) | 2023.05.18 |
JAVA finalize() 소멸자 메소드에 대해 (0) | 2023.05.14 |