Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Guava로 Ordering으로 컬렉션 정렬하기

얼마전에 Guava에 대해서 Guava를 써야하는 5가지 이유라는 포스팅 했었는데 컬렉션 위주로 조금씩 써보고 있습니다.



Comparable을 이용한 정렬
자바에서 클래스를 정렬하려면 Comparable을 구현해야 합니다. 다음 Person 클래스를 보겠습니다. 간단한 엔티티클래스입니다.


// Person.java
package kr.sideeffect;

public class Person implements Comparable<Person> {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
    public int compareTo(Person o) {
        return this.getAge() - o.getAge();
    }
}

자바의 Collections의 sort함수를 사용하려면 담고 있는 클래스가 위처럼 Comparable인터페이스를 구현해서 compareTo함수를 구현해 놓아야 합니다. compareTo함수를 이용해서 정렬의 조건을 정해줄 수 있는데 여기서는 나이순으로 정렬하기 위해서 age필드를 비교했습니다. 두 값이 같으면 0, this가 크면 양수, this가 적으면 음수가 리턴되도록 작성하면 됩니다. 동작을 확인하기 위해서 다음과 같이 테스트코드를 작성했습니다.


// PersonTest.java
package kr.sideeffect;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Test;

public class PersonTest {
    @Test
    public void 정렬테스트() throws Exception {
        // given
        Person p1 = new Person("Outsider", 32);
        Person p2 = new Person("nephilim", 40);
        Person p3 = new Person("Anarcher", 35);
        Person p4 = new Person("fupfin", 43);
        Person p5 = new Person("Arawn", 33);
        
        List<Person> list = new ArrayList<Person>();
        list.add(p1);
        list.add(p2);
        list.add(p3);
        list.add(p4);
        list.add(p5);
        
        // when
        Collections.sort(list);
    
        // then
        assertEquals(list.get(0).getName(), p1.getName());
        assertEquals(list.get(1).getName(), p5.getName());
        assertEquals(list.get(2).getName(), p3.getName());
        assertEquals(list.get(3).getName(), p2.getName());
        assertEquals(list.get(4).getName(), p4.getName());
    }
}


이 테스트 코드는 성공합니다. compareTo를 작성하였기 때문에 나이순으로 잘 정렬이 됩니다.(위 예제의 나이는 실제인물과는 상관없이 예제를 위한 것임을 밝힙니다.) 하지만 정렬을 해야할 때마다 Comparable을 구현해주어야 하는건 꽤나 귀찮은 일이고(당연한 일인지 모르겠지만 전 무척 귀찮더군요.) 정렬을 여러가지 해야할 경우에는 더욱 피곤해 집니다.



Guava의 Ordering
Guava에서는 Ordering이라는 클래스로 정렬을 제공할 수 있습니다. Ordering을 테스트하기 위해서 Geek이라는 새로운 엔티티를 만들었고 Comparable을 구현하지 않았으므로 Collections.sort로 정렬할 수 없습니다.

// Geek.java
package kr.sideeffect;

public class Geek {
    private String name;
    private int age;
    
    public Geek(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}


이 Geek 엔티티를 담은 정렬을 사용하기 위해서 다음과 같은 테스트코드를 작성하였습니다.


// GeekTest.java
package kr.sideeffect;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import com.google.common.collect.Ordering;
import com.google.common.primitives.Ints;

public class GeekTest {
    @Test
    public void 정렬테스트() throws Exception {
        // given
        Geek g1 = new Geek("nephlim", 40);
        Geek g2 = new Geek("Anarcher", 35);
        Geek g3 = new Geek("fupfin", 43);
        Geek g4 = new Geek("Arawn", 33);
        
        List<Geek> list = new ArrayList<Geek>();
        list.add(g1);
        list.add(g2);
        list.add(g3);
        list.add(g4);
    
        // when
        Ordering<Geek> byAge = new Ordering<Geek>() {
            @Override
            public int compare(Geek left, Geek right) {
                return Ints.compare(left.getAge(), right.getAge());
            }
        };
        
        Collections.sort(list, byAge);
    
        // then
        assertEquals(list.get(0).getName(), g4.getName());
        assertEquals(list.get(1).getName(), g2.getName());
        assertEquals(list.get(2).getName(), g1.getName());
        assertEquals(list.get(3).getName(), g3.getName());
    }
}


when에 작성한 부분이 Ordering을 사용한 부분입니다. new Ordering으로 새로운 클래스를 만들면서 Ordering의 compare를 오버라이드라이드해서 정렬의 기준을 작성해 주면 됩니다. 예제에서는 Guava가 프리미티브타입에 대한 유틸리티성 클래스인 Ints를 사용해서 compare로 두 값을 비교했습니다. 사실 이는 Comparator인터페이스를 이용해서 when부분을 다음처럼 작성해도 동일합니다.


Comparator<Geek> byAge = new     <Geek>() {
    public int compare(Geek left, Geek right) {
        return ((Integer)left.getAge()).compareTo((Integer)right.getAge());
    }
};
        
Collections.sort(list, byAge);


하지만 Ordering을 쓰면 다양한 정렬에 대해서 쉽게 사용할 수 있습니다.


Collections.sort(list, byAge.reverse());
Collections.sort(list, byAge.reverse().nullsFirst());
Collections.sort(list, byAge.reverse().nullsLast());

Ordering에는 정렬을 거꾸로 하는 reverse나 null의 정렬순서를 정해주는 nullsFirst, nullsLast가 있습니다. 기본적인 순서로 해주는 natural도 있습니다. 이러한 함수들을 조합해서 원하는 조합을 만들 수 있으며 좀 더 복잡한 정렬조건은 compound를 사용해서 조합할 수 있습니다.
2011/11/17 01:49 2011/11/17 01:49