Java에서 객체를 정렬하려면 어떻게 해야 할까요? 기본적으로 어떤 것을 비교 기준으로 삼을지가 중요하다고 했습니다. Key 2개를 비교하는 compare 기반 정렬이라면. 이는 Quick sort, merge sort, Tim sort 등이 그러합니다. 반면에 Key 2개를 비교하면서 정렬하지 않는 것도 있는데요. 대표적으로 기수 정렬이라던지, Count sort 등이 있습니다.

 

 

 Merge Sort는 잘 보면 Key 2개를 가지고 비교하면서, 정복하는 알고리즘임을 알 수 있어요. 그러면, 우리는 어떠한 두 원소를 비교했을 때 어떤 것이 우선 순위가 더 높은지 정하는 게 중요하다고 했습니다. 그러한 기준이 있는 것들은 따로 비교 기준을 정의하지 않아도 됩니다. 그런데, 이런 경우를 생각해 봅시다.

 

 

 <1, "dog"> 하고 <2, "abc">하고 비교하려고 합니다. 어떤 게 더 우선 순위가 높을까요? 알 수 있나요? 아니요. 잘 모릅니다. 앞에 있는 수가 더 작아야 priority가 높은 것일까요? 아니면, 뒤에 있는 문자열의 사전순이 더 앞서야 priority가 높은 것일까요? 기준이 없기 때문에, sort를 하지 못합니다. 무엇을 기준으로 해야 할 것인지도 모르고요.

 

 Comparable <T>를 implements를 하면, T라는 객체를 Comparable하게 만들어주는데요. HashMap, HashSet을 할 때도 이에 대해서 언급을 한 적이 잠깐 있었어요. 이들은 버킷 하나에 있는 item 수가 8이상이 되면, TreeMap으로 바꾸는데요.

 

 

 이것은 Key 값을 기준으로 정렬되어 있어요. 기준 위치에 Key가 있다고 했을 때, 키 값이 Key보다 더 작다면 왼쪽에, 기준보다 크다면 우측에 저장이 됩니다. HashMap이나 HashSet에서 키 객체가 Comparable 하지 않은 경우에, identityHashCode를 기준으로 넣습니다. 그런데, 실질적으로 Key값 중복이 일어나지 않는 구조이기 때문에, insert를 할 때 전체를 돌아줘야 한다고 했었어요.

 

 만약에, Comparable 했다면 이야기가 달라졌을 겁니다. 다소, 엉뚱한 이야기인 듯 한 이야기를 했는데요. Collection을 이해하는 데 상당히 중요한 개념 중 하나이기 때문에, 반복해서 계속 설명하였습니다. 중요한 것은 100번, 200번 반복 설명해도 지나치지 않습니다.

 

 


 그러면 객체를 어떻게 비교 가능하게 만들까요?

 

 

 moo 객체가 있습니다. implements가 있는 곳 부터 주석을 친다면 사실 moo를 어떤 기준으로 비교할 것인지에 대한 정보가 아예 없어요. 그런가요? 비교 불가능하다는 겁니다. 이 때 비교 가능하게 만들기 위해서는, 무언가를 implements 해야 하는데요. 그것이 바로 Comparable입니다. 이것은 반드시 아래 메서드를 구현해야 합니다. compareTo를요.

 

 여기서 우선 순위라는 것을 정의할 겁니다. sort가 무엇인가요? 데이터들을 우선 순위가 높은 순에서 낮은 순으로 다시 재배열하는 것입니다. 정의가 깔끔하죠. 만약에 오름차순으로 한다. 그러면 작은 게 priority가 높은 것입니다. 내림차순으로 하면 그 반대일 겁니다. 한 마디로 정리하겠습니다.

 

 기준 객체 this가, 인자로 들어오는 객체의 우선 순위보다 높다면 음수를, 같다면 0을, 낮다면 양수를 리턴합니다. 예를 들어서, 위에 있는 compareTo 함수는, 기본적으로 x 오름차순으로, x가 같다면 y 오름차순으로 정렬할 때 쓰는 비교 함수입니다.

 

 

 쉽게 말해서, this가 o보다 앞선다면, this.x - o.x < 0이거나, this.x - o.x = 0이면서 this.y - o.y < 0이여야 합니다.

 

 

 이제 Main class를 이렇게 작성하신 다음에 프로그램을 실행시켜 봅시다.

 

 

 제가 설정한 기준대로 잘 정렬되었음을 볼 수 있습니다.

 


 그런데 이런 경우가 있습니다. 어떠한 객체가 Comparable이 구현되어 있습니다. 그럴 때, 그 객체를 다른 기준으로 정렬하려면 어떻게 해야 할까요? 사실 여러 방법이 있습니다. 그냥 Wrapper Class를 만들고 거기 내에서 Comparable <...>을 쓰는 방법이 있긴 합니다만. Comparator를 쓰는 것도 나쁘지 않습니다. Java의 String은 이미, Comparable 합니다.

 

 

 그러니, 그냥 String은, ArrayList에 그냥 넣기만 하고 sort를 해도 잘 돌아감을 알 수 있습니다.

 

 

 위와 같이 코드를 작성하고 실행시켜 봅시다.

 

 

 문자열이 알파벳 소문자로만 이루어져 있다면, 사전순으로 정렬이 됩니다. 그런데, 우리는 다른 정렬 기준을 필요로 할 때가 있습니다. 예를 들어서, 알파벳 소문자로만 이루어진 String을 내림차순으로 정렬을 한다던지, 아니면 한글과 영어 소문자로만 이루어진 것이 있을 때, 한글이 출석 번호 앞에 오게 하고, 영어는 뒤에 오게 한다던지. 이 둘은 분명히 다른 기준입니다.

 

 

 이 때, 우리는 비교 클래스를 만들어 줄 수 있어요. 이를 저는 comp 클래스라고 하겠습니다.

 

 

 이것은 implements Comparator<...>를 하는데요. 이것은 반드시 compare 메서드를 구현해야 합니다. o1과 o2를 비교하는데요. o1이 o2보다 우선 순위가 낮다면 양수를, 같다면 0을, o1이 o2보다 더 높다면 음수를 리턴합니다. 만약에 알파벳 순으로만 왔을 때, 사전 역순으로 정렬하려면 어떻게 해야 할까요?

 

 가장 쉬운 방법은 o1.compareTo(o2)의 리턴값에 -1을 곱하는 것입니다. o1 < o2일 때, o1.compareTo(o2)는 음수를 리턴하는데요. 여기에 -1을 곱하면, o1<o2일 때, -o1.compareTo(o2)는 양수를 리턴합니다. 이 때, 비교 클래스인 comp에 의하면 o1이 o2보다 사전순으로 앞설 때, o1>o2다. 라는 것을 알려줍니다.

 

 

 혹은, 이렇게 생각하셔도 됩니다. String 2개가 알파벳 소문자로만 이루어졌다고 해 봅시다. 원래 String의 compareTo는, this와 매개변수로 넘어온 o2를 비교했을 때, this가 앞서면 음수를 리턴합니다. this가 뒤에 있으면 양수를 리턴하고요. 위 그림은, o1과 o2를 비교할 때 쓰는, o1.compareTo(o2)를 했을 때, 음수를 리턴하는 경우 중 하나입니다.

 

 그런데, 우리는 o1가 o2보다 사전순으로 앞설 때, o1의 우선 순위가 o2의 우선 순위보다 낮게 하고 싶다. 이 말입니다. 그러면 거꾸로 이렇게 비교하면 어떤가요?

 

 

 이 때, o2.compareTo(o1)을 하면, 0보다 큰 양수가 리턴될 겁니다. 만약에, compare 함수의 매개변수에 o1, o2 순서대로 넘겨주었고, o1이 o2보다 사전 순으로 앞선 상황입니다. 이 때, 양수가 리턴된다면, o1은 o2보다 우선 순위가 낮다고 판단을 할 거에요. 즉, 사전순으로 뒤집으려면, o2.compareTo(o1)을 해도 된다는 것입니다. 이것은 ps를 하실 때, 많이 쓰이는 것이니 알아두시면 좋을 듯 싶네요.