지난 포스트에 이어서, 계속 Stream API에 대한 포스팅을 하고 있습니다.


지난 포스팅에서는 각 컨테이너를 매핑하여, 원하는 형태의 객체로 출력을 할 수 있는 방법을 소개했었습니다. 

마치 SQL 처럼 특정 필드만을 추출한다던가, 두 개 이상의 컨테이너를 JOIN 할 수도 있었죠.


해당 포스팅은 이 곳에서 확인!! 



오늘은 특정 속성이 데이터 집합에 있는지, 있다면 해당 객체를 추출할 수 있는 다양한 유틸 메소드를 살펴볼까 합니다.


예를 들어, 우리는 이러한 작업을 많이 하곤 합니다.


아래 로은 특정 숫자 컬렉션에서 5의 배수가 있는지 확인하는 로직입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
List<Integer> sampleList = Arrays.asList(5,10,12,38);
        
Boolean isHave = false;
for (Integer number : sampleList) {
    if (number % 5 == 0) {        
        // 5의 배수인 데이터가 있는지 확인
        isHave = true;
        break;
    }
}
        
System.out.println("5의 배수가 존재  : " + isHave);
 
// 출력 : 5의 배수가 존재  : true
cs


루프를 돌면서, 출력할 컬렉션 객체에 if 조건을 사용하여 여부를 업데이트하려 하고 있습니다.


해당 로직은 다음과 같이 수정될 수 있습니다.



1. Predicate 가 적어도 한 요소와 일치하는지 확인 (anyMatch)


anyMatch 유틸 메소드로 다음과 같은 로직을 구할 수 있습니다.


1
2
3
4
5
List<Integer> sampleList = Arrays.asList(5,10,12,38);
        
System.out.println("5의 배수가 존재  : " + sampleList.stream().anyMatch(n -> n % 5 == 0));
 
// 출력 : 5의 배수가 존재  : true
cs


단순히 predicate 형태로 컬렉션 제네릭 객체의 조건만 명시해주면 조금 더 간소화된 코드를 구현할 수 있습니다.


2. Predicate 를 이용한 기타 확인 (allMatch, noneMatch)


Predicate 를 이용하여 컬렉션의 모든 요소가 일치하는지를 확인하려면, allMatch 메소드를 사용할 수 있습니다. 또한 반대로 일치하는 요소가 없는지를 확인하는 기능 역시도 noneMatch 메소드를 사용함으로써 간단하게 구현할 수 있습니다.


1
2
3
4
5
6
7
8
9
List<Integer> sampleList = Arrays.asList(5,10,15,40);
        
System.out.println("모든 요소가 5의 배수인가? : " + sampleList.stream().allMatch(n -> n%5 == 0));
System.out.println("5의 배수는 없는가? : " + sampleList.stream().noneMatch(n -> n % 5 == 0));
 
// 출력
// 모든 요소가 5의 배수인가? : true
// 5의 배수는 없는가? : false
 
cs


3. 요소 검색


Stream API 를 사용하면 쉽게 한 요소에 접근할 수 있습니다. 일련의 중간 연산(filter, distinct 등)을 수행한 후 findAny, findFirst 등의 최종연산 메소드로 조건에 맞는 한 요소를 선택할 수 있습니다. 예제를 한번 볼까요? 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
List<Integer> sampleList = Arrays.asList(17,19,28,37,16);
        
sampleList.
    stream().
    filter(n -> n % 2 == 0).
    findAny().
    ifPresent(n -> System.out.println("2의 배소의 요소 찾기 : " + n));
        
sampleList.
    stream().
    filter(n -> n % 2 == 0).
    findFirst().
    ifPresent(n -> System.out.println("2의 배소의 첫번째 요소 찾기 : " + n));
 
// 출력
// 2의 배소의 요소 찾기 : 28
// 2의 배소의 첫번째 요소 찾기 : 28
cs


두 메소드의 내용을 살펴보면, 동일함을 알 수 있습니다. 왜냐하면, 쇼트서킷 검사(보통 Boolean 연산 특정 조건에 의해 검사결과가 정해지면 추 후 연산은 안하는 행위)를 통해 결과를 찾은 뒤 즉시 실행을 하기 때문이죠.


그렇다면 왜 굳이 메소드를 두 개로 둔 이유가 무엇일까요?


그 이유는 공짜로 얻을 수 있는 병렬성 이 후 후처리 때문입니다. Collection 클래스에서 Stream 추출 시, parallelStream 을 통해 병렬 처리를 쉽게 수행할 수 있습니다. 그러나 병렬처리를 수행 시 컬렉션의 순서가 보장이 되지 않습니다! ㅡㅡ^


즉 병렬처리를 통해 데이터 연산을 수행하였다면, 명시적으로 첫 번째 요소를 찾기 위해 findFirst 를 사용하여야만 합니다! 


아래 예제를 통해 결과를 확인해볼까요? 아래 예제는 병렬처리를 했음에도 findFirst 사용 시, 첫 번째 요소찾기를 보장하고 있음을 보여주고 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
List<Integer> sampleList = Arrays.asList(17,19,28,37,16);
        
sampleList.
    parallelStream().
    filter(n -> n % 2 == 0).
    findAny().
    ifPresent(n -> System.out.println("2의 배소의 요소 찾기 : " + n));
        
sampleList.
    parallelStream().
    filter(n -> n % 2 == 0).
    findFirst().
    ifPresent(n -> System.out.println("2의 배소의 첫번째 요소 찾기 : " + n));
 
// 출력
// 2의 배소의 요소 찾기 : 16
// 2의 배소의 첫번째 요소 찾기 : 28

cs



마지막으로 생각해봐야할 부분은 요소 검사 메소드 활용 시, 결과가 항상 있다는 것을 보장하지 않는다는 점입니다. 보통 이럴 때 기존 자바에서는 null 로써, 데이터를 처리했지만 JAVA8 부터 많은 함수형 프로그래밍에서 볼 수 있는 Optional 의 개념이 생겼습니다. 


Optional값의 존재나 부재 여부를 표현하는 컨테이너 클래스로, 예제에서 사용한 ifPresent 는 값이 존재할 때만 실행을 명령하는 메소드 입니다. (Optional 은 추 후 따로 포스팅을 한 후 연결을 시켜두겠습니다. ㅡㅡ^)



자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기



반응형
Posted by N'

현재 개발하고 있는 앱의 메모리 누수가 감지되고 있습니다. ㅡㅡ^ 


메모리 누수 감지는 


https://github.com/square/leakcanary


를 통해서 프로파일링을 하고 있고, 대부분 문제가 스레드가 활동하는 동안 Context 가 살아남아서 생기는 문제가 있습니다.


뭐 자바의 패러다임으로 볼 때, 메모리는 VM 이 관리해주기 때문에 딱히 개발자가 건들 필요는 없어보이지만, 안드로이드 메모리가 제한적이고 각 객체가 더이상 쓰이지 않을 때 바로바로 해제를 해주어야 한다는 측면에서 적절한 작업이 이뤄줘야하는 것으로 보입니다. 특히 액티비티 인스턴스는 용량이 크기 때문에 잘 해제가 되야 합니다. ㅡㅡ^, 


해당 문제를 해결하기 위해, 아래 포스트처럼 WeakReference 를 통해 해결을 하려 하고 있으며, 이를 적용하기 위한 일괄된 패턴이 필요했습니다.



그래서 이번에 시도해보고 있는 것은 

잔디 블로그 (http://tosslab.github.io/android/2015/03/01/01.Android-mvc-mvvm-mvp.html

에서 소개한 MVP 패턴 사례를 참고하여, 메모리 누수를 막아보려고 하고 있습니다.


제가 하려고 했던 아래 코드입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SplashActivity extends Activity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
    }
 
    @Override
    protected void onResume() {
        super.onResume();
 
        Handler hd = new Handler();
        hd.postDelayed(() -> {
            // 심플하지만, 이 곳 핸들러의 역할 때문에 메모리 누수가 존재
            setResult(RESULT_OK);
            finish();
 
            overridePendingTransition(R.anim.abc_fade_in, R.anim.abc_fade_out);
        }, 1000);
    }
}
cs



핸들러 내부에서 Context 가 살아남아 있으며, 실제 프로파일러에서는 Handler 에서 메모리 릭이 감지되었다고 나오고 있습니다.


이러한 이유로, 해야할 일을 나누어 봅시다.


WeakReference 관리는 Presenter 가 담당합니다. 아래와 같이 말이죠.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SplashPresenter {
    private Handler hd;
    private WeakReference<SplashActivity> splashActivityWeakReference;
 
    public SplashPresenter(SplashActivity splashActivity) {
        splashActivityWeakReference = new WeakReference<>(splashActivity);
        hd = new Handler();
    }
 
    /**
     * 1초 후에 해당 액티비트를 종료한다.
     */
    public void delayFinished() {
        hd.postDelayed(() -> {
            SplashActivity splashActivity = splashActivityWeakReference.get();
 
            if (splashActivity != null) {
                splashActivity.smoothFinishActivity();
            }
 
        }, 1000);
    }
}
cs


잔디 블로그의 경우에는 Presenter 를 인터페이스화 시킨 후 구현화하였지만,


- 저의 경우는 Activity 와 Presenter 가 1:1 관계이고, 

- Activity 에서 View 관련 업데이트를 해야할 종류가 많을 경우 interface 에 그만큼 메소드를 정의해야하기 때문에 불편하다는 생각이 있었으므로, 


Activity 와 Presenter 를 단순 has-a 관계로 처리하고자 하였습니다.


그리하여, Activity 구현 부는 다음과 같이 View 만 업데이트하는 구조를 취하게 되었습니다. Activity 자체가 WeakReference 관리를 할 필요는 없는것이죠.


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
public class SplashActivity extends Activity {
    private SplashPresenter splashPresenter;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
 
        splashPresenter = new SplashPresenter(this);
    }
 
    @Override
    protected void onResume() {
        super.onResume();
        splashPresenter.delayFinished();
    }
 
    /**
     * Activity 종료
     * <p>
     * <pre>
     *     Activity 를 페이드아웃 애니메이션을 사용하며 종료.
     * </pre>
     */
    public void smoothFinishActivity() {
        setResult(RESULT_OK);
        finish();
        overridePendingTransition(R.anim.abc_fade_in, R.anim.abc_fade_out);
    }
}
cs


다음과 같은 구조를 통해, View 를 담당하는 Activity 클래스는 WeakReference 를 고려할 필요가 없게 되었으며, Activity instance 는 GC Time에 메모리 회수가 가능하게 되었습니다.



반응형
Posted by N'

Rx의 도입과 액티비티를 옵저버로 관리하게 되니, 메모리 누수라는 골치아픈게 발생하고 있습니다.


WeakReference 클래스를 사용하면 된다고 나오지만, 정확히 이해가 가지 않아, 테스트 코드를 작성해보았습니다.


일단, WeakReference 를 사용하지 않을 경우.

- 확인해보면 GC 가 강제 실행되었지만, A 내부 B 객체가 살아있음을 알 수 있습니다. (컬렉션이 물고 있기 때문이죠.)


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
public class Test {
 
    public static class A {
        private B b = new B();
 
        public B getB() {
            return b;
        }
 
        public void setB(B b) {
            this.b = b;
        }
 
        public static final class B {
            public String text = "아직 살아있다.";
        }
    }
 
    public static void main(String[] args) {
        // TODO Auto-generated method stu
        A a = new A();
        HashSet<A.B> abc = new HashSet<>();
 
        abc.add(a.getB());
        a = null;
        System.gc(); // GC 강제 발생
 
        for (A.B b : abc) {
            System.out.println(b.text);
        }
    }
}
 
// 아직 살아있다.
 
cs


WeakReference 로 참조를 유지할 경우.

- GC 가 돌며, A 의 인스턴스가 삭제되었고, 참조하고 있던 B의 인스턴스도 삭제가 되었음을 알 수 있습니다. (안드로이드에서도 이 힌트를 가지고, 메모리 릭을 방지할 수 있을 것이라 생각합니다. ㅡㅡ^)


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
public class Test {
 
    public static class A {
        private B b = new B();
 
        public B getB() {
            return b;
        }
 
        public void setB(B b) {
            this.b = b;
        }
 
        public static final class B {
            public String text = "아직 살아있다.";
        }
    }
 
    public static void main(String[] args) {
        // TODO Auto-generated method stu
        A a = new A();
        HashSet<WeakReference<A.B>> abc = new HashSet<>();
 
        abc.add(new WeakReference<>(a.getB()));
 
        a = null;
        System.gc();
 
        for (WeakReference<A.B> b : abc) {
            System.out.println(b.get().text);
        }
    }
}
 
// Exception in thread "main" java.lang.NullPointerException
    at Test.main(Test.java:31)
 

cs


반응형
Posted by N'

안드로이드에서 메모리 릭이 발생하고 있는지 아닌지를 다음 코드를 통해 확인합시다.


기종 따라 다르지만, instanceCount 가 무한대로 증가한다면, 문제가 있는 겁니다. ㅡㅡ^


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestInstanceActivity extends Activity {  
           public static int instanceCount = 0;
           public TestInstanceActivity () {
                     super();
                     instanceCount++;
                     Log.d("test""TestInstanceActivity() instanceCount: " + instanceCount);
           }
 
 
           @Override
           protected void finalize() throws Throwable {
                     super.finalize();
                     instanceCount--;
                     Log.d("test""finalize() instanceCount: " + instanceCount);
           }
}
cs


반응형
Posted by N'

안드로이드 N이 등장하면서, 클라이언트의 배터리를 아껴주기 위하여, DOZE 모드가 등장하였습니다.


이 때, 모든 네트워크 작업이 중단 되기 때문에 반드시 이에 대한 처리를 해주어야합니다.

특히 푸시 서비스를 제공하는 앱을 개발하기 위해서는 FCM(이전 GCM 등)을 사용하여, 앱을 깨우는 작업이 필요합니다. (FCM은 유일하게 DOZE 모드 중, 네트워크 수신이 가능합니다.)


그러하기 때문에 테스트를 위해서 DOZE 모드 상태를 강제적으로 만들필요가 있는데, 아래와 같은 명령어를 ADB에서 사용하면 됩니다. (STEP 이 IDLE 상태로 변할 때까지 명령어를 난타합시다. ㅡㅡ^)



- adb shell dumpsys battery unplug (배터리 플러그 빼기)

- adb shell dumpsys deviceidle step (idle 모드 만들기)




반응형
Posted by N'

안녕하세요. 블로그 주인 "초보프로그래머" 입니다.


드디어 일상이란 카테고리에 첫 번째 게시물을 작성해보네요.

 

하지만 일상이라고 해서, 갑자기 큰 주제에서 벗어나는 것 보다는 그래도 개발 블로그니, 개발과 관련된 생각과 일상을 적어보려고 해요. ㅋㅋㅋㅋ


그 중, 첫번째 주제로 잡은 것은 UML을 그려보자라는 것이었습니다.


사실 문서화라는 것이 정말 귀찮은 작업이고, 작성했다 하더라도 누가 찾아볼까라는 생각도 되서 등한시 하고 있었는데, 이번에 인수인계 받은 로직을 리팩토링하며 UML을 그려보는 것이 어떨까에 대한 생각이 들었습니다.


갑자기 그 생각이 든 것은, 어찌보면 간단한 이유입니다.


객체지향적으로 프로그래밍한다? 디자인 패턴을 잘 활용한다? 분명 이 생각을 가지고 코딩을 수행하다보면, 분명 클래스의 개수가 기하급수적으로 늘어납니다. ㅡㅡ^ (리팩토링 전 모든 로직들이 Spring 프레임워크 기반의 서비스 클래스에 정의되어 있지만, 리팩토링 후 클래스들이 많이 생겼습니다. 즉 봐야할 클래스가 많아진 것이죠.)


짜임새있게 만들어진 구조라면 "변화에 유연한 개발자가 되자" 라는 것에 부합할 수 있겠지만 아무것도 모르는 사람이 보면 그냥 복잡하게 짠 코드로만 보일 것입니다. 


아래는 트리와 비슷한 개념을 코드로 표현하기 위해서, 컴포짓패턴을 사용한 것인데 모르는 사람이 보면 그냥 난감합니다. 하지만 어떤 트리의 개념이 추가된다면 AbstractApprovalDepth 클래스를 상속받은 구현클래스를 작성하여 위치에 맞게 child를 명시한 곳에 추가하면 됩니다. 모르면 그냥 모르죠.....



무언가 모순이지 않나요? 

유지보수 좋으라고 여러 패턴 도입하며, 개발을 했는데 남이 보면 복잡합니다. ㅡㅡ^


그래서 UML을 문서화하며 모든 부모클래스에 해당 UML을 언급하면 그나마 보기 좋지 않을까에 대해 고민해보게 되었습니다. 


원래 인스턴스를 만드는 것은 클래스 구조를 보았을 때, 가장 하위클래스를 사용하는 것이 적절하기 때문에 모든 비지니스 클래스에 UML을 넣는 것은 조금 그렇고(귀찮아질듯?), 어느정도 틀이 되는 인터페이스나 추상클래스에만 UML을 참고하게 하여 처음보는 사람이 부모클래스를 하나라도 타고 가면 UML을 볼 수 있게하자 라는 전략으로 해보려고 했습니다.


그래서 프로젝트 하나 몰래 만들고, ㅋㅋㅋ (아직 선배님들은 아무도 모르는게 함정..)



말머리는 다음과 같이 해보았습니다. 프로젝트의 취지입니다. 



첫 번째 이슈 작성!



이슈에는 목적을 명시하고 UML 올렸습니다. UML만 올리면 살짝 부족한 느낌이 있더군요. 그래서 살짝 코멘트도 같이 올렸습니다. (DB의 ERD처럼 관리가 잘되었으면 좋겠습니다. ㅜㅡㅜ)


UML을 명시했으니, 코드와 연결을 해야겠죠...


상위 클래스에 가서,




이렇게 연결을 해주었습니다. 문서화가 과연 도움이 될까요?



반응형

'개발이야기 > 기타 개발' 카테고리의 다른 글

블로그 커뮤니티 서비스 "Post Paper"  (0) 2015.02.07
Posted by N'

지난 포스트에 이어서, 계속 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(127"남두현"), new MemberVo(220"유형주"),
                new MemberVo(320"태재영"), new MemberVo(440"남궁계홍"));
        
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(127"남두현"), new MemberVo(220"유형주"),
                new MemberVo(320"태재영"), new MemberVo(440"남궁계홍"));
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을 추가하는 방법만으로 변화에 보다 쉽고 유연하게 대처


할 수 있음을 알게 되었습니다.



자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기



반응형
Posted by N'

안녕하세요. 블로그 주인인 초보프로그래머입니당.


오랜만의 블로그 포스팅입니다.  개인적으로 바쁜 일이 있어서(곧 회사내에서 출시를 하기 때문에 버그를 열심히 잡았죠... ㅡㅡ^), 오랜만에 공부를 해보고 내용을 정리하게 되었습니다.


솔직히 블로그할 시간은 있긴 했지만, 게을러지다보니 공부하게 되는 것이 쉽지가 않네요.


하지만 책을 산 것이 아깝고, 곧 진행했던 안드로이드 프로젝트에도 함수형 프로그래밍의 결실을 시도해봐야하니(RxJava or Android N) 다시 한번 붙잡게 되었습니다.


그런 의미에서 오늘은 계속 진행하던 Stream API 의 활용에 대해 보고자 합니다. 

Stream API에 대해 처음보게 된다면 이 곳을 먼저 확인해주세요.



이전에 Stream API를 소개할 때, 컨테이너(Collection)의 활용을 질의 형태로 작성할 수 있다고 언급했었는데요. 마치 고수준 언어인 SQL을 사용하듯 말이죠. (Collection 객체를 테이블이라고 본다면, 질의를 통해 어떤 결과를 얻는다 생각하면 좋겠네요? ㅡㅡ^)


오늘은 그 중 필터링과 슬라이싱(즉 검색.. ㅡㅡ^)을 해보려 합니다.


1. 필터링

SQL의 where 절과 같은 역할입니다. Collection 객체 중 프리디케이트(Predicate)에 부합하는 객체들을 따로 추출하는 역할을 합니다. 그 메소드는 이전부터 많은 예제로 사용되었던 filter 입니다. 


몸풀기로 시작해보겠습니다.


1
2
3
4
5
6
7
8
9
List<Integer> numberList = Arrays.asList(22345688910);
 
for (Integer a : numberList.stream().filter(a -> a % 2 == 0).
                collect(Collectors.toList())) {
    System.out.println(a);
}
 
console result : [2,2,4,6,8,8,10]
 
cs


filter 메소드를 이용하여, 짝수만을 조회하였습니다. SQL로 치자면, 이정도??


1
SELECT number FROM NUMBERLIST WHERE number % 2 = 0;

cs


2. 고유 요소 필터링

위의 결과 중, 중복된 결과가 있습니다. 원하는 결과가 중복된 값이 없기를 바란다면 distinct 메소드를 사용해주면 됩니다.


1
2
3
4
5
for (Integer a : numberList.stream().filter(a -> a % 2 == 0).
                distinct().collect(Collectors.toList())) {
    System.out.println(a);
}
 
cs


생각해보면 SQL에서 DISTINCT 를 사용하여, 고유 결과를 출력할 수 있다는 것을 알고 있습니다.


1
SELECT DISTINCT number FROM NUMBERLIST WHERE number % 2 = 0;
cs


3. 결과값 제한과 스킵

보통 게시판을 만들 때, limit offset 등을 사용하여 페이징 처리를 하곤 합니다. 왜냐하면 많은 데이터를 한번에 다 보여줄 수도 없거니와, 사용도 불편하기 때문이죠.


Stream API에서는 limit offset 과 같은 역할을 해줄 수 있는 메소드 역시 가지고 있습니다.


일단 Stream을 잘 살펴보면, limit 라는 메소드를 가지고 있습니다. 아래 코드는 Stream의 결과 중, 3개를 반환하는 메소드입니다.


1
2
3
numberList.stream().filter(a -> a % 2 == 0).distinct().limit(3).forEach(System.out::println);
 
console result : [2,4,6]
cs


재미있는 점은 limit를 사용하지 않은 결과보다, limit의 파라미터 값이 크더라도 에러를 출력하지 않습니다. 이 점은 우리가 최대 몇개를 가져온다라는 비지니스 로직을 구현할 때, 방어코드를 할 필요가 없어졌음을 말합니다.


마지막으로 skip을 이용하여, 요소를 건너뛸 수 있습니다. 예를들어 3번째 결과부터 2개 출력이라 하면 이렇게 응용을 할 수 있습니다.


1
2
3
numberList.stream().filter(a -> a % 2 == 0).distinct().skip(3).limit(2).forEach(System.out::println);
 
console result : [8,10]
cs


skip역시 입력받는 파라미터에 대해 방어코드를 할 필요가 없습니다. 조건에 만족하지 못한다면 빈 배열을 출력합니다.



오늘 포스팅에서는 Stream API를 이용한 필터 및 슬라이싱하는 방법을 알아봤습니다. 


어느정도 함수형 프로그래밍의 감이 오시나요? 


오늘 포스팅으로 부터 알 수 있는 점은 함수형 프로그래밍으로 비지니스 로직을 구현 시,


1. 간편하고, 직관적인 선언형 위주의 구현


2. 방어코드 없이, 오류가 적은 코드의 구현


이라는 장점을 볼 수 있었음을 알 수 있었습니다.




자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기


반응형

'개발이야기 > 함수형 프로그래밍' 카테고리의 다른 글

Stream API 활용편3 (검색과 매칭)  (0) 2017.01.18
Stream API 활용편2 (매핑)  (0) 2016.08.27
Stream과 Collection 차이  (0) 2016.08.04
Stream API  (0) 2016.08.03
람다 조합  (0) 2016.08.02
Posted by N'

지난 포스팅부터 "본격 선언형 프로그래밍을 할 수 있게 해준 Stream API" 에 대해 포스팅하고 있습니다. 

(모던해지는 자바에서 lambda와 더불어 선언형 프로그래밍이 어느정도 대세가 되지 않을까요? ㅡㅡ^)



오늘 포스팅에서는 "데이터 처리 연산을 지원하도록 추출된 연속된 요소 (JAVA8 IN ACTION 에서 인용)" 인 Stream 과 기존 Collection 의 차이에 대해 포스팅해보려 합니다. 


여기에서 연속된 요소라는 정의는 순차적으로 접근가능한 요소라는 점에서, Collection 과 Stream의 큰 차이는 데이터 저장 우선 vs 연산 우선 이라 볼 수 있을 것 같네요.


기존 Collection 은 데이터를 어떻게 잘 저장하고 접근할 것이냐에 초점을 맞췄습니다. 내부 요소에 대한 처리를 하기 위해서는 외부적으로  for-loop 를 통해 각각의 데이터에 접근하며, 비지니스 로직을 처리해야 합니다(외부 반복). 즉 어떤 처리를 위해서 Collection 내부에 모든 값을 가지고 있어야 합니다.


반면 Stream 의 경우 어떻게 처리를 할 것인가에 초점을 맞췄는데요. Stream 은 내부적으로 반복 과정을 숨겨 알아서 처리하고 결과를 어딘가에 출력하는 과정을 수행합니다. (내부 반복)  내부 반복을 사용하는 덕분에 우리는 컨테이너 처리에 대하여 순차적으로나 병렬적으로 처리를 간단하게 설정할 수 있게 되었어요. 아래 코드는 순차처리와 병렬 처리에 대한 간단한 코드입니다.


1
2
3
4
5
6
7
8
9
10
dataList.stream().filter((a) -> a % 7 == 0).sorted((a, b) -> b.compareTo(a)).limit(4)
        .forEach(System.out::println);        // 순차 처리
 
dataList.parallelStream().filter((a) -> a % 7 == 0).sorted((a, b) -> a.compareTo(b)).limit(4)
        .forEach(System.out::println);        // 병렬 처리
 
/*
* 위의 코드 중 병렬 처리는 의도된 결과가 나오지 않습니다. 병렬로 처리를 하기 때문에 
* parallelStream 의 사용은 "이 일이 병렬로 처리가능한가?" 를 생각해보고 사용해야합니다.
*/
cs


또한 Stream 의 처리 과정은 이론적으로 요청하는 값에 대해서만 처리를 하겠다는 핵심적인 내용이 있습니다. 이 것은 요청할 때만 처리하여, collection은 게으르게 만들겠다는 소리인데... 이 것 역시 예제로 보면 좋을 것 같아요. 


무한한 짝수를 표현해야하는 컨테이너의 문제에 대하여,


Collection은 아래과 같이 데이터를 우선적으로 만들고 있어, 소비는 할 수 없네요.


1
2
3
4
5
6
7
8
ArrayList<Integer> intList = new ArrayList<>();
for (int i = 0;; i+=2) {
    intList.add(i);
}

// 영원히 소비가 불가능합니다.
for (int i = 0, size = intList.size(); i < size; ++i){
    System.out.println(intList.get(i));
}
cs


반면 Stream 의 경우 무한한 짝수를 모두 만들기보다는 들어오는 요청에 대해서 즉시 만들어 소비하죠. (연속적인 요소가 어떻게 처리가 되는지에 대한 과정을 보기 위한 것이므로, for-loop 내부에 log 를 찍는 경우는 생각안하겠습니다.)


1
2
3
4
IntStream intList = IntStream.iterate(0, (i) -> i+2);;
intList.forEach(System.out::println);
 
// result : 0,2,4,6 ......
cs


사실 이러한 특성들은 사실 모두 비지니스 로직으로 풀어갈 수 있는 문제로 보이네요

하지만 이러한 것들을 미리 알아두는 게, 보다 더 편리하고 뻐그없겠금 사용할 수 있게 도와주지 않을까요?  




자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기

반응형

'개발이야기 > 함수형 프로그래밍' 카테고리의 다른 글

Stream API 활용편2 (매핑)  (0) 2016.08.27
Stream API 활용편1 (필터와 슬라이싱)  (0) 2016.08.26
Stream API  (0) 2016.08.03
람다 조합  (0) 2016.08.02
메서드 레퍼런스  (0) 2016.08.01
Posted by N'

JAVA8 이 기존 JAVA와 달라진 부분은 단지 람다표현식이 추가된 것만 있는 것은 아닌 것 같아요. 


람다 표현식과 더불어 같이 추가된 것은 컨테이너(Collection)를 사용하는 비지니스 로직을 보다 간결하고(쉽지는 않을듯? 배워야하기 때문이죠.ㅡㅡ^ ) 성능 좋게 만들 수 있는 Stream API의 등장은 clean code 제작에 많은 도움이 될 것같습니다.


요즘 개발하고 있는 Spring 기반의 back단 작업이나 안드로이드위에서 작성하는 비지니스 로직은 DB를 껴안고 있기 때문에 컨테이너를 자주 사용하게 되고, 당연히 루프를 돌면서 이 작업, 저 작업을 하게 됩니다.


비지니스 로직에 따라 if, break, continue 등 여러 문법 등을 사용하게 되고, 때에 따라 복잡해지는 것을 작성해줘야하지만 Stream API를 사용하면 내가 무슨일을 해야한다는 것을 어떤 규칙에 따라 선언만 해주면 되는 선언형 프로그래밍을 할 수 있습니다. (SQL 질의 처럼 말이죠. SQL 질의어를 사용하면 복잡한 key-value 기반의 테이블에서 효과적으로 데이터를 찾을 수 있죠. 찾는 로직은 몰라도 되고요.)


예를 들면 다음과 같은 코드를 볼 수 있습니다. dataList 라는 컨테이너를 이용해서 같은 로직을 작성해보겠습니다.


1
2
3
4
5
6
7
8
LinkedList<Integer> dataList = new LinkedList<Integer>() {
    {
        Random random = new Random();
        for (int i = 0; i < 100++i) {
            add(random.nextInt(100));
        }
    }
};
cs


아래는 기존 JAVA8 이전 Collection을 loop돌면서 컨테이너 내부의 가장 큰 7의 배수 4개를 찾는 것입니다. (아무리 라인수를 줄일려고 해도 저는 이정도가 한계인가 봅니다. ㅜㅡㅜ)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Collections.sort(dataList, new Comparator<Integer>() {
    @Override
    public int compare(Integer arg0, Integer arg1) {
        // TODO Auto-generated method stub
        return arg1.compareTo(arg0);
    }
});
 
for (int i = 0, count = 0, size = dataList.size(); i < size && count < 4++i) {
    if (dataList.get(i) % 7 == 0) {
        System.out.println(dataList.get(i));
        ++count;
    }
}
 
cs


다음은 Stream API를 사용한 로직입니다.


1
2
dataList.stream().filter((a) -> a % 7 == 0).sorted((a, b) -> b.compareTo(a)).limit(4)
                .forEach(System.out::println);
cs


stream 내부 메소드를 이용해 질의를 하듯이 코드를 작성하였습니다. 정말 간단해졌죠?


이와같이 Stream API를 이용하면 단순히 비지니스 처리를 선언으로 하기 때문에 쉽게 구현할 수 있으며, 각각의 블럭 (sorted나 filter) 등이 chain 형식을 지원하기 때문에 복잡한 로직에 대해 가독성과 명확성을 확실시 해줄 수 있을 것같습니다. (즉 파이프라인 구조입니다.)


또한 내부적으로는 데이터 처리과정을 병렬화할 수 있기 때문에 성능을 더 좋게 만들 수 있습니다. 

(물론 병렬을 위한 thread 처리에 대해서는 신경쓰지 않아도 됩니다.)


이와같은 Stream API의 특징을 JAVA8 IN ACTION에서는 다음과 같이 요약하고 있습니다.


1. 선언형

더 간결하고 가독성이 좋아집니다.


2. 조립할 수 있음

유연성이 좋아집니다.


3. 병렬화

성능이 좋아집니다.


그럼 여기에서, Stream의 구조에 대해서 조금 더 살펴보죠.

Stream 을 사용하는 구조는 크게 데이터 소스를 받는 것과 비지니스 로직에 대한 각종 컨디션을 파이프라인으로 구성하기 위한 중간 연산, 구성할 파이프라인을 실행할 최종 연산정도 있습니다.


위의 작성한 코드를 살펴보면 다음과 같이 나타낼 수 있네요.


1
2
3
4
dataList.stream()        // 데이터 소스 
.filter((a) -> a % 7 == 0).sorted((a, b) -> b.compareTo(a)).limit(4)  // 중간 연산
.forEach(System.out::println);    // 최종 연산
 
cs


위의 역할을 살펴보면, dataList 로 부터 데이터 소스를 받아오고 있으며, 7의 배수, 내림차순, 선착순 4개 등 chain 형식으로 각종 컨디션을 연결한 중간 연산과정이 있으며, 이러한 조건의 값들을 console에 표시하겠다는 최종 연산으로 나누어짐을 알 수 있습니다. 

(Stream API에서 제공하는 보다 더 다양한 연산은 다음에 정리하여, 포스팅하도록 하겠습니다. )


이러한 Stream API를 이용한 스트림 요소의 연산은 오직 딱 한번만 사용(소비)할 수 있습니다. 예를들어 이 것은 예외를 일으킵니다.


1
2
3
4
5
6
7
Stream<Integer> stream = dataList.stream();
 
stream.filter((a) -> a % 7 == 0).sorted((a, b) -> b.compareTo(a)).limit(4)
        .forEach(System.out::println);    
stream.filter((a) -> a % 7 == 0).sorted((a, b) -> a.compareTo(b)).limit(10)
        .forEach(System.out::println);        // IllegalStateException 예외 발생
 
cs


아래는 예외 메시지입니다.


1
2
3
4
5
6
7
8
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.<init>(Unknown Source)
    at java.util.stream.ReferencePipeline.<init>(Unknown Source)
    at java.util.stream.ReferencePipeline$StatelessOp.<init>(Unknown Source)
    at java.util.stream.ReferencePipeline$2.<init>(Unknown Source)
    at java.util.stream.ReferencePipeline.filter(Unknown Source)
    at testClass.main(testClass.java:40)
 
cs


이것으로 Stream API 에 대한 Summary 정도는 여기까지 포스팅하겠습니당. 

(나머진 필요한 것 따라 메소드를 사용해주면 되지 않을까요? 그래서 Summary 입니다. ㅡㅡ^)



자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기




반응형

'개발이야기 > 함수형 프로그래밍' 카테고리의 다른 글

Stream API 활용편1 (필터와 슬라이싱)  (0) 2016.08.26
Stream과 Collection 차이  (0) 2016.08.04
람다 조합  (0) 2016.08.02
메서드 레퍼런스  (0) 2016.08.01
람다의 실제 형식  (0) 2016.07.28
Posted by N'