[CHAPTER 14] Stream API 리뷰 (1) [과제 리뷰]
안녕하세요. :-) 블로그 주인장인 "성숙한 개발을 지향하는 Ndroid" 입니다.
(잘 모르겠지만, 이 것이 필명입니다.)
드디어 함수형 프로그래밍(FP) 챕터까지 마무리함으로써, 기본적으로 목표했던 내용까지 모두 끝냈습니다.
짧았지만 진행했던 시간동안, 개인적으로 당연하게 생각했던 지식들을 다시 한번 되돌아 볼 수 있는 계기가 되었고, 이러한 여러가지 생각들을 공유할 수 있는 자리를 만들 수 있던 것은 행운이었던 것 같습니다.
진행했던 시간동안 도움이 되었고, 실제로 잘 사용만 해준다면 더할나위없이 좋은 것은 없겠죠? ㅎㅎ
그리고! 스터디를 끝냈던 주차에 [전혀, 생각 못한 기분좋은 이벤트?] 도 있어서 매우 행복했습니다.
(하하하하하 @.@)
그렇다고, 후기같은 감성글을 남기고자 할 생각은 아닙니다!!!!
이번 포스팅 부터 세 차례동안 지난 내용에 대한 리뷰글을 남기고자 합니다.
혹시나 복습을 하고자 하는 분들이 있을지도 모르니(할지는 의문?), 그에 대한 이정표를 제공하고자 합니다.
첫번째 주제는 Stream API (1) 에서 진행했던 과제 리뷰입니다.
이 글에 대한 자료는 아래 포스팅에서 참고!
과제에서는 JAVA8 이전에 작성된 간단한 코드를 Stream API 를 이용하여 리팩토링하는 시간을 가졌었습니다.
단계별로 하나씩 보도록 하죠.
Q1. Filter 를 이용한 if 절 리팩토링.
아래 코드는 컬렉션을 순회하며, 특정 조건에 맞는 자료를 출력하는 로직입니다.
1 2 3 4 5 6 7 8 9 | // 구성원 정보 출력. List<MemberVo> memberVoList = MemberService.GetInstance().selectMemberList(); for (MemberVo memberVo : memberVoList) { // 구성원 순번이 3 이하인 정보만 출력. if (memberVo.getMemberSubjectSn() < 3) { System.out.println(String.format("이름 : %s, 나이 : %d", memberVo.getName(), memberVo.getAge())); } } | cs |
비교적 간단해보이는 이 로직은 내부반복을 이용하는 Stream API 에서 filter 키워드를 이용하여 리팩토링할 수 있었습니다.
1 2 3 4 | MemberService.GetInstance().selectMemberList(). stream(). filter(memberVo -> memberVo.getMemberSubjectSn() < 3). forEach(memberVo -> System.out.println(String.format("이름 : %s, 나이 : %d", memberVo.getName(), memberVo.getAge()))); | cs |
위의 코드나 아래 코드나 사실 큰 차이는 없어보이지만,
함수형으로 작성된 코드는 분기조건(filter)과 실행행위(foreach)의 로직을 분리하고 있습니다.
이는 꽤 복잡한 로직에서 생각보다 매력적으로 다가올 수 있을 것처럼 보입니다.
관련 내용은 아래 포스팅에서 확인할 수 있습니다. :-)
Q2. 정렬과 결과의 개수 제한 로직 리팩토링.
이번 문제에서는 Stream API 의 조금 더 기능을 써보고자 진행했던 내용입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 구성원 정보 출력. List<MemberVo> memberVoList = MemberService.GetInstance().selectMemberList(); // 나이 순으로 내림차순 정렬, Collections.sort(memberVoList, new Comparator<MemberVo>() { @Override public int compare(MemberVo o1, MemberVo o2) { return o2.getAge().compareTo(o1.getAge()); } }); // 구성원 목록이 존재할 경우, 나이가 가장 많은 사람의 이름을 출력. if (!memberVoList.isEmpty()) { MemberVo memberVo = memberVoList.get(0); System.out.println(String.format("나이가 가장 많은 사람은 ? %s", memberVo.getName())); } | cs |
Q1 과 비교하여, 특정 조건에 맞게 정렬도 해야하며, 정렬된 결과에 따라 딱 한개만 결과가 나오도록 해야하는군요.
하지만, Stream API 에는 다양한 파이프라인 메소드들이 존재하니 이 것 역시 쉽게 해결할 수 있을 것입니다.
1 2 3 4 5 | MemberService.GetInstance().selectMemberList(). stream(). sorted((o1, o2) -> o2.getAge().compareTo(o1.getAge())). findFirst(). ifPresent(memberVo -> System.out.println(String.format("나이가 가장 많은 사람은 ? %s", memberVo.getName()))); | cs |
Stream API 에서 지원하는 sort 메소드를 이용하여 정렬하였고, 첫 번째 데이터를 찾기 위해 findFirst 를 사용했습니다.
findFirst 의 결과는 있을 수도 없을 수도 있기 때문에, Optional 형태로 출력함을 잊지마세용.
(현재 케이스에서는 MemberService::selectMemberList 의 결과가 빈 리스트일 수 있음.)
여기서 또한 주목할 점은 의무체인 Lambda 를 이용해볼 수 있다는 것입니다.
sort 의 비교구문은 현재 Lambda 로 되어있지만, Comparator 에서 제공하는 팩토리 메소드를 이용하면 조금 더 직관적으로 변경할 수 있습니다.
1 2 3 4 5 6 | // 나이를 이용하여, 내림차순 정렬. Comparator<MemberVo> comparator1 = (MemberVo o1, MemberVo o2) -> o2.getAge().compareTo(o1.getAge()); // Comparator 의 comparing 으로 어떤 값을 이용하여 정렬할지 지정. // reversed 메소드를 이용하여 내림차순. Comparator<MemberVo> comparator2 = Comparator.comparing(MemberVo::getAge).reversed(); | cs |
어때요? 보다 직관적이고 수정하기 쉽겠죠?
해당 내용은 아래 포스팅에서 확인할 수 있습니다. :-)
2016/08/02 - [개발이야기/함수형 프로그래밍] - 람다 조합
2016/08/26 - [개발이야기/함수형 프로그래밍] - Stream API 활용편1 (필터와 슬라이싱)
2017/01/18 - [개발이야기/함수형 프로그래밍] - Stream API 활용편3 (검색과 매칭)
Q3. 맵핑 튜토리얼.
Stream API 에서 가장 많이 사용한다고 볼 수 있는 mapping(사상화)에 대한 튜토리얼입니다.
map 을 이용하면, 간단하게 컬렉션의 제네릭 타입을 목적에 맞게 변경시킬 수 있습니다.
예제를 한번 보죠. 아래 코드는 간단합니다.
구성원목록에서 지역을 추출한 뒤, 오름차순에 따라 정렬 후 콘솔 출력을 하고 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 구성원 정보 출력. List<MemberVo> memberVoList = MemberService.GetInstance().selectMemberList(); // red-black tree 알고리즘 따라, 이 곳에 넣은 정보는 자동으로 정렬이 됨. final TreeSet<String> treeSet = new TreeSet<>(); { for (MemberVo memberVo : memberVoList) { treeSet.add(memberVo.getLocation()); } } for (String location : treeSet) { System.out.println(location); } | cs |
아래 코드는 map 메소드를 이용하여, 아래와 같이 리팩토링할 수 있습니다.
1 2 3 4 | MemberService.GetInstance().selectMemberList().stream(). map(MemberVo::getLocation). sorted(). forEach(System.out::println); | cs |
정말 간단해서 좋군요.
map 에 치환 Function 만 적절(MemberVo -> String)하게 넣어주면, 모든 것이 해결! ^^
해당 내용은 아래에서 확인할 수 있습니다.
Q4. FlatMap 을 이용한 Inner Join 패턴 구현.
특정 Stream 을 다른 Stream 으로 변경할 수 있는 flatMap 을 이용하여, Inner join 과 같은 패턴을 구현해 볼 수 있습니다.
이를 활용해 보기 위한 예제는 아래와 같았습니다.
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 | // 구성원 정보 출력. List<MemberVo> memberVoList = MemberService.GetInstance().selectMemberList(); // 코멘트 정보 출력. List<CommentVo> commentVoList = MemberService.GetInstance().selectMemberCommentList(); final ArrayList<MemberCommentVo> memberCommentVoList = new ArrayList<>(); { // 구성원 정보와 코멘트 정보를 엮는다. for (MemberVo memberVo : memberVoList) { for (CommentVo commentVo : commentVoList) { if (memberVo.getMemberSubjectSn().intValue() == commentVo.getMemberSubjectSn().intValue()) { MemberCommentVo memberCommentVo = new MemberCommentVo(); memberCommentVo.setMemberSubjectSn(memberVo.getMemberSubjectSn()); memberCommentVo.setComment(commentVo.getComment()); memberCommentVo.setName(memberVo.getName()); memberCommentVoList.add(memberCommentVo); } } } // 이름으로 정렬. Collections.sort(memberCommentVoList, new Comparator<MemberCommentVo>() { @Override public int compare(MemberCommentVo o1, MemberCommentVo o2) { return o1.getName().compareTo(o2.getName()); } }); } // 오직 세개까지만 처리. for (int i = 0, size = memberCommentVoList.size(); i < 3; ++i) { MemberCommentVo memberCommentVo = memberCommentVoList.get(i); System.out.println(String.format("%s : %s", memberCommentVo.getName(), memberCommentVo.getComment())); } | cs |
두 타입의 목록에서 구성원순번이 같은 데이터 끼리 엮어 새로운 리스트를 만듭니다.
또한 새로운 목록을 이름 순으로 정렬하고 있네요.
게다가 3개 까지만, 출력하도록 제한하고 있습니다.
이 로직을 구현하기 위해서 꽤 코드가 길어졌군요.
하지만, Stream API 를 이용하면, 간단하게 처리할 수 있을 것 같습니다.
큰 관건은 두 데이터를 잘 엮어주기만 하면 될 것 같아 보이며, 나머지 정렬이나 데이터 제한은 앞써 배운 메소드들을 이용하면 될 것 같네요.
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 | // 구성원 정보 출력. List<MemberVo> memberVoList = MemberService.GetInstance().selectMemberList(); // 코멘트 정보 출력. List<CommentVo> commentVoList = MemberService.GetInstance().selectMemberCommentList(); memberVoList.stream(). flatMap(memberVo -> { // flatMap 은 현재 stream 에서, 다른 타입의 stream 으로 변경할 수 있게 해줌. return commentVoList.stream(). filter(commentVo -> { // 구성원 순번이 같은 것끼리만 필터하도록 처리! return memberVo.getMemberSubjectSn().equals(commentVo.getMemberSubjectSn()); }). map(commentVo -> { // 취합 데이터 생산. MemberCommentVo memberCommentVo = new MemberCommentVo(); memberCommentVo.setMemberSubjectSn(memberVo.getMemberSubjectSn()); memberCommentVo.setComment(commentVo.getComment()); memberCommentVo.setName(memberVo.getName()); return memberCommentVo; }); } ). sorted(Comparator.comparing(MemberCommentVo::getName)). limit(3). forEach(memberCommentVo -> String.format("%s : %s", memberCommentVo.getName(), memberCommentVo.getComment())); | cs |
flatMap 구문을 보면, 특정 구성원 데이터(memberVo)에 대하여 새로운 Stream 를 출력하도록 하는 Function 을 받도록 되어 있습니다.
이에 따라, CommentVoList 에서는 구성원 순번에 따라 알맞은 데이터를 filter 하고 mapping 하는 Stream 을 넘기도록 하고 있습니다.
나머지 구문은 이름만 봐도 무슨 일을 하는지 알 수 있겠죠? :-)
- sorted : 구성원 이름에 따라 정렬
- limit : 3개 제한
- foreach : 데이터를 이용한 행위 구현.
해당 내용은 아래에서 확인할 수 있습니다.
Stream API 에 대한 첫 번째 튜토리얼은 꽤 심박하지만, 강력했던 것 같습니다.
특히, 내부반복을 이용한다는 측면에서 많은 비지니스 로직을 [필터,맵핑,결과]등 여러 단계를 명시적으로(반 강제적 ㅡㅡ^) 나눌 수 있기 때문에 유지보수성에 있어서 꽤 괜찮은 코드들을 작성할 수 있을 것 같습니다.
조금만 익숙해지면, 실무에서 이를 적용하는 것은 어렵지 않을 것입니다. :-)
그럼, 이 쯤에서 이번 포스팅을 마칩니다.
복습하는 그대여, 파이팅!!! @.@
'스터디 > [STUDY] FP' 카테고리의 다른 글
[CHAPTER 17] 함수형 프로그래밍 기타 내용 [실습 리뷰] (0) | 2017.08.13 |
---|---|
[CAHPTER 15] Stream API 리뷰 (2) [과제 리뷰] (2) | 2017.08.13 |
[CHAPTER 17] Optional + 디폴트 메소드 + 기타. (0) | 2017.08.03 |
[CAHPTER 15] Stream API 리뷰 (2) (3) | 2017.07.26 |
[CAHPTER 14] Stream API 리뷰 (1) (0) | 2017.07.20 |