Guava로 Ordering으로 컬렉션 정렬하기
얼마전에 Guava 에 대해서 Guava를 써야하는 5가지 이유 라는 포스팅 했었는데 컬렉션 위주로 조금씩 써보고 있습니다.
Comparable을 이용한 정렬
자바에서 클래스를 정렬하려면 Comparable을 구현해야 합니다. 다음 Person 클래스를 보겠습니다. 간단한 엔티티클래스입니다.
1// Person.java
2package kr.sideeffect;
3
4public class Person implements Comparable<Person> {
5 private String name;
6 private int age;
7
8 public Person(String name, int age) {
9 this.name = name;
10 this.age = age;
11 }
12
13 public String getName() {
14 return name;
15 }
16 public void setName(String name) {
17 this.name = name;
18 }
19 public int getAge() {
20 return age;
21 }
22 public void setAge(int age) {
23 this.age = age;
24 }
25
26 public int compareTo(Person o) {
27 return this.getAge() - o.getAge();
28 }
29}
자바의 Collections의 sort함수를 사용하려면 담고 있는 클래스가 위처럼 Comparable인터페이스를 구현해서 compareTo함수를 구현해 놓아야 합니다. compareTo함수를 이용해서 정렬의 조건을 정해줄 수 있는데 여기서는 나이순으로 정렬하기 위해서 age필드를 비교했습니다. 두 값이 같으면 0, this가 크면 양수, this가 적으면 음수가 리턴되도록 작성하면 됩니다. 동작을 확인하기 위해서 다음과 같이 테스트코드를 작성했습니다.
1// PersonTest.java
2package kr.sideeffect;
3
4import static org.junit.Assert.*;
5
6import java.util.ArrayList;
7import java.util.Collections;
8import java.util.List;
9import org.junit.Test;
10
11public class PersonTest {
12 @Test
13 public void 정렬테스트() throws Exception {
14 // given
15 Person p1 = new Person("Outsider", 32);
16 Person p2 = new Person("nephilim", 40);
17 Person p3 = new Person("Anarcher", 35);
18 Person p4 = new Person("fupfin", 43);
19 Person p5 = new Person("Arawn", 33);
20
21 List<Person> list = new ArrayList<Person>();
22 list.add(p1);
23 list.add(p2);
24 list.add(p3);
25 list.add(p4);
26 list.add(p5);
27
28 // when
29 Collections.sort(list);
30
31 // then
32 assertEquals(list.get(0).getName(), p1.getName());
33 assertEquals(list.get(1).getName(), p5.getName());
34 assertEquals(list.get(2).getName(), p3.getName());
35 assertEquals(list.get(3).getName(), p2.getName());
36 assertEquals(list.get(4).getName(), p4.getName());
37 }
38}
이 테스트 코드는 성공합니다. compareTo를 작성하였기 때문에 나이순으로 잘 정렬이 됩니다.(위 예제의 나이는 실제인물과는 상관없이 예제를 위한 것임을 밝힙니다.) 하지만 정렬을 해야할 때마다 Comparable을 구현해주어야 하는건 꽤나 귀찮은 일이고(당연한 일인지 모르겠지만 전 무척 귀찮더군요.) 정렬을 여러가지 해야할 경우에는 더욱 피곤해 집니다.
Guava의 Ordering
Guava에서는 Ordering이라는 클래스로 정렬을 제공할 수 있습니다. Ordering을 테스트하기 위해서 Geek이라는 새로운 엔티티를 만들었고 Comparable을 구현하지 않았으므로 Collections.sort로 정렬할 수 없습니다.
1// Geek.java
2package kr.sideeffect;
3
4public class Geek {
5 private String name;
6 private int age;
7
8 public Geek(String name, int age) {
9 this.name = name;
10 this.age = age;
11 }
12
13 public String getName() {
14 return name;
15 }
16 public void setName(String name) {
17 this.name = name;
18 }
19 public int getAge() {
20 return age;
21 }
22 public void setAge(int age) {
23 this.age = age;
24 }
25}
이 Geek 엔티티를 담은 정렬을 사용하기 위해서 다음과 같은 테스트코드를 작성하였습니다.
1// GeekTest.java
2package kr.sideeffect;
3
4import static org.junit.Assert.*;
5
6import java.util.ArrayList;
7import java.util.Collections;
8import java.util.List;
9import org.junit.Test;
10import com.google.common.collect.Ordering;
11import com.google.common.primitives.Ints;
12
13public class GeekTest {
14 @Test
15 public void 정렬테스트() throws Exception {
16 // given
17 Geek g1 = new Geek("nephlim", 40);
18 Geek g2 = new Geek("Anarcher", 35);
19 Geek g3 = new Geek("fupfin", 43);
20 Geek g4 = new Geek("Arawn", 33);
21
22 List<Geek> list = new ArrayList<Geek>();
23 list.add(g1);
24 list.add(g2);
25 list.add(g3);
26 list.add(g4);
27
28 // when
29 Ordering<Geek> byAge = new Ordering<Geek>() {
30 @Override
31 public int compare(Geek left, Geek right) {
32 return Ints.compare(left.getAge(), right.getAge());
33 }
34 };
35
36 Collections.sort(list, byAge);
37
38 // then
39 assertEquals(list.get(0).getName(), g4.getName());
40 assertEquals(list.get(1).getName(), g2.getName());
41 assertEquals(list.get(2).getName(), g1.getName());
42 assertEquals(list.get(3).getName(), g3.getName());
43 }
44}
when에 작성한 부분이 Ordering 을 사용한 부분입니다. new Ordering으로 새로운 클래스를 만들면서 Ordering의 compare를 오버라이드라이드해서 정렬의 기준을 작성해 주면 됩니다. 예제에서는 Guava가 프리미티브타입에 대한 유틸리티성 클래스인 Ints를 사용해서 compare 로 두 값을 비교했습니다. 사실 이는 Comparator 인터페이스를 이용해서 when부분을 다음처럼 작성해도 동일합니다.
1Comparator<Geek> byAge = new <Geek>() {
2 public int compare(Geek left, Geek right) {
3 return ((Integer)left.getAge()).compareTo((Integer)right.getAge());
4 }
5};
6
7Collections.sort(list, byAge);
하지만 Ordering을 쓰면 다양한 정렬에 대해서 쉽게 사용할 수 있습니다.
1Collections.sort(list, byAge.reverse());
2Collections.sort(list, byAge.reverse().nullsFirst());
3Collections.sort(list, byAge.reverse().nullsLast());
Ordering에는 정렬을 거꾸로 하는 reverse나 null의 정렬순서를 정해주는 nullsFirst, nullsLast가 있습니다. 기본적인 순서로 해주는 natural도 있습니다. 이러한 함수들을 조합해서 원하는 조합을 만들 수 있으며 좀 더 복잡한 정렬조건은 compound를 사용해서 조합할 수 있습니다.
Comments