Outsider's Dev Story

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

Guava로 리스트에서 객체의 필드로 새로운 리스트 만들기

List<Person>같은 컬렉션을 가지고 있을 때 Person클래스의 특정필드만을 가지고 새로운 리스트를 만들고 싶었습니다. for문으로 돌면서 새로운 리스트를 만들어도 되지만 Guava를 쓰면 쉽게 만들 수 있었습니다.


// Person.java
package kr.sideeffect;

public class 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;
  }
}

지난번 Ordering에 대한 포스팅에서도 사용했던 Person클래스입니다.(새로 만들기가 귀찮아서...) Guava에는 List<F>를 List<F>로 바꿔주는 Lists.transform함수를 제공하고 있는데 정의는 다음과 같습니다.


public static <F,T>
  List<T> transform(
    List<F> fromList,
    Function<? super F,? extends T> function
  )

transform함수는 변환할 대상이 되는 리스트를 받고 이 리스트를 어떻게 변환할지를 정하는 Function을 두번째 파라미터로 받아서 새로운 리스트를 만들어줍니다.


// PersonTest.java
package kr.sideeffect;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import com.google.common.base.Function;
import com.google.common.collect.Lists;

public class PersonTest {
    
  @Test
  public void 필테스트() throws Exception {
    // given
    Person p1 = new Person("Outsider", 32);
    Person p2 = new Person("nephlim", 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
    Function<Person, String> nameFilter = new Function<Person, String>() {
        public String apply(Person person) {
          return person.getName();
        }
    };
        
    List<String> result = Lists.transform(list, nameFilter);

    // then
    assertEquals(result.size(), 5);
    assertEquals(result.get(0), p1.getName());
  }
}


여기서는 Person을 담고 있는 리스트인 list에서 Person의 name만으로 만들어진 새로운 리스트를 만들려고 하는 것이기 때문에 nameFilter라는 Function객체를 만들었습니다.  이 객체는 Person타입에서 String타입으로 변환되기 때문에 Function<Person, String>으로 정의했습니다. 변환하는 함수는 클래스 내부의 apply함수로 정의하는데 apply함수는 Person을 받아서 String을 리턴해 주도록 만들었고 내부에서 name의 값을 리턴해 줍니다. 이를 Lists.transform(list, nameFilter)와 같이 사용하면 name만 담긴 새로운 리스트를 만들어줍니다.

디버그모드에서 result의 타입을 확인한 화면

list는 ArrayList였지만 transform이 리턴해주는 리스트는 ArrayList는 아닙니다. 디버그로 찍어보면 위처럼 나오는데 정확한 타입은 잘 모르겠네요. 이를 ArrayList로 변환하려면 다음과 같이 작성할 수 있습니다.


// ArrayList로 변환
List<String> result = Lists.newArrayList(Lists.transform(list, nameFilter));


Lists에 static함수로 newArrayList가 제공되기 때문에 간단하게 ArrayList로 변환할 수 있습니다.


Function<Person, String> nameFilter = new Function<Person, String>() {
  public String apply(Person person) {
    return person == null ? null : person.getName();
  }
};

List<String> result = Lists.newArrayList(Iterables.filter(
    Iterables.transform(list, nameFilter), 
    Predicates.<String>notNull()
  ));


만약 null에 대한 체크가 필요하다면 위처럼 할 수 있습니다. 이건 Guava의 Iterables.transform과 Iterables.filter를 조합한 것입니다. 8번라인을 보면 Lists.transform대신에 Iterables.transform를 사용해서 변환을 하고 변환된 리스트를 다시 Iterables.filter에 Predicates를 사용해서 null인 객체를 모두 제거해 주었습니다. 물론 nameFilter에서는 person이 null일 경우 null을 리턴해주도록 했습니다.

즉흥적으로 찾아보면서 쓰고 있는지라 제대로 쓰고 있는지는 잘 모르겠네요 Lists.transform외에도 Iterables.transform, Iterators.transform, Collections2.transform등이 존재하는데 제대로 안서봐서 차이점들은 잘 모르겠습니다.
2011/11/28 23:48 2011/11/28 23:48