Stream API 활용편2 (매핑)
지난 포스트에 이어서, 계속 Stream API에 대한 포스팅을 하고 있습니다.
Stream API를 활용하여, 컨테이너(Collection)에서 복잡하고 다양한 조건을 질의형태로 구현할 수 있다는 것을 배웠습니다. 방어코드 없이 말이죠!!
지난 포스팅은 이곳에서 확인!
이러한 질의와 같은 방법은 SQL과 비슷해서 우리가 원하는 결과를 깔끔하게 구현할 수 있는데요. 오늘은 검색과 더불어 특정 데이터를 선택하는 작업인 매핑에 대해 알아보려 합니다.
계속해서 알기 쉬운 SQL에 비교하여 알아보겠습니다.
우리는 DBMS에 질의를 할 때, 언제나 테이블의 ROW 데이터 모두를 원하지 않습니다. 질의어 자체에서 출력 결과를 조정할 수 있으며, 선택을 할 수 있습니다. 예를들어 "구성원이라는 테이블에, 이름만 출력" 하고자 한다면 이렇게 사용을 하죠.
1 2 3 4 5 6 | SELECT member.name FROM member as member WHERE member.age = 40 | cs |
JAVA8에서는 map 메소드를 사용하여 특정 필드의 결과만을 가지는 Collection을 생성할 수 있습니다. 함수형 인터페이스로 친다면 Function 의 성격을 가지고 있습니다. (T를 받아 R을 출력하고 있으니 말이죠 ㅡㅡ^)
함수형 인터페이스의 성격이 무슨 말인지 모르신다면 해당 포스트를 참고하세요.
다시 돌아와서, map의 활용을 보도록하겠습니다.
일단 테스트VO 클래스에 대한 명시입니다.
1 2 3 4 5 6 | public class MemberVo { private Integer sn; private Integer age; private String name; } | cs |
위 VO는 구성원을 가르키며, 고유넘버, 나이, 이름 정도의 멤버 변수를 가지고 있습니다.
상단의 언급한 SQL 쿼리의 로직을 수행해보도록 하죠!
Map의 활용
1 2 3 4 5 6 7 8 9 10 | List<MemberVo> memberList = Arrays.asList( new MemberVo(1, 27, "남두현"), new MemberVo(2, 20, "유형주"), new MemberVo(3, 20, "태재영"), new MemberVo(4, 40, "남궁계홍")); memberList.stream() .filter(a -> a.getAge().intValue() == 40) .map(MemberVo::getName) .forEach(System.out::println); console value : 남궁계홍 | cs |
지난번에 배웠던 검색 메소드인 filter를 이용하여 나이에 대한 조건을 언급하였고, map 메소드를 이용하여 이름이라는 데이터를 출력하였습니다.
보다 다양한 결과를 출력하고 싶다면, map 메소드의 파라미터안에 적절한 람다 혹은 메소드 레퍼런스를 언급해주면 됩니다.
JAVA 8 IN ACTION 에서는 이러한 map 과 더불어, flatMap을 같이 소개했습니다. flatMap의 기능은 스트림의 평면화로, 두 개의 컨테이너 객체의 스트림을 하나로 묶는 것을 말합니다. (쉽게 말해서 SQL의 JOIN과 같다고 볼 수 있습니다. ㅡㅡ^)
flatMap을 이용하여 두 테이블을 합친 로직을 구현해볼까요?
FlatMap의 활용
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 | List<MemberVo> memberList = Arrays.asList(new MemberVo(1, 27, "남두현"), new MemberVo(2, 20, "유형주"), new MemberVo(3, 20, "태재영"), new MemberVo(4, 40, "남궁계홍")); List<BoardDetailVo> boardList = Arrays.asList(new BoardDetailVo(1, "람다게시판입니다.", "람다에 관련된 클만 올려주세요."), new BoardDetailVo(1, "람다란 무엇이니가?", "함수형 프로그래밍의 꽃?"), new BoardDetailVo(2, "물어볼 게 있습니다.", "람다공부는 어떻게 하죠?")); memberList.stream().flatMap(i -> boardList.stream() .filter(j -> j.getSn() == i.getSn().intValue()) .map(j -> { MemberBoardResultVo result = new MemberBoardResultVo(); result.setSn(i.getSn()); result.setName(i.getName()); result.setContents(j.getContens()); result.setTitle(j.getTitle()); return result; })).forEach(ret -> { System.out.println(String.format("[%s]%s:%s", ret.getName(), ret.getTitle(), ret.getContents())); } ); - console result - [남두현]람다게시판입니다.:람다에 관련된 클만 올려주세요. [남두현]람다란 무엇이니가?:함수형 프로그래밍의 꽃? [유형주]물어볼 게 있습니다.:람다공부는 어떻게 하죠? | cs |
일단 chain된 Stream 이 살짝 복잡해 보이네요. 파이프 하나씩 한번 살펴보죠!
flatMap 내부부터 한번 살펴보면,
1. memberList의stream이 주가 되어, flatMap 내부에서 boardList의 Stream을 사용하고 있습니다.
1 | memberList.stream().flatMap(i -> boardList.stream()....) | cs |
2. boardList의 stream 내부 파이프라인 중 filter는 memberList 의 각 객체 중, 본인 객체의 sn이 서로 같은 것만 찾으라는 조건을 명시하고 있습니다.
1 | filter(j -> j.getSn() == i.getSn().intValue()) | cs |
3. boardList stream의 내부 map에서는 Function 의 역할로 MemberBoardResultVo 를 출력하도록 되어 있습니다. 람다표현식을 사용하여, MemberVo와 BoardDetailVo 에서 각각 필요한 정보를 주입합니다.
1 2 3 4 5 6 7 8 | map(j -> { MemberBoardResultVo result = new MemberBoardResultVo(); result.setSn(i.getSn()); result.setName(i.getName()); result.setContents(j.getContens()); result.setTitle(j.getTitle()); return result; }) | cs |
4. foreach에서는 위의 flatMap으로부터 출력된 컨테이너를 순회하며, 필요한 정보를 출력하고 있습니다.
1 2 3 4 5 6 7 | forEach(ret -> { System.out.println(String.format("[%s]%s:%s", ret.getName(), ret.getTitle(), ret.getContents())); } ); | cs |
람다식이 조금 복잡해보이지만, 사실 기존 java8 이전의 구현 방식대로 진행한다고 하면, 2중 for-loop을 통해 보다 복잡한 로직이 될 수 있습니다. 위의 예제는 비교적 간단해보이지만, 컬렉션 3~4개를 합친다거나, 합쳐진 컬렉션에서 limit, skip, order by 등의 추가사항이 붙는다면 기존 구현된 소스를 보고 고민에 빠지는 시간이 조금 길어질것입니다.
하지만 람다식을 통해 다음과 같이 chain 메소드 한개만 출력해주면 간단히 해결이 됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | memberList.stream().flatMap(i -> boardList.stream() .filter(j -> j.getSn() == i.getSn().intValue()) .map(j -> { MemberBoardResultVo result = new MemberBoardResultVo(); result.setSn(i.getSn()); result.setName(i.getName()); result.setContents(j.getContens()); result.setTitle(j.getTitle()); result.setAge(i.getAge()); return result; })) .sorted((a, b)-> a.getAge().compareTo(b.getAge())) .limit(1) .forEach(ret -> { System.out.println(String.format("[%s]%s:%s", ret.getName(), ret.getTitle(), ret.getContents())); } ); - console result - [유형주]물어볼 게 있습니다.:람다공부는 어떻게 하죠? | cs |
단순히, 두 객체를 머징하는 내부 람다에 age를 추가 하였고, sorted와 limit를 사용하였습니다.
즉 우리는 이번 포스팅을 통해 알 수 있었던 것은 컨테이너를 사용하는 로직에서
단순히 체인형 stream을 추가하는 방법만으로 변화에 보다 쉽고 유연하게 대처
할 수 있음을 알게 되었습니다.
|
'개발이야기 > 함수형 프로그래밍' 카테고리의 다른 글
Stream API 활용편4 (리듀싱) (0) | 2017.01.23 |
---|---|
Stream API 활용편3 (검색과 매칭) (0) | 2017.01.18 |
Stream API 활용편1 (필터와 슬라이싱) (0) | 2016.08.26 |
Stream과 Collection 차이 (0) | 2016.08.04 |
Stream API (0) | 2016.08.03 |