Java 프로젝트를 수행하며 항상 고민하는 라이브러리로 Guava와 Apache Commons가 있다. 세분화 된 라이브러리와 다양한 기능은 Apache Commons이 좋지만 결국 한 개, 한 개 추가하다보면 apache-xxx 라이브러리에 대한 많은 의존성이 생긴다. 굳이 필요없는 기능까지 많이 의존하게 되어 나의 경우는 대부분 Guava를 기본으로 시작한다. 그리고 Java 프로젝트를 하며 항상 사용하는 라이브러리를 정리하려고 기록한다.

Guava

Guava는 Google의 core libraries이다. Collection(JDK 버전이 올라가며 많이는 필요 없는), caching, string processing 등 프로젝트 구현 시 공통적으로 필요한 요소들이 존재한다.

익숙해지면 코드량을 엄청나게 줄이며 구현이 가능하고 가독성이 좋아진다. 그 중에서도 내가 주로 사용하고 편리한 기능들을 다음과 같이 정리하였다. 예제 코드는 참고[1]에 작성된 Wiki에서 참고하였다.

Preconditions

전달된 Aurgments에 대한 검증을 수행하는데 매번 if-else를 사용하기에는 코드가 너무 길어진다. 이 때 Preconditions를 사용하면 다음과 같이 한 줄로 구현 가능하다.

checkArgument(i >= 0, "Argument was %s but expected nonnegative", i);
checkArgument(i < j, "Expected i < j, but %s > %s", i, j);

특히 checkNotNull, checkState 등 자주 사용하는 패턴들이 정의되어 있어 더 짧고 명시적으로 표현한다.

Common object methods

equals, hashCode, toString은 자바에서 클래스 구현 시 매번 구현하는 반복작업이다. 요즘에는 IntelliJ IDEA를 이용하여 샤~샥 한 방에 만들어내는데 Guava의 MoreObjects와 Objects를 이용하여 생성한다. 그 이유는 역시 짧게 정리된 코드 때문이다.

import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;

public class Person {
    private final String id;
    private final int age;

    public Person(String id, int age) {
        this.id = id;
        this.age = age;
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("id", id)
                .add("age", age)
                .toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equal(id, person.id);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(id, age);
    }
}

위 코드의 toString() 결과는 Person{id=dgb, age=84}이다.

Caches

Cache 기능은 항상 최적화를 위해서 구현한다. Guava를 이용하면 Cache 크기, expire 시간을 정의하여 쉽게 Cache를 만든다.

LoadingCache<String, Person> peopleCache = CacheBuilder.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build(
            new CacheLoader<String, Person>() {
                @Override
                public Person load(String id) throws Exception {
                    return personRepository.findById(id);
                }
            });
            
final Person person = peopleCache.get("P10000");

Person 객체에 대한 Cache를 만든 예제이다. ID가 P10000인 Person 객체를 가져오는데 Cache에 존재하면 해당 인스턴스를 사용하고 없는 경우 personRepository.findById를 이용하여 가져온다. CacheLoader 구현체 정의 시에 load 함수 외에 loadAll 함수를 이용해서 모든 목록을 가져오게 적용 가능하다.

Ranges

Range도 숫자 값에 대한 검증, 혹은 조건에 따른 처리 시에 많이 활용하는 기능이다. 사실 더 많은 기능이 있지만 나는 A < x < B 와 같은 조건 처리 용도로 많이 사용한다.

Range.closed(1, 3).contains(2); // true
Range.closed(1, 3).contains(4); // false
Range.lessThan(5).contains(5); // returns false
Range.closed(1, 4).containsAll(Ints.asList(1, 2, 3)); // returns true

Guava Guide Wiki에 있는 예제(참고 [1])으로 Range를 이해하기에 가장 좋은 예제이다. 이 외에도 greaterThan, atLeast, intersection 등이 유용하다.

Guava 좋아요!

Guava는 참 좋다. 코드도 내가 선호하는 스타일이다. 사실 위에서 언급한 내용보다 Guava는 훨씬 많은 기능을 가지고 있다.

그래도 좀 자주 사용하는 기능

Guava에도 단점이 있다. JDK7, 8, 이제 9로 버전이 올라가면서 stream, collections 등 좋은 기능들이 많이 생겨서 Guava에 있는 일부 기능들은 많이 더 이상 사용되지 않는(Deprecated)다. 즉, Guava 없이도 동일한 기능을 꽤 짧고 간결하게 코드 작성이 가능하다. 그리고 Apache commons에는 대부분 훨씬 많은 기능을 포함하고 있다.

하지만 아직까지 Guava에서도 필요한 많은 기능들과 간결한 코드를 제공한다.

ps. 사실 Guava와 Apache commons는 그때 그때 기분에 따라 사용하는 듯하다. 둘 다 워낙 좋아서 해당 프로젝트에서 정의하는데로 사용하면 될 것 같다. 하지만 난 개인적으로는 GUAVA!

참고