본문 바로가기

Java

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

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

 

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

 

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

 

1
2
3
4
5
6
7
8
9
10
11
12
 
public class Example {
 
    public static void main(String[] args) {
        int[] intArray = {52143};
        
        Arrays.sort(intArray);
        // 1, 2, 3, 4, 5로 정렬 된다.
    }
}

 

동적배열인 List는

 

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.*;
 
public class Example {
 
    public static void main(String[] args) {
        List<Integer> intList = Arrays.asList(52413);
    
        // 1, 2, 3, 4, 5로 정렬된다.
    }
}
 
 

 

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

Collections.sort()는 파라미터로 List<T> 인 List를 받는다.

 

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

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

 

1
2
3
4
interface Comparable<T> {
    int compareTo(T obj)
}
 

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

sort() 메서드를 통해 비교가 가능한 것이다.

 

그렇다면 임의로 만든 클래스의 객체 배열은 어떻게 정렬할까

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
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() 메서드로 정렬을 할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 
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 인터페이스를 구현하면 된다.

 

1
2
3
4
interface Comparator<T> {
    int compare(T obj1, T obj2);
}
 
 

 

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

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

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

 

compareTo 메서드는 파라미터 한개로 객체를 받아서

this 키워드를 사용해서 비교했지만

 

compare 메서드는 파라미터 두개로 두개의 객체를 받아서

서로 비교해주면 된다.

 

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

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

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
 
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;
    }
}
 
 

 

Person 객체가 동일한 경우에만 compare 메소드가 0을 리턴해야 하는 것이 바람직하기 때문에,

PersonAgeComparator 클래스에서는 나이 기준 Comparator이지만 name까지 비교를 하고

PersonNameComparator 클래스에서는 이름 기준 Comparator이지만 age까지 비교를 한다.

 

정렬을 한 번하고 Comparator 클래스들을 재 사용할 것이 아니라면

굳이 class를 만들 필요 없이 무명 클래스와 람다 익스프레션을 활용하면 된다.

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

 

1
2
3
4
5
6
7
8
9
public class Person {
    int age;
    String name;
 
    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }
}
 

무명 클래스를 활용한 예시

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 
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, 홍길동}으로 정렬된다.
    }
}
 

 

람다 익스프레션을 활용하면 코드를 더 줄일  수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 
public class Example {
 
    public static void main(String[] args) {
        Person[] people = {new Person(30"홍길동"), new Person(20"김철수"), new Person(25"김영희")};
        
                int r = p1.age - p2.age;
                if (r == 0) {
                    r = p1.name.compareTo(p2.name);
                }
                return r;
            }); 
        // 나이 기준 정렬
        // {20, 김철수}, {25, 김영희}, {30, 홍길동}으로 정렬된다.
        
            int r = p1.name.compareTo(p2.name);
            if (r == 0) {
                r = p1.age - p2.age;
            }
            return r;
        }); 
        // 이름 기준 정렬
        // {25, 김영희}, {20, 김철수}, {30, 홍길동}으로 정렬된다.
    }
}
 

 

Stream API를 활용하는 방법도 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 
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, 홍길동}으로 정렬된다.
    }
}