Java의 정렬 클래스 Comparable, Comparator 정리

들어가며

 

컬렉션의 아이템들을 정렬할 때 쓰이는 클래스, 인터페이스들에 대해 깊게 알지 못했던게 최근 코딩테스트 응시 중 밑천이 드러나버렸다. 이를 반성하는 의미로 정리하고자 포스팅하려한다.

 

 

Comparable

 

Comparable 인터페이스는 컬랙션의 정렬 조건을

정렬 하고자 하는 객체 내부에 정의 할 수 있게 해준다.

 

public static class Person implements Comparable<Person> {

    public String name;

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

    //직관적으로 하기위해 getter, setter 생략

    @Override
    public int compareTo(Person o) {
        return this.name.compareTo(o.name); //name으로 오름 차순 정렬
    }
}

 

위와 같이 객체 내부에 compareTo() 메서드를 오버라이딩하여 정렬 조건을 정의 해두고

아래와 같이 작성하면 정의한 정렬 조건에 의해 정렬이 수행된다.

 

Comparable 인터페이스의 추상메서드인 compareTo() 메서드는

메서드를 호출한 객체보다 인자로 넘어온 객체가 더 크면 음수를

같다면 0을, 작다면 양수를 반환한다. 이 반환 값은 정렬에 사용된다.

 

public void example() {
    List<Person> list = Arrays.asList(new Person(“김철수”), new Person(“이영희”));
    list.sort(null); //Comparable 인터페이스의 compareTo 메서드를 오버라이딩 하지 않으면 예외가 발생한다.
}


※ String, Integer 등의 기본 데이터 타입 클래스는 내부적으로 Comparable의 compareTo를 이미 구현하고 있어 별다른 처리가 필요 없이 정렬을 수행할 수 있다.

 

 

List

 

아래 List 인터페이스의 sort() 메서드 Comparable, Comparator 조합으로 정렬이 수행될  있다.

 

void sort(Comparator<? super E> c)

 

다음은 sort() 메서드의 특징이다.

  • 인자로 Comparator 또는 null 을 넘길 수 있음.
  • 인자로 null을 넘기면, Comparator를 대신할 정렬조건이 필요하므로 컬렉션의 제네릭 타입은 Comparable 인터페이스를 상속하여 compareTo 메서드를 오버라이딩 해야함. 그렇지 않으면 예외 발생.
  • 컬렉션의 제네릭 타입이 Comparable 인터페이스도 상속했고, Comparator도 인자로 넘기게되면 Comparable로 정의된 정렬 조건은 무시됨.

 

Comparator

 

Comparator 함수형 인터페이스로 아래와 같은 메서드를 가진다. 

 

int compare(T o1, T o2) // 반환값(음수, 0, 양수)에 따라 정렬시킬 수 있다.

 

쓰임새에 대한 예제는 다음과 같다.

 

public void example() {
    List<String> list = Arrays.asList(“김철수”, “이영희”);

    list.sort((o1, o2) -> o1.compareTo(o2)); //오름차순 정렬
    list.sort((o1, o2) -> o2.compareTo(o1)); //내림차순 정렬
}

 

간단하게 작성할 있도록 Comparator 다음의 static 메서드를 지원한다.

 

public void example() {
    list.sort(Comparator.naturalOrder()); //오름차순 정렬
    list.sort(Comparator.reverseOrder()); //내림차순 정렬
}

 

 

숫자를 비교한다면 단순히 뺄셈을 해주며 다음과 같이 작성하면 된다.

 

(o1, o2) -> o1 - o2 //오름차순 정렬      
(o1, o2) -> o2 - o1 //내림차순 정렬

OR  

Comparator.naturalOrder() //오름차순 정렬     
Comparator.reverseOrder() //내림차순 정렬

 

 

 

Comparator로 사용자가 생성한 클래스 타입 객체들의 정렬은 어떻게 처리할까?

 

public class Person {
    public String name;
    public Integer point;
	
    public Person(String name, Integer point) {
        this.name = name;
        this.point = point;
	}
    //직관적으로 하기위해 getter, setter 생략
}

public void example() {
    List<Person> list = Arrays.asList(new Person(“김철수”, 10), new Person(“이영희”, 5));
    ...
}

 

위와 같이 값이 존재할 아래와 같이 간단하게 정렬할 있다.

 

list.sort((o1, o2) -> o1.name.compareTo(o2.name)) // name으로 오름차순 정렬

list.sort(Comparator.comparing(o -> o.name))      // name으로 오름차순 정렬

list.sort((o1, o2) -> o2.name.compareTo(o1.name)) // name으로 내림차순 정렬


list.sort((o1, o2) -> o1.point - o2.point);       // point으로 오름차순 정렬

list.sort(Comparator.comparingInt(o -> o.point)); // point으로 오름차순 정렬

list.sort((o1, o2) -> o2.point - o1.point);       // point으로 내림차순 정렬

 

 

Collections

 

컬렉션 클래스들의 유틸클래스인 Collections도 아래와같이 정렬 메서드를 지원한다.

 

public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null);
}

 

 

내부적으로 List sort() 메서드를 단순히 호출하고 끝내기 때문에 나머지 내용들은 앞서 얘기 했던 내용과 동일하기에 적지 않는다.

 

 

Stream

 

stream 중간 연산자 정렬을 담당하는 메서드가 아래와 같이 존재한다.

 

Stream<T> sorted()
  • 메서드를 호출하기위해서는 중간연산을 진행하는 컬렉션의 제네릭 타입은 Comparable 인터페이스의 compareTo 메서드를 오버라이딩하여 정렬 조건을 정의해야한다.
  • 정의하지 않는다면 예외가 발생한다.

 

Stream<T> sorted(Comparator<? super T> comparator)
  • 정렬 조건을 Comparator로 정의한다.
  • Comparable 인터페이스의 compareTo 메서드를 오버라이딩 했어도 이는 무시된다.

 

 

 

 

'☕️ Java' 카테고리의 다른 글

Volatile  (0) 2021.05.18
Thread Local 개념과 내부 구조  (0) 2021.05.08
HashMap은 탐색시 어떻게 O(1)의 성능을 낼까?  (0) 2021.05.02
Java는 Call by reference를 지원할까?  (0) 2021.03.28