- Java 정렬 방법
Java 에서의 정렬은 java.util.Collections클래스의 static 메소드인 sort()를 이용한다.
먼저, API문서를 살펴보면 오버로딩 된 두개의 sort() 메소드가 있음을 확인할 수 있다. 차례대로 알아보자.
- Comparable<T> 인터페이스 이용
sort() 메소드의 매개변수 타입을 보면 List<T>로 제네릭 타입을 받고 있다. 이때 T는 <T extends Comparable>로 보아 Comparable 인터페이스를 구현한 타입이어야 한다는 것을 알 수 있습니다.
( 근데 이때, Comparable은 인터페이스인데 extends가 아니라 implements여야 하는 것이 아닌가 의문이 들기도한다. 이는 사실 자바 엔지니어들이 제네릭을 설계할 때, 매개변수화된 타입에 제약조건을 줄 수 있는 방법 중 일반 클래스의 하위 클래스로 제한하는 것 뿐만 아니라, 특정 인터페이스를 구현하는 클래스로 타입을 제한할 수 있는 방법도 필요했다. 즉 extends와 implements를 모두 지칭하기 위한 키워드가 필요했던 것이다. 근데 키워드를 함부로 만들어서 쓴다면 이미 구현되어있는 코드 중에서 만약 해당 키워드로 작성된 변수나 메소드가 있다면 코드가 제대로 동작하지 않게 된다( 키워드나 예약어는 변수명으로 쓸 수 없기 때문) 따라서 결국 둘 중에 하나를 선택하기로 했고 extends로 사용하자는 결론이 났다고 한다. 즉, <T extends Comparable> 에서 extends 오른쪽에 있는 것이 클래스인지 인터페이스인지는 중요하지 않고 무조건 extends를 쓰는 것이다. 위 코드는 'Comparable인터페이스를 구현하는 타입 T' 라고 이해하면 되고, 만약 사용자가 정의한 Animal이라는 클래스가 있다고 할 때 <T extends Animal>이라고 한다면, 'Animal 클래스를 상속하는 타입 T' 이라고 이해하면 된다.)
Comparable 인터페이스를 살펴보면
comparTo() 메소드 하나 밖에 없으므로 이 메소드만 오버라이딩 해주면 Collections.sort() 의 매개변수로 List를 넘겨서 정렬을 할 수 있다.
1. 자 먼저, String 타입을 원소로 갖는 List를 정렬해보자.
이처럼 String 타입의 원소를 가진 List가 Collections.sort()의 매개변수로 넘겨져 정렬이 가능한 이유는 API문서를 확인해보면 String 클래스는 Comparable 인터페이스를 사전편찬 순으로 정렬되도록 구현하고 있기 때문이다.
2. 그럼 만약 아래와 같이 사용자가 정의한 클래스 Person이 있다고하고 이를 원소로하는
List를 정렬시켜보자.
- Person 클래스
- Main 클래스
Main 클래스에서 sort()부분에서 에러난다. 왜냐하면 Person 클래스는 Comparable 인터페이스를 구현하지 않았기 때문에 Collections.sort(List<T> list) 의 매개변수로 넘길수 없기 때문이다. 게다가 Person의 어떤 멤버변수를 기준으로 정렬을 해야할지도 모르기 때문에 이를 개발자가 직접 정해주어야한다. 따라서 Person타입의 객체를 원소로 가진 List를 정렬하고자 한다면 Comparable인터페이스를 구현해야한다. 즉, compareTo(T obj) 메소드를 오버라이딩 해야한다. 이때 다음의 내용을 근거로 정의해야한다.
- 인자로 전달된 obj가 작다면 양의 정수 반환
- 인자로 전달된 obj가 크다면 음의 정수 반환
- 인자로 전달된 obj와 같다면 0을 반환
그럼 Person의 age를 기준으로 정렬한다고 기준을 잡고, compareTo()메소드를 오버라이딩 해보자.
그러면 age순으로 정렬된 결과를 확인할 수 있다.
이처럼 만약 Person 클래스의 멤버필드로 주소나, 전화번호등이 추가적으로 있을 때 주소의 사전편찬 순으로 정렬하고 싶다거나 전화번호의 숫자 순서대로 정렬하고 싶다면 이에 맞게 Comparable<T> 인터페이스를 구현해주면된다. 즉, cmpareTo(T obj) 메소드를 오버라이딩 해주면된다.
정리를 해보면 인자가 한 개인 sort(List o) 메소드를 호출하면 List에 있는 원소의 compareTo() 메소드에 의해 순서가 결정된다. 따라서 List에 들어있는 원소가 반드시 Comparable 인터페이스를 구현한 클래스 유형이어야만 한다.
- Comparator<T> 인터페이스 이용
만약 알파벳의 사전편찬 순이라던가 숫자 오름차순 같이 natural order 대로의 정렬 말고 사용자가 원하는 임의의 정렬 기준(가령 이름의 문자열 길이 순서)대로 정렬하고 싶으면 어떻게 해야할까? 이럴땐 정렬이 되는 기준을 개발자가 직접 정의해주어야 한다. 이때 기준이 되는 것이 Comparator 인터페이스를 구현하는 것이다. 즉, compare() 메소드를 오버라이딩 해주는 것이다.
Comparator 인터페이스는 compare() 메소드와 equals() 메소드 두 가지를 갖고 있지만 사실 이를 구현 하는 모든 클래스는 Object클래스를 상속한다. 그런데 Object클래스에서 equals() 메소드를 구현하고 있기 때문에, Comparator 인터페이스를 구현할 때 equals 메소드는 저절로 구현한게 되버리기 때문에 compare()메소드만 오버라이딩 해주면된다.
위 코드에서 Comparator 인터페이스를 구현하는 nameLengthCompare라는 클래스는 Main()메소드 안에서만 사용될 것이기 때문에 내부클래스로 구현하였고 그 안의 compare() 메소드를 구현할때의 정렬 기준은 위에서 설명한 compareTo()메소드와 비슷하다.
즉, compare( T o1, T o2) 일때 ,
o1 > o2 이면 1
o1 < o2 이면 -1
o1 = o2 이면 0
을 반환하도록 구현해주면 된다.
그리고 Collections.sort() 메소드의 매개변수로 정렬하고자 하는 List 변수명과, Comparator 인터페이스를 구현한 클래스의 인스턴스를 넘겨주면 정렬을 할 수 있다.
결과를 보면 name의 길이 순서대로 정렬이 된 것을 확인할 수 있다.
sort(List o, Comparator c)를 호출했을 때는 List에 있는 원소의 compareTo() 메소드가 호출되지 않고 Comparator()의 compare() 메소드가 호출된다. 즉 List에 들어있는 원소들이 Comparable 인터페이스를 구현하지 않은 클래스 타입이라도 상관없다.