[Info]Tags categorized posts and contents patterns..

[AJAX] Ajax Code E xamples.. [Book] About the book.. [CSS] CSS Code E xamples.. [DB] Sql Code E xamples.. [DEV] All development stor...

레이블이 Guava인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Guava인 게시물을 표시합니다. 모든 게시물 표시

2016년 4월 6일 수요일

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

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

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

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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함수를 제공하고 있는데 정의는 다음과 같습니다.


Java

1
2
3
4
5
public static <F,T>
  List<T> transform(
    List<F> fromList,
    Function<? super F,? extends T> function
  )

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

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 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로 변환하려면 다음과 같이 작성할 수 있습니다.


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

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

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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등이 존재하는데 제대로 안서봐서 차이점들은 잘 모르겠습니다.


2016년 4월 5일 화요일

[JAVA]Guava로 Ordering으로 컬렉션 정렬하기..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

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

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


Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 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가 적으면 음수가 리턴되도록 작성하면 됩니다. 동작을 확인하기 위해서 다음과 같이 테스트코드를 작성했습니다.


Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 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로 정렬할 수 없습니다.


Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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 엔티티를 담은 정렬을 사용하기 위해서 다음과 같은 테스트코드를 작성하였습니다.

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 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부분을 다음처럼 작성해도 동일합니다.

Java
1
2
3
4
5
6
7
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을 쓰면 다양한 정렬에 대해서 쉽게 사용할 수 있습니다.

Java
1
2
3
Collections.sort(list, byAge.reverse());
Collections.sort(list, byAge.reverse().nullsFirst());
Collections.sort(list, byAge.reverse().nullsLast());

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

[JAVA]Guava를 써야하는 5가지 이유..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

얼마전에 올렸던 List에서 중복요소 제거한 List로 만들기에서 많은 댓글이 오가면서 반농담삼아 그냥 Guava를 쓰자라는 결론(?)에 이르르면서 최근에 Guava를 좀 찾아서 써보면서 Insightful Logic이라는 런던의 스타트업회사의 회사의 블로그에서 작성한 5 Reasons to use Guava라는 글을 보고 Guava를 파악해보기 위해서 번역한 글을 포스팅합니다. 이 내용은 Insightful Logic에 번역허락을 받고 올립니다.

Guava는 구글이 작성한 자바 오픈소스 라이브러리입니다. 개발자라면 이전에 한번정도는 스스로 작성해 보았거나 필요했지만 작성할 시간이 없었을만한 유용한 유틸리티 함수와 클래스들을 다양하게 제공하고 있습니다. 여기에 Guava를 사용하면 좋은 5가지 이유가 있습니다.

1. 컬렉션 초기화와 유틸리티
동질한 제너릭 컬렉션(제너릭 호모지니어스 컬렉션)은 자바가 가진 좋은 기능입니다. 하지만 때때로 컬렉션의 생성하기 아주 거추장 스럽습니다.


Java
1
2
// 컬렉션 생성
final Map<String, Map<String, Integer>> lookup = new HashMap<String, Map<String, Integer>>();

Java 7에서는 이 문제를 정말 제너릭한 방법으로 해결했는데 제한된 타입추론의 형식을 형식에 구애받지 않고 다이아몬드 오퍼레이터로 참조할 수 있도록 하였습니다다. 그래서 위의 예제를 다음처럼 작성할 수 있습니다.


Java
1
2
// in Java 7
final Map<String, Map<String, Integer>> lookup = new HashMap<>();

이전의 자바버전에서도 생성자없는 메서드생성자가 아닌 메서드를 통해서 이러한 추론은 사실상 가능했습니다. 그리고 Guava는 이미 존재하는 자바 컬렉션에 생성자를 만드는 준비를 위해 많을 것을 제공합니다. 위의 예제는 다음처럼 작성할 수 있다.

Java
1
2
// in Guava
final Map<String, Map<String, Integer>> lookup = Maps.newHashMap();

또한, Guava는 많은 유용한 유틸리티 함수가 Maps, Sets등의 컬렉션에 들어있습니다. 특별히 제가 좋아하는 것은 Sets.union과 Sets.intersection메서드인데 이는 값을 재계산하지 않고 sets의 뷰를 리턴합니다.

2. 제한된 함수형 스타일의 프로그래밍
Guava는 함수형 스타일로 메서드를 전달하기 위한 일반적인 메서드들을 제공합니다. 예를 들어 많은 함수형 언어가 가지고 있는 map이 Collections2.transform 메서드의 형식으로 존재합니다. 또한 Collections2는 컬렉션에서 어떤 값을 제한하도록 하는 filter 메서드를 가지고 있습니다. 예를 들어 컬렉션에서 null인 요소들을 제거하고 다른 컬렉션에 저장하기 위해서 다음처럼 사용할 수 있습니다.


Java
1
2
// Collections2.filter
Collection<?> noNullsCollection = filter(someCollection, notNull());

존재하는 컬렉션을 수정하는 대신에 새로운 컬렉션을 리턴하고 계산된 컬렉션은 Lazy하게 계산된다는 것을 알아야 합니다.

3. 멀티맵(Multimaps)과 바이맵(Bimaps)
단일키에 여러 값을 저장하는 것등은 Map의 정말 일반적인 사용입니다. 일반적으로 표준 자바 컬렉션의 사용은 값타입처럼 또 다른 컬렉션을 사용함으로써 이루어집니다. 이것은 안타깝게도 결국에는 컬렉션 초기화같은 반복되는 많은 형식를 포함하게 됩니다. 멀티맵(Multimaps)은 이것을 아주 깔끔하게 만들어줍니다.


Java
1
2
3
4
5
Multimap<String, Integer> scores = HashMultimap.create();
scores.put("Bob", 20);
scores.put("Bob", 10);
scores.put("Bob", 15);
System.out.println(Collections.max(scores.get("Bob"))); // prints 20

또한 다른 방향의 바이맵(Bimaps) 클래스가 있습니다. 바이맵은 키처럼 값의 유일함을 강제합니다. 값도 유일하기 때문에 바이맵은 반대로 값을 키처럼 사용할 수도 있습니다.

4. 쉬운 해쉬코드와 비교자(Comparators)
자바에서 필드들의 해쉬코드로 클래스의 해쉬코드를 생성하는 것은 아주 일반적입니다. Guava는 이것을 위해서 Object 클래스에 유틸리티 메서드를 제공합니다. 다음은 그 예제입니다.


Java
1
2
3
4
5
6
7
int foo;
String bar;

@Override
public int hashCode() {
  return Objects.hashCode(foo, bar);
}

해쉬코드 메서드를 정의했다면 equals 계약을 유지하는 것을 잊지 말아야 합니다.

비교자(Comparator)는 오퍼레이션의 순서를 연결하는 것을 포함해서 자주 작성하게되는 또 다른 예입니다. Guava는 이 과정을 쉽게 하기 위해서 ComparisonChain 클래스를 제공합니다. 여기 int와 String클래스의 예제가 있습니다.


Java
1
2
3
4
5
6
7
int foo;
String bar;

@Override
public int compareTo(final GuavaExample o) {
  return ComparisonChain.start().compare(foo, o.foo).compare(bar, o.bar).result();
}

5. 방어적 코딩
메서드의 형식을 위해서 어떠한 필수조건들을 작성하는 것에 대해서 스스로 찾아본 적이 있습니까? 때때로 이것은 불필요하게 거추장스럽게 되거나 의도를 직접적으로 전달하는데 실패할 수 있습니다. Guava는 Preconditions 클래스를 일반적인 필수조건의 시리즈로 제공합니다.

필수조건을 위해서 if문과 명시적인 예외를 던지는 예제가 있습니다.


Java
1
2
3
if (count <= 0) {
  throw new IllegalArgumentException("must be positive: " + count);
}

명시적으로 필수조건을 작성할 수 있습니다.

Java
1
2
// in Guava
checkArgument(count > 0, "must be positive: %s", count);

결론
존재하는 라이브러리 클래스를 Guava로 교체하는 것은 유지보수를 위해 필요한 많은 양의 코드를 줄여주고 잠재적으로 생산성을 높여줍니다. 예를 들어 Apache Commons 프로젝트의 대안이 될 수 있습니다. 당신은 이미 이러한 라이브러리를 알고 있거나 사용할 수도 있고 Guava의 접근과 api를 선호할 수도 있습니다. Guava는 Idea Graveyard를 가지고 있습니다. Idea Graveyard 구글 엔지니어들이 라이브러리의 제한과 나쁜 디자인 결정에 대해서 무엇을 이해하고 있는가에 대한 아이디어를 줍니다. 자신만의 라이브러리 클래스를 작성하는 것으로 다시 돌아간다는 점에서 이러한 결정에 개인적으로 동의하지 않을 수도 있습니다. 그럼에도 불구하고 Guava는 간결하고 적은 형식을 장려하고 Guava의 몇몇 적절한 애플리케이션의 많은 자바 프로젝트에 도움이 될 수 있습니다.