본문으로 바로가기

CompareTo / Comparator 사용하기

category Java 2020. 3. 13. 21:56

Comparable, Comparator, 배열을 정렬하는 여러가지 방법

JAVA에서 배열을 정렬을 하는 방법은 여러가지가 있다.

먼저 기본 자료형인 int, double, String 등의 배열은

Arrays.sort() 메서드를 호출하면 된다.

import java.util.Arrays;

public class Example {

    public static void main(String[] args) {
        int[] intArray = {5, 2, 1, 4, 3};

        Arrays.sort(intArray);
        // 1, 2, 3, 4, 5로 정렬 된다.
    }
}

동적배열인 List는 Collections.sort() 메서드를 호출하면 된다.

import java.util.*;

public class Example {

    public static void main(String[] args) {
        List<Integer> intList = Arrays.asList(5, 2, 4, 1, 3);

        Collections.sort(intList);
        // 1, 2, 3, 4, 5로 정렬된다.
    }
}

Arrays.sort()는 파라미터로 T [] 인 기본 배열을 받고 Collections.sort()는 파라미터로 List 인 List를 받는다.

그렇다면 sort() 메서드에서 기본 자료형들은 어떻게 비교해서 정렬을 할 수 있을까 ?

바로 Comparable 인터페이스를 구현했기 때문이다.

interface Comparable<T> {
    int compareTo(T obj)
}

즉, Comparable 인터페이스의 구현해서 compareTo 메서드를 오버라이딩한 클래스는

sort() 메서드를 통해 비교가 가능한 것이다. 그렇다면 임의로 만든 클래스의 객체 배열은 어떻게 정렬할까?

import java.util.Arrays;

public class Example {

    public static void main(String[] args) {
        Person[] people = {new Person(30, "홍길동"), new Person(20, "김철수"), new Person(25, "김영희")};
        Arrays.sort(people);
    // 정렬 실패.
}
}
class Person {
    int age;
    String name;

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }
}

위의 같은 코드를 실행한다면

Exception in thread "main" java.lang.ClassCastException: Person cannot be cast to [java.lang.Comparable] 라는 오류가 발생한다.

 

Person 클래스는 java lang.Comparable에 캐스팅 될 수 없다는 오류이다.

한마디로 Person 클래스는 Comparable 인터페이스를 구현하지 않았기 때문에

에러가 난 것이고 Arrays.sort() 메서드로 정렬을 할 수 없는 것이다.

Person 클래스에 Comparable 인터페이스를 구현하고 compareTo메서드를 오버라이딩하면

Arrays.sort() 메서드로 정렬을 할 수 있다.

import java.util.Arrays;

public class Example {

    public static void main(String[] args) {
        Person[] people = {new Person(30, "홍길동"), new Person(20, "김철수"), new Person(25, "김영희")};
        Arrays.sort(people);
        // 나이 순 정렬 성공 {20, 김철수}, {25, 김영희}, {30, 홍길동}으로 정렬된다.
    }
}
class Person implements Comparable<Person>{
    int age;
    String name;

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    /*
     * 나이를 기준으로 정렬하고 나이가 같다면 이름 순으로 정렬한다.
     */
    @Override
    public int compareTo(Person other) {
        int r = this.age - other.age;
        if (r == 0) {
            r = this.name.compareTo(other.name);
        }
        return r;
    }
}

하지만 이렇게 Comparable 인터페이스를 구현한 클래스라면 compareTo 메서드를 여러개 재정의 할 수 없으니 비교기준을 한가지 밖에 가질 수 없다.

위의 코드처럼 나이를 기준으로 먼저 비교해서 정렬하고

나이가 같다면 이름 순으로 정렬하는 compareTo 메서드를 재정의했다면

이름 순으로 먼저 비교하고 싶을 때는 난감한 경우가 생긴다.

그럴 땐 Comparator 인터페이스를 구현하면 된다.

interface Comparator<T> {
    int compare(T obj1, T obj2);
}

Comparator 인터페이스는 Comparable 인터페이스와 다르게

compare 메서드를 오버라이딩 해야한다.

compare 메서드는 compareTo 메서드와 다르게 파라미터 변수가 두 개이다.

compareTo 메서드는 파라미터 한개로 객체를 받아서this 키워드를 사용해서 비교했지만

compare 메서드는 파라미터 두개로 두개의 객체를 받아서서로 비교해주면 된다.

Comparator 인터페이스는 비교 기준이 여러 개 일때 사용하는 인터페이스기에

비교할 클래스에 구현하는 것이 아니라

따로 클래스를 구현해서 만들어 주는 것이 좋다.

import java.util.Arrays;
import java.util.Comparator;

public class Example {

    public static void main(String[] args) {
        Person[] people = {new Person(30, "홍길동"), new Person(20, "김철수"), new Person(25, "김영희")};

        Arrays.sort(people, new PersonAgeComparator()); // 나이 기준 정렬
        // {20, 김철수}, {25, 김영희}, {30, 홍길동}으로 정렬된다.

        Arrays.sort(people, new PersonNameComparator()); // 이름 기준 정렬
        // {25, 김영희}, {20, 김철수}, {30, 홍길동}으로 정렬된다.
    }
}
class Person {
    int age;
    String name;

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }
}

class PersonAgeComparator implements Comparator<Person> {

    @Override
    public int compare(Person p1, Person p2) {
        int r = p1.age - p2.age;
        if (r == 0) {
            r = p1.name.compareTo(p2.name);
        }
        return r;
    }
}
class PersonNameComparator implements Comparator<Person> {

    @Override
    public int compare(Person p1, Person p2) {
        int r = p1.name.compareTo(p2.name);
        if (r == 0) {
            r = p1.age - p2.age;
        }
        return r;
    }
}

정렬을 한 번하고 Comparator 클래스들을 재 사용할 것이 아니라면 굳이 class를 만들 필요 없이 무명 클래스와 람다 익스프레션을 활용하면 된다.

이 방법들이 코드 양이 훨씬 줄어든다.

  • 무명클래스

    import java.util.Arrays;
    import java.util.Comparator;
    
    public class Example {
    
        public static void main(String[] args) {
            Person[] people = {new Person(30, "홍길동"), new Person(20, "김철수"), new Person(25, "김영희")};
    
            Arrays.sort(people, new Comparator<Person>() {
                @Override
                public int compare(Person p1, Person p2) {
                    int r = p1.age - p2.age;
                    if (r == 0) {
                        r = p1.name.compareTo(p2.name);
                    }
                    return r;
                }
            }); // 나이 기준 정렬
            // {20, 김철수}, {25, 김영희}, {30, 홍길동}으로 정렬된다.
    
            Arrays.sort(people, new Comparator<Person>() {
                @Override
                public int compare(Person p1, Person p2) {
                    int r = p1.name.compareTo(p2.name);
                    if (r == 0) {
                        r = p1.age - p2.age;
                    }
                    return r;
                }
            }); // 이름 기준 정렬
            // {25, 김영희}, {20, 김철수}, {30, 홍길동}으로 정렬된다.
        }
    }
  • 람다 익스프레션

    import java.util.Arrays;
    import java.util.Comparator;
    
    public class Example {
    
        public static void main(String[] args) {
            Person[] people = {new Person(30, "홍길동"), new Person(20, "김철수"), new Person(25, "김영희")};
    
            Arrays.sort(people, (p1, p2) -> {
                    int r = p1.age - p2.age;
                    if (r == 0) {
                        r = p1.name.compareTo(p2.name);
                    }
                    return r;
                }); 
            // 나이 기준 정렬
            // {20, 김철수}, {25, 김영희}, {30, 홍길동}으로 정렬된다.
    
            Arrays.sort(people, (p1, p2) -> {
                int r = p1.name.compareTo(p2.name);
                if (r == 0) {
                    r = p1.age - p2.age;
                }
                return r;
            }); 
            // 이름 기준 정렬
            // {25, 김영희}, {20, 김철수}, {30, 홍길동}으로 정렬된다.
        }
    }
  • Stream API

    import java.util.Arrays;
    
    public class Example {
    
        public static void main(String[] args) {
            Person[] people = {new Person(30, "홍길동"), new Person(20, "김철수"), new Person(25, "김영희")};
    
            people = Arrays.stream(people)
                    .sorted((p1, p2) -> {
                        int r = p1.age - p2.age;
                        if (r == 0) {
                            r = p1.name.compareTo(p2.name);
                        }
                        return r;
                    })
                    .toArray(Person[]::new);
            // 나이 기준 정렬
            // {20, 김철수}, {25, 김영희}, {30, 홍길동}으로 정렬된다.
    
            people = Arrays.stream(people)
                    .sorted((p1, p2) -> {
                        int r = p1.name.compareTo(p2.name);
                        if (r == 0) {
                            r = p1.age - p2.age;
                        }
                        return r;
                    })
                    .toArray(Person[]::new);
            // 이름 기준 정렬
            // {25, 김영희}, {20, 김철수}, {30, 홍길동}으로 정렬된다.
        }
    }

'Java' 카테고리의 다른 글

[Java 8] 메서드 레퍼런스(Methods Reference)  (0) 2020.09.15
[Java] 유용한 Math 메소드  (0) 2020.07.05
Map의 유용한 인터페이스  (0) 2020.06.19
자바 잡학사전  (0) 2020.02.12
deque [java]  (0) 2019.09.19