BackEnd/Java

JAVA 객체 배열, 리스트 값 비교(Comparable, Comparator)를 통한 정렬

Potwings 2023. 9. 3. 15:37

실무에서는 데이터 정렬 시 주로 쿼리에서 진행하지만,

최근 정렬 알고리즘 문제를 풀던 중 Comparable, Comparator을 사용하여 정렬을 하게 되어 비교 인터페이스에 대해 정리해보자 한다.

 

비교 인터페이스를 사용하는 이유

일반적인 숫자의 경우 기본적인 비교 연산자를 통하여 대소비교를 진행할 수 있다.

 

하지만 객체의 경우를 보자,

만일 String name, int age 두 개의 값을 가지고 있는 Person 객체를 비교한다해보자.

Person 객체는 나이의 크기 통하여 대소비교를 진행할 지 이름값의 알파벳 순으로 대소비교를 진행할 지를 알 수 없으니 일반적인 방식으로는 대소비교가 불가능하다.

 

이 때 우리는 해당 객체에 비교 인터페이스를 상속시켜 해당 객체를 어떤값을 통하여 대소 비교를 진행할 지 정의해줄 수 있다.

 

우선 자바에서 비교를 위한 인터페이스에는 ComparableComparator가 있다.

 

Comparable - compareTo(T o)

https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html#method.summary

 

Comparable (Java Platform SE 8 )

This interface imposes a total ordering on the objects of each class that implements it. This ordering is referred to as the class's natural ordering, and the class's compareTo method is referred to as its natural comparison method. Lists (and arrays) of o

docs.oracle.com

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

 

10814번: 나이순 정렬

온라인 저지에 가입한 사람들의 나이와 이름이 가입한 순서대로 주어진다. 이때, 회원들을 나이가 증가하는 순으로, 나이가 같으면 먼저 가입한 사람이 앞에 오는 순서로 정렬하는 프로그램을

www.acmicpc.net

해당 문제에서는 나이순으로 정렬 중 나이가 동일할 경우 가입 순서를 기준으로 정렬하는 문제이다.

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 (Java Platform SE 8 )

Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second. In the foregoing description, the notation sgn(expression) designates the mathematical s

docs.oracle.com

Comparator는 해당 인터페이스의 compare 메소드를 통하여 객체 비교를 진행할 수 있다.

compare 메소드는 compareTo 메소드와 다르게 파라미터로 객체 두 개를 받는다.

 

전달받은 두 객체의 값을 비교하여

첫번째 파라미터로 전달받은 값이 더 작을 경우 음수를 return

첫번째 파라미터로 전달받은 값이 더 클 경우 양수를 return한다.

 

객체의 클래스에 Comparator를 상속받아 compare메소드를 구현하더라도 sort 메소드 사용 시 해당 메소드를 참조하여 정렬을 진행하지 못한다.

compare 메소드만 구현해두었을 경우 정렬 시 예외가 발생한다.

 

그럼 정렬 시 compare 메소드는 어떻게 사용할까??

 

답은 sort메소드에 두번째 파라미터로 compare를 구현한 클래스를 전달해주는 것이다.

sort 메소드에 Comparator을 두번째 파라미터로 전달할 수 있다.

아래 예시를 통하여 더 자세히 알아보자

 

예시

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;
    }
});