[Java 1.8] 두개의 리스트 조인(병합)하기

자바에서 타입이 다른 두개의 리스트를 조인하고 싶다. 데이터베이스에서 조인을 해서 가져오는 것이 최선이지만 어플에서 조인을 해야하는 경우도 종종 생기는 것 같다. 마침 자바 1.8 에서 제공하는 람다 표현식을 써보고 싶어서 테스트 겸해서 하나 만들어 보았다.

예제

게시판 리스트 화면을 예로 들겠다. 게시글 리스트에서 회원이름을 출력하고 싶다. 게시판인 Article 클래스에는 memberNo를 가지고 있고, Member클래스에는 memberNo와 memberName이 있다. List<Article> 와 List<Member>를 조인해보자.




List<Article>안에 memberName이 전부 null이 였는데, Member 클래스에서 회원번호를 찾아서 이름을 넣어주는 코드다.


List<Article> articles;
List<Member> member;

for (Article article : articles) {
    for (Member member : members) {
        if (article.getMemberNo() == member.getMemberNo()) {
            article.setMemberName(member.getMemberName());
        }
    }
}

이중 루프를 썼더니 잘 된다. 이제 같은 동작을 1.8 의 람다 표현식을 사용해서 구현해보자. CollectionJoiner 라는 클래스를 만들어서 안에서 루프를 돌리고, 외부에서는 조건문과 할당해주는 부분만 넘기면 되게 해보자.

먼저 테스트할 코드부터 작성해보자.


List<Article> newList = CollectionJoiner.collections(articles, members)
    .when((article, member) -> article.getMemberNo() == member.getMemberNo())
    .then((article, member) -> {
        article.setMemberName(member.getMemberName());
        return article;
    });

2중루프와 같은 동작을 하는 CollectionJoiner 클래스를 사용할 때의 코드이다. 위의 코드가 작동하게 하기 위해서는 두가지의@FunctionalInterface를 사용해야 한다.

  • BiPredicate<T, U> 클래스의 boolean test(T, U);
  • BiFunction<T, U, R> 클래스의 R apply(T, U);

이제 아래 클래스를 생성해보자.

CollectionJoiner.java

package com.test;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;

public class CollectionJoiner{

    private CollectionJoiner() {
    }

    public static <T, U> CollectionJoinerHelper<T, U> collections(Collection<T> t, Collection<U> u) {
        return new CollectionJoinerHelper<T, U>(t, u);
    }

    public static class CollectionJoinerHelper<T, U> {

        private Collection<T> t;
        private Collection<U> u;
        private BiPredicate<T, U> condition;

        private CollectionJoinerHelper(Collection<T> t, Collection<U> u) {
            this.t = t;
            this.u = u;
        }

        public CollectionJoinerHelper<T, U> when(BiPredicate<T, U> condition) {
            this.condition = condition;
            return this;
        }

        public <R> List<R> then(BiFunction<T, U, R> function) {

            List<R> list = new ArrayList<>();

            for (T tOne : t) {
                for (U uOne : u) {
                    if (condition.test(tOne, uOne)) {
                        list.add(function.apply(tOne, uOne));
                    }
                }
            }

            return list;
        }

    }

}

클래스를 추가하고 실행해보면, 결과는 이중루프를 돌린 것과 같다.

처음에는 CollectionJoiner 같은 클래스로 만들면 사용할 때, 코드 양이 줄어들 줄 알았는데, 비슷한 것 같다. 그래도 자바 1.8의 람다를 연습하는 데에는 도움이 되었다.
지금은 then을 호출하자마자 List<R>를 리턴하는데, 나중에는 리턴타입도 Collectors 클래스를 통해서 정할 수 있도록 만들어 봐야겠다.