안녕하세요. :-) 블로그 주인장인 "성숙한 개발을 지향하는 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


어때요? 보다 직관적이고 수정하기 쉽겠죠?


해당 내용은 아래 포스팅에서 확인할 수 있습니다. :-)




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 에 대한 첫 번째 튜토리얼은 꽤 심박하지만, 강력했던 것 같습니다.


특히, 내부반복을 이용한다는 측면에서 많은 비지니스 로직을 [필터,맵핑,결과]등 여러 단계를 명시적으로(반 강제적 ㅡㅡ^) 나눌 수 있기 때문에 유지보수성에 있어서 꽤 괜찮은 코드들을 작성할 수 있을 것 같습니다.


조금만 익숙해지면, 실무에서 이를 적용하는 것은 어렵지 않을 것입니다. :-)


그럼, 이 쯤에서 이번 포스팅을 마칩니다.


복습하는 그대여, 파이팅!!! @.@

반응형
Posted by N'

JAVA8 을 이용한 FP 의 마지막 스터디입니다.

(시작이 있으면 끝도 있는 법...... ㅜㅡㅜ)


다음 스터디에서 진행할 아래 글들을 미리 읽고 오면 좋을 것 같습니다. :-)



이번 포스팅 역시, [JAVA8 IN ACTION] 과 같이 읽는 것을 권장합니다.


또한, 이번 스터디 역시 무언가 있습니다.


STUDY_OOP_FP_17.zip


알집을 보고, "또 숙제야" 할 수 있겠지만, 이번에는 숙제가 아니라 실습입니다.

마지막 내용이니, 같이 (페어 프로그래밍?.. 아닌 듯..) 한 번 해보죠. 


분명 스터디 시작 때는 같이 코딩하자고 해놓고서, 한 번도 안한 것 같아 준비했습니다. @.@



그럼, 마지막까지 파이팅!!!

반응형
Posted by N'

프로그래머는 일련의 비지니스 문제를 해결하기 위해 프로그램을 개발합니다.

(당연한 이야기지만, ㅡㅡ^)


이 때 비지니스 로직 중 가장 많이 사용하는 것은 아마 자료구조일 것이며, 수많은 목록형 데이터를 처리하기 위해 자료구조를 순회하며 행위를 수행하도록 작성하곤 합니다.


이를테면, 아래와 같이 말이죠?


1
2
3
4
5
6
List<String> nameList = Arrays.asList("강현지""남두현""유덕형""유형주");
        
for (String name : nameList) {
    // 리스트 목록을 순회하며, 데이터를 콘솔로 출력
    System.out.println(name);
}
cs


이와 같이 List 자체의 내부 구현 방법은 노출되어 있지 않지만, 내부의 데이터를 쉽게 순회할 수 있다는 것은 매력적입니다.

(즉 사용자는 ArrayList, LinkedList 의 내부 구현 상태는 알 필요가 없고, for-each 같은 순회 방법만 알면 됩니다.)


이와 같은 패턴을 Iterator 패턴(반복자 패턴)이라 부르며, 이번 포스팅에서는 이에 대한 예제와 함께 자세한 이야기를 다뤄보고자 합니다.


Iterator 패턴은 객체 내부의 특정 목록 데이터 순회하는 것이 목적이며, 흔히 아래와 같은 인터페이스를 구현하도록 하고 있습니다.

(아래 인터페이스는 java.util 패키지에 존재하는 Iterator 인터페이스 입니다.)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface Iterator<E> {
 
    /**
     * 다음 순회하고자 하는 객체가 존재하는가?
     *
     * @return
     */
    boolean hasNext();
 
    /**
     * 순회하고자 하는 다음 객체를 출력.
     * 
     * @return
     */
    E next();
}
cs


해당 인터페이스는 특정 목록 데이터를 순회하는 로직에 사용하는 메소드 서명들을 포함하고 있습니다. 

(다음 순회하는 항목이 있는가? 다음 항목으로 이동 등...)


이를 이용하여, 특정 클래스의 내부 목록을 순회하는 방법을 다음과 같이 서술해 볼 수 있을 것 같군요.


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
/**
 * 심플한 반복자의 구현
 *
 * Created by Doohyun on 2017. 8. 1..
 */
public class SimpleArrayIterator implements Iterator<String>{
 
    private String[] helloArray;
    private int position = 0;        // 현재 위치한 포지션의 index 관리
 
    public SimpleArrayIterator(String[] helloArray) {
        this.helloArray = helloArray;
    }
 
    /**
     * 현재 포지션이 길이 안에 존재하며 해당 배열의 값이 null 이 아닐 때, 다음 항목이 존재한다고 볼 수 있습니다.
     *
     * @return
     */
    @Override
    public boolean hasNext() {
        return position < helloArray.length && helloArray[position] != null;
    }
 
    /**
     * 다음 포지션으로 진행.
     *
     * @return
     */
    @Override
    public String next() {
        ++position;
        return helloArray[position - 1];
    }
}
cs


이렇게 구현한 반복자를 사용하는 방법은 종종 비지니스 로직을 작성하던 중 구현해 본 적이 있을 것입니다.


보통 Iterator 를 사용하여 순회하는 예제는 다음과 같이 제공하곤 합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 목록 데이터.
String[] nameArray = {"강현지""남두현""유덕형""유형주"};
 
// 반복자 제작.
SimpleArrayIterator simpleArrayIterator = new SimpleArrayIterator(nameArray);
 
// 반복자를 이용하여, 순회.
while (simpleArrayIterator.hasNext()) {
    System.out.println(simpleArrayIterator.next());
}
 
// CONSOLE LOG
// 강현지
// 남두현
// 유덕형
// 유형주
cs


하지만, 보통 우리는 반복자를 이용하여 데이터를 순회하기 보다는 for-loop 를 통해 순회를 하곤 합니다. 


특히, JAVA5 부터 지원하는 새로운 형태의 for문을 종종 이용하곤 하는데요.

이 때 Java.lang.Iterable 인터페이스를 구현해주면, 새롭게 작성된 클래스도 해당 for 문을 사용할 수 있습니다.


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
/**
 * Array 배열을 순회하는 반복가능 클래스 구현.
 *
 * Created by Doohyun on 2017. 8. 3..
 */
public class SimpleArrayIterable implements Iterable<String>{
 
    private String[] nameArray;
 
    public SimpleArrayIterable(String[] nameArray) {
        this.nameArray = nameArray;
    }
 
    /**
     * 반복자를 출력하도록 하고 있습니다.
     *
     * <pre>
     *     새로 작성된 for-loop 에서는 해당 반복자를 사용하도록 하고 있습니다.
     * </pre>
     *
     * @return
     */
    @Override
    public Iterator<String> iterator() {
        return new SimpleArrayIterator(nameArray);
    }
}
 
// 반복 가능 클래스 테스트 로직..
{
 
    // 목록 데이터.
    String[] nameArray = {"강현지""남두현""유덕형""유형주"};
 
    // 반복 가능 클래스의 인스턴스 제작.
    SimpleArrayIterable simpleArrayIterator = new SimpleArrayIterable(nameArray);
 
    // 새로운 형태의 for-loop 이용..
    for (String name : simpleArrayIterator) {
        System.out.println(name);
    }
 
}
cs


이로써, 예상할 수 있는 것은 모든 Collection 은 Iterable 을 구현하고 있다는 것을 알 수 있습니다.

모든 Collection 은 새로운 형태의 for-loop 을 사용할 수 있으니까요?


이와 같이, 알게 모르게 많은 사용하고 있던 Iterator 패턴은 [순회하는 방법을 캡슐화]하고 있습니다.

이는 Collection 의 내부 구현과 별도로 일관된 균일한 접근을 할 수 있도록 도와줄 수 있을 것 같습니다.

(꼭 Collection 이 아닌, Tree Graph 등 여러 자료구조를 탐색하는 것에도 사용할 수 있겠죠?)


이를 통해 꽤 괜찮은 컨셉의 비지니스 로직을 만들 수 있겠죠? ^^

한 번 이 글을 보고 실습해보면 나쁘지 않을 것 같습니다..... 



또한, 언제나 마무리 인사로,


"이 글을 보는 분들에게 도움이 되길 바랍니다. :-)"





반응형
Posted by N'