JAVA8 을 공부하는 있는 책인 [JAVA8 in Action] 에서 언급되었던 내용 중 한 가지는 "공짜로 병렬성을 얻을 수 있다"는 것이었습니다. Collection 군의 데이터를 새로 추가된 API 인 Stream 의 형태를 parallel 관련 keyword 를 사용하여, 선언형 프로그래밍을 작성하면 병렬처리가 된다는 것이었죠.


아래는 병렬처리를 수행하는 간단한 예제입니다.


1
2
3
4
5
6
7
8
9
10
// Collection 클래스 군의 parallelStream 사용.
List<Integer> boxedDataList = IntStream.of(dataSet).mapToObj(Integer::new).collect(Collectors.toList());
boxedDataList.parallelStream().reduce(Integer::sum).ifPresent(System.out::println);
        
// 순차스트림 (IntStream) 을 병렬스트림으로 변
IntStream.of(dataSet).boxed().parallel().reduce(Integer::sum).ifPresent(System.out::println);
 
// 출력 결과
// 499500
// 499500
cs


위의 예제를 보면 정말 간단하다는 것을 알 수 있습니다. (Too Simple!!)

병렬 keyword 를 제외한 중간, 최종 연산 방법이 생각이 안나면, 이 곳을 참고하세요. 



간단히 리뷰를 해보면, 


1번 선언에서는 일반적으로 Collection 에서 스트림을 구하는 것 대신에 parallelStream 키워드를 통해 병렬스트림으로 변경한 후, 처리를 하고 있습니다. 


2번 선언에서는 이미 순차스트림인 상태를 parallel 중간연산을 사용하여 병렬스트림으로 변경시켰습니다.


단순히 병렬스트림으로 변경하는 것만으로 병렬처리를 할 수 있으니, 책에서 소개한 것과 같이 공짜로 병렬성을 얻었다는 말이 뻥은 아니라는 것을 알 수 있습니다. ㅡㅡ^


즉 병렬처리를 하기 위해 생각해봐야할 고민인 

사용할 스레드 개수경쟁상태(race condition), 계산된 결과들의 동기화 등이 추상화되었습니다.


 

parallel 관련 키워드 메소드를 사용하여 순차스트림을 병렬스트림으로 변경했들이,

병렬스트림을 순차스트림으로 변경할 수도 있습니다. sequencial 키워드로 말이죠. :-) 


하지만, 이러한 중간연산(parallel 과 sequencial)을 아래와 같이 특정 중간연산의 제어를 하겠다는 목적으로 여러번 사용하는 것은 부질 없습니다. ㅡㅡ^


1
2
3
4
5
6
IntStream.of(dataSet).boxed()
            .parallel()
            .filter(n -> n % 2 == 0)
            .map(n -> n + 2)
            .sequential()
            .collect(Collectors.reducing(Integer::sum)).ifPresent(System.out::println);
cs


최종적으로 선택된 sequential 만 적용되며, 위 연산은 순차상태로 계산됩니다.


하지만, 이러한 병렬처리가 무조건 성능을 끌어다 줄까요?


이 이야기는 다음 포스팅에서...




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



반응형
Posted by N'


JAVA8 의 등장하면서 생긴 가장 큰 변화 중 하나는 Stream API 를 통한 함수형 프로그래밍 패러다임을 사용할 수 있게 된 것입니다. 비지니스 로직 중 대부분을 SQL 과 같은 질의로 처리할 수 있게 되었기 때문에 활용을 잘한다면 성능과 간결함이라는 토끼를 모두 잡을 수 있게 된 것이죠. 


지난 포스팅까지 살펴본 Stream API 는 파이프라인식으로 여러 메소드를 연결하여 원하는 결과를 질의하는 형태였는데요. 이러한 파이프라인에 해당하는 각 메소드는 데이터를 필터하거나 타입을 변경하는 중간연산(filter, skip, map 등)과 결과를 원하는 형태로 반환하는 최종연산(foreach, collect, reduce)으로 분류할 수 있을 것 같습니다.


오늘은 그 중 최종 연산을 원하는 형태로 질의할 수 있는 방법중 하나인 collect 집중적으로 알아보려 합니다. 

(중간 연산에 대한 내용은 아래에서 참고하실 수 있습니다.)




Stream API를 포스팅 했던 대부분 예제에서는 collect 를 다음과 같이 중간연산의 결과를 List 형태로 출력하는 방법을 많이 사용했었습니다.


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
class Menu {
    private Integer price;
    private String name;
    
    public Menu (String name, int price) {
        this.name = name;
        this.price = price;
    }
 
    public String getName() {
        return name;
    }
    
    public Integer getPrice() {
        return price;
    }
}
 
final List<Menu> menuList = Arrays.asList(
                new Menu("고기"1200)
                , new Menu("랍스타"7600)
                , new Menu("피자"3100));
        
List<Menu> meatMenuList = menuList.stream().
            filter(menu -> menu.getName().equals("고기")).
            collect(Collectors.toList());
cs


collect 부분의 메소드를 조금 zoom-in 해서 보면, Collector 클래스를 파라미터로 받겠금 되어있는 것을 볼 수 있는데 오늘의 메인은 바로 이 Collector 입니다. :-)




1. Collector 란?


Collector 는 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public interface Collector<T, A, R> {
    /**
     * 새로운 결과 컨테이너를 만들기 위한 메소드
     * <pre>
     *         처음에는 빈 값이 들어가며, 연산 과정을 통해 채워진 후 결과를 출력합니다.
     * </pre>
     * 
     * @return
     */
    Supplier<A> supplier();
    
    /**
     * 컨테이너에 요소를 추가하는 메소드
     * <pre>
     *         공급자 (Supplier) 를 통해 출력해야할 컨테이너에 데이터를 추가합니다.  
     * </pre>
     * 
     * @return
     */
    BiConsumer<A, T> accumulator();
    
    /**
     * 병렬 처리(parallelStream) 시, 두 컨테이너를 병합합니다.
     * 
     * @return
     */
    BinaryOperator<A> combiner();
    
    /**
     * 최종 변환값을 결과 컨테이너 적용합니다.
     * 
     * <pre>
     *         누적된 결과 컨테이너와 최종형태가 같다면, 항등함수( f(x) = x ) 를 내보냅니다.
     * </pre>
     * 
     * @return
     */
    Function<A, R> finisher();
    
    /**
     * 컬렉터 연산 시, 방법에 대한 전략 목록을 정의합니다.
     * 
     * <pre>
     *         UNORDERED : 리듀싱 결과는 방문 순서나 누적 순서에 영향을 받지 않는다.
     *         CONCURRENT : 누적 컨테이너에 요소를 추가하는 accumulator 메소드를 동시에 호출할 수 있다.
     *         IDENTITY_FINISH : finisher 메소드가 항등함수를 내보내야하는 상황이라면, 해당 메소드를 생략하고 누적객체를 바로 사용한다.
     * </pre>
     * 
     * @return
     */
    Set<Characteristics> characteristics();
}
cs


구현된 Collector 는 위의 메소드를 일련의 논리적 순서를 수행하며 원하는 결과를 도출할 수 있습니다. 


즉 collect 를 사용하여 최종결과를 얻기 위해서는 위의 interface 를 사용자가 구현하거나, Collectors 클래스 내부의 toList() 와 같이 이미 구현된 클래스를 사용하는 방법이 있습니다.


Collectors 는 구현화된 전략 Collector 를 출력하는 팩토리로, Stream API 는 이 전략 Collector 를 바탕으로 최종연산을 할 수 있습니다.


Collectors 내부에 정의된 최종연산을 수행하는 방법이 크게 다음과 같은 세 가지 (Reducing, Grouping, Partitioning) 정도의 목적을 가지고 있다고 할 수 있습니다.



2. Reducing 


Stream API 와 관련된 포스팅 중 map 과 reduce  연산을 통하여 요약된 결과를 출력하는 map-reduce  패턴에 대해 언급을 한 적이 있습니다. map 과 reduce 를 통해 Collection 의 원하는 부분만을 원하는 결과로 출력할 수 있다는 것은 매우 매력적인 일이라 할 수 있습니다. (해당 내용은 아래 포스트에서 확인할 수 있습니다.)



Collectors 클래스에서도 이러한 요약연산을 할 수 있는 reducing 메소드를 제공하며, 이 외에도 많이 사용될 법한 통계용 전략 Collector 역시 볼 수 있습니다.


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
final List<Menu> menuList = Arrays.asList(
                new Menu("고기"1200)
                , new Menu("랍스타"7600)
                , new Menu("피자"3100));
                
// 총 합
menuList.stream().collect(Collectors.summingInt(Menu::getPrice));
        
// 사용자 정의 총 합
menuList.stream().collect(Collectors.reducing(0, Menu::getPrice, (a, b) -> a+b));
        
// 사용자 정의 총 합 Optional
menuList.stream().map(Menu::getPrice).collect(Collectors.reducing((a, b) -> a+b)).ifPresent(System.out::println);
        
// 연산 통계
IntSummaryStatistics statics = menuList
                                .stream()
                                .collect(Collectors.summarizingInt(Menu::getPrice));
 
System.out.println("Max : " + statics.getMax());
System.out.println("Min : " + statics.getMin());
System.out.println("sum : " + statics.getSum());
System.out.println("Average : " + statics.getAverage());
System.out.println("count : " + statics.getCount());
 
// 출력물 
// 11900
// Max : 7600
// Min : 1200
// sum : 11900
// Average : 3966.6666666666665
// count : 3
cs


Reducing 을 하는 방법은 위와 같이 열려 있으며, 비지니스 로직 구현 시 알맞은 상황따라 구현하면 될 것 같네요.



3. Grouping


SQL 로 쿼리를 만들다보면 특정 필드따라 그룹을 만들고 싶은 경우가 있으며 GROUP BY 키워드를 사용하여 다음과 같이 grouping 된 결과를 얻을 수 있습니다.


1
2
3
4
5
6
7
SELECT
    company_subject_sn
    , COUNT(member_subject_sn)
FROM
    MEMBER
GROUP BY
    company_subject_sn
cs


Collectors 의 groupingBy 메소드를 사용하면, Collection 객체에 대하여 특정조건으로 분류시키는 작업을 간단하게 만들 수 있습니다.


예를들어 아래의 조직구성원 타입의 Collection 을 회사별, 조직별로 묶는 Map 을 제작은 아래와 같습니다.


비지니스 로직을 처리하기 위한 작업 중 아래와 같이 여러 key 값을 이용하여 Map 을 구성해야할 경우가 다수 존재합니다. 알고리즘 자체는 어려운 것은 아니지만 매번 저렇게 작성을 해야한다는 것은 안타까운 일입니다.


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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
 * 조직 정보를 담은 객체
 * 
 * @author Doohyun
 *
 */
class OrganizationMember {
    private Integer companySubjectSn;    // 회사 순번 
    private Integer memberSubjectSn;    // 구성원 순번
    private Integer organizationSn;        // 조직 순번  
    private String name;                // 이름
    
    public OrganizationMember(Integer companySubjectSn, Integer memberSubjectSn, Integer organizationSn, String name) {
        this.companySubjectSn = companySubjectSn;
        this.memberSubjectSn = memberSubjectSn;
        this.organizationSn = organizationSn;
        this.name = name;
    }
    
    public Integer getCompanySubjectSn() {
        return companySubjectSn;
    }
    public Integer getMemberSubjectSn() {
        return memberSubjectSn;
    }
    public Integer getOrganizationSn() {
        return organizationSn;
    }
    public String getName() {
        return name;
    }
}
 
List<OrganizationMember> testList = Arrays.asList(
                new OrganizationMember(511"남두현")
                , new OrganizationMember(622"윤석진")
                , new OrganizationMember(633"성지윤")
                , new OrganizationMember(744"백선기")
                , new OrganizationMember(754"황후순")
                , new OrganizationMember(561"이현우")
                , new OrganizationMember(572"태재영"));
        
HashMap<Integer, Map<Integer, List<String>>> groupingMap = new HashMap<>();
        
for (OrganizationMember organizationMember : testList) {
    final Integer companySubjectSn = organizationMember.getCompanySubjectSn();
    final Integer orgSn = organizationMember.getOrganizationSn();
            
    if (!groupingMap.containsKey(companySubjectSn)) {
        // 회사순번으로 분류
        groupingMap.put(companySubjectSn, new HashMap<>());
    }
            
    final Map<Integer, List<String>> orgMap = groupingMap.get(companySubjectSn);
    if (!orgMap.containsKey(orgSn)) {
        // 조직순번으로 분류
        orgMap.put(orgSn, new LinkedList<>());
    }
            
    // 조직순번으로 분류
    orgMap.get(orgSn).add(organizationMember.getName());
}
        
System.out.println(groupingMap.toString());
 
// 출력
// {5={1=[남두현, 이현우], 2=[태재영]}, 6={2=[윤석진], 3=[성지윤]}, 7={4=[백선기, 황후순]}}
 
cs



Collectors 팩토리에서 제공하는 groupingBy 를 사용하면 위의 문제를 보다 쉽고 가독성있게 구현할 수 있습니다. 아래 예제는 위의 기능을 Collectors.groupingBy() 를 사용하여 간결하게 작성한 내용입니다.


1
2
3
4
5
6
7
8
9
10
11
Map<Integer, Map<Integer, List<String>>> groupinParallelMap = 
                    testList
                    .parallelStream()
                    .collect(
                        Collectors.groupingBy(OrganizationMember::getCompanySubjectSn
                        , Collectors.groupingBy(OrganizationMember::getOrganizationSn, Collectors.mapping(OrganizationMember::getName, Collectors.toList()))));
        
System.out.println(groupinParallelMap.toString());
 
// 출력
// {5={1=[남두현, 이현우], 2=[태재영]}, 6={2=[윤석진], 3=[성지윤]}, 7={4=[백선기, 황후순]}}
cs


파이프라인이 조금 복잡해보이지만,  groupingBy 에서 회사조직 순번순으로 분류작업을 하였으며Stream API map 비슷한 역할을 하는 mapping  메소드를 통해 조직객체를 이름으로 변경을 했습니다.



4. Partitioning


앞서 설명한 Collectors.groupingBy() 과 비슷하게, Stream 내의 객체들을 특정 Predicate 로 분류하여 그룹화할 수 있는 기능 또한 존재합니다. Collectors.partitioningBy() 을 통해 사용할 수 있습니다.


Predicate 로 분류작업을 수행하기 때문에 반환되는 key 의 값은 Boolean(참, 거짓) 입니다. 그러므로, 결과 Map 은 최대 2 개의 그룹이 존재한다고 봐도 될 것 같습니다.


아래 예제는 회사순번이 5인 그룹과 아닌 그룹으로 분류, 또한 세부 그룹을 조직순번으로 분류한 작업입니다.


1
2
3
4
5
6
7
8
9
10
Map<Boolean, Map<Integer, List<String>>> groupinParallelMap = 
                    testList
                        .parallelStream()
                        .collect(
                            Collectors.partitioningBy((OrganizationMember v) -> v.getCompanySubjectSn() == 5
                            , Collectors.groupingBy(OrganizationMember::getOrganizationSn
                                    , Collectors.mapping(OrganizationMember::getName, Collectors.toList()))));
 
// 출력
// {false={2=[윤석진], 3=[성지윤], 4=[백선기, 황후순]}, true={1=[남두현, 이현우], 2=[태재영]}}
cs


앞의 예제와 크게 변하지 않고 단순히 파이프라인 한 개만 변할 것을 알 수 있습니다. (이게 함수형 프로그래밍의 장점이죠!!)


또한 이런 분류작업을 병렬로 하고 있음(parallelStream) 을 알 수 있습니다.



오늘 포스팅으로 Stream 에서 진행되는 중간연산과 최종연산을 수행하는 방법을 간략하게는 훑어보게 된 것 같습니다. 이러한 결과로 컨테이너 객체를 이용한 비지니스 로직이 한 결 더 유연하고 간략하며, 최적화까지 할 수 있는 여지가 생겼습니다. 


하지만 잘 사용해야 가능한 이야기겠죠? :-)



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






반응형
Posted by N'

해당 포스팅에서 언급된 내용은 Ndroid 에서 제공합니다.

https://github.com/skaengus2012/Ndroid


안녕하세요. 연휴는 잘 보내고 계신가요?


저는 4일간 연휴 중 조금 마음을 refresh 하고자, 하루는!! 

기존 작성하였던 코드를 리뷰해보고, 리팩토링을 하는 시간을 가지게 되었어요. 


그래서 오늘 포스팅은 이에 대한 결과물로 조금 더 구체적인 안드로이드 개발의 패턴화에 대해 생각해보는 포스팅을 해보려 합니다. (그냥 일개 초보수준 프로그래머가 작성한 것입니다. ㅡㅡ^)


일단 리팩토링 한다고 해서 아무렇게나 작성을 해보기 보단 지난 포스팅 때 다뤘던 두 가지 사례를 적극적으로 사용해보는 시간을 가졌습니다. 아래의 두 포스팅을 말이죠. :-)



작업을 했던 내용은 다음과 같습니다.


1. Activity에 조금 더 최적화된 EasyWeakReference


코드를 작성하다보면 종종 어떤 일들은 안드로이드의 메인스레드에서 실행되어야할 경우가 많이 존재합니다. 특히 병렬처리를 하려 한다면, 멀티스레드 작업의 결과를 View 에 반영해야하는데 이 작업은 꼭 메인스레드에서 이루어 져야 하는 작업이죠!!


안드로이드에서 이에 접근할 수 있는 방법은 여러 종류가 있지만 저는 그중에서 runOnUiThread 를 주로 애용하는 편입니다. 


이 개념을 저는 일전에 제작한 NxWeakReference 에 추가하고 싶었으며(보다 더 쉽게 일하기 위해 ㅡㅡ^), 이를 위해 저는 해당 클래스의 확장체를 만들게 되었습니다.


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
/**
 * Activity 전용 약한참조 객체
 *
 * Created by Doohyun on 2017. 1. 28..
 */
 
public class NxActivityWeakReference<extends Activity> extends NxWeakReference<T> {
    /**
     * 단순한 상속에 대한 생성자 제작
     * @param referent
     */
    public NxActivityWeakReference(T referent) {
        super(referent);
    }
 
    /**
     * 단순한 상속에 대한 생성자 제작
     *
     * @param referent
     * @param q
     */
    public NxActivityWeakReference(T referent, ReferenceQueue<T> q) {
        super(referent, q);
    }
 
    /**
     * Activity 에서 runOnUiThread 로 실행함
     *
     * @param weakReferenceConsumer
     */
    public void runOnUiThread(IWeakReferenceConsumer<T> weakReferenceConsumer) {
        final T activity = this.get();
 
        if (activity != null) {
            activity.runOnUiThread(() -> weakReferenceConsumer.accept(activity));
        }
    }
}
cs


간략하게 리뷰를 하자면, 생성자에서 Activity 를 상속받은 인스턴스를 강제로 입력하도록 제작하였으며, runOnUiThread 메소드를 통해 보다 더 쉽게 mainThread 에서 비지니스로직을 수행할 수 있습니다.



2. 안드로이드 MVP 규칙 정의


일전에 한번 JANDI 블로그(http://tosslab.github.io/android/2015/03/01/01.Android-mvc-mvvm-mvp.html)를 통해 protoType 화 했던 MVP 패턴에 대한 규칙을 조금 더 명세하였습니다. 규칙은 다음과 같습니다.


- View 에 해당하는 Activity는 오직 View 자체 변경 메소드만 작성할 것!


View 는 버튼 클릭 등에 의한 이벤트로 부터 해야할 비지니스 로직을 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
25
26
27
28
29
30
31
32
33
public class LoginActivity extends MitActivity {
 
    // 하위 View
    private RelativeLayout ryDialogBg;
    private LoginLoadingDialog loginLoadingDialog;
    private Button btnLogin;
 
    // Presenter
    private LoginPresenter loginPresenter;
    
    .....
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        loginPresenter = new LoginPresenter(this);
       
        // 이벤트에 대한 비지니스 로직을 Presenter 로 위임! 
        btnLogin.setOnClickListener(loginPresenter::clickLoginButton);
    }
 
    /**     
     * View 자체의 변경 메소드
     *
     * 키보드 숨기기
     */    
    public void showLoginLoadingDialog() {
        loginLoadingDialog.show();
        ryDialogBg.setVisibility(View.VISIBLE);
    }
 
}
cs


- Presenter 는 View 와 Model 의 연결고리, 즉 기존의 Controller 


Presenter 는 View 로 부터의 요청 시, Modeler 로 명령을 위임합니다. 


또한 Modeler 로부터  받은 정보를 View 로 반영하기 위해, View 에 정의된 메소드를 직접 실행합니다. 이를 위해 Presenter 는 View 에 대한 약한참조를 가지고 있습니다.


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
public class LoginPresenter implements ILoginServiceObserver {
 
    private NxActivityWeakReference <LoginActivity> loginActivityEasyWeakReference;
 
    public LoginPresenter(LoginActivity loginActivity) {
  // Activity 와 약한참조 유지
        loginActivityEasyWeakReference = new NxActivityWeakReference <>(loginActivity);
    }
 
    /**
     * 로그인 시도
     * 
     * <pre>
     *  View 인 Activity에서 Click Event로 할 일을 위임한 메소드
     * </pre>
     * @param view
     */
    public void clickLoginButton(View view) {
       final UserInfo userInfo = getCurrentInputUserInfo();
 
       if (userInfo != null) {
          // View 변경 메소드 사용
          loginActivityMidasWeakReference.run(LoginActivity::showLoginLoadingDialog);
          // Modeler 로 실제 비지니스 로직 위임.
          LoginModeler.GetInstance().tryLogin(userInfo);  
       }
    }
}
cs


Presenter 는 다른 컴포넌트의 옵저버 역할을 수행합니다. 다른 컴포넌트의 알람의 결과(멀티스레드의 콜백결과 등..)로 View 가 변경되야 할 때, 이를 Presenter 로 전달을 하여 View 를 변경시킬 수 있도록 합니다.


- Model 을 담당하는 Modeler 개념 추가 


Modeler 는 스프링에 있던 Service 와 같은 역할을 수행합니다. (안드로이드에는 서비스가 있으니 서비스라는 이름은 차마 사용할 수 없었습니다. ㅜㅡㅜ)


Presenter 의 요청으로 내부DB 의 CRUD 를 수행하는 DAO 클래스를 사용하거나, network 작업을 수행합니다. 물론 요청이 콜백이었다면, 데이터를 전달하는 역할도 수행하겠죠?


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
public class LoginModeler {
    /**
     * 로그인을 시도한다
     *
     * <pre>
     *     userInfo 가 넘어오는 것에 성공했다면 정확한 정보를 보장함!
     *     유효성 검사 필요 없음
     * </pre>
     *
     * @param userInfo
     */
    public void tryLogin(@NonNull UserInfo userInfo) {
        // 실제 비지니스 로직 수행 (네트워크 접근)
        BindServiceManager.GetInstance().runRMI(iMessengerBinder -> iMessengerBinder.loginProcess(userInfo.getUserId(), userInfo.getPwd()));
    }
 
    /**
     * 유저 정보를 저장한다.
     *
     * <pre>
     *     userInfo 는 null 이 아닌 한 정확한 정보를 보장함!
     *     유효성 검사 필요 없음
     * </pre>
     * @param userInfo (null 허용)
     * @return
     */
    public boolean saveUserInfo(@Nullable UserInfo userInfo) {
        // 실제 비지니스 로직 수행 (DB CRUD)
        final boolean resultYn;
 
        if (userInfo != null) {
            resultYn = TxManager.GetInstance().runTransaction(() -> {
                UserInfoServiceManager.GetInstance().insert(userInfo.getUserId(), userInfo.getPwd());
                SettingServiceManager.GetInstance().insert();
            });
        } else {
            resultYn = false;
        }
 
        return resultYn;
    }
}
cs



계속해서 이 패턴화 를 견고하게 작업해 볼 생각입니다. 


보다 유연하고 질 좋은 코드를 꿈꾸며 말이죠. :-)


이상 설연휴 중 삽질이었습니다. ㅡㅡ^ 



반응형
Posted by N'

해당 포스팅에서 언급된 내용은 Ndroid 에서 제공합니다.

https://github.com/skaengus2012/Ndroid


안드로이드 개발을 진행하다보면 Activity 의 생명주기와 스레드 사용지점이 맞지 않아 누수가 되곤 합니다. 그러한 이유로 WeakReference 를 사용하란 이야기가 많이 나오곤 합니다.


보통 Activity 인스턴스를 약한참조 관계로 가지고 있습니다. 그리고 해당 인스턴스의 메소드를 사용하기 위해 우린 객체를 꺼내서 다음과 같이 사용합니다.


1
2
3
4
5
6
MyActivity activity = easyWeakReference.get();
        
if (activity != null) {
    // 메모리가 해제되어 더이상 존재하지 않는 객체인지 확인할 필요 존재!
    activity.func1();
}
cs


심플하게 사용할 수 있지만, 


프로그램의 덩치가 커지고 WeakReference 를 사용할 일이 많아진다면,


별 거 아닌 것 같은 저 null check 는 매우 귀찮고 중복작업일 수 있습니다.


이 문제는 아래의 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
public class NxWeakReference<T> extends WeakReference<T> {
 
    /**
     * 단순한 상속에 대한 생성자 제작
     * @param referent
     */
    public NxWeakReference(T referent) {
        super(referent);
    }
 
    /**
     * 단순한 상속에 대한 생성자 제작
     * @param referent
     * @param q
     */
    public NxWeakReference(T referent, ReferenceQueue<super T> q) {
        super(referent, q);
    }
 
    /**
     * 약한 참조 속 실제 객체의 사용
     * @param weakReferenceConsumer
     */
    public void run(IWeakReferenceConsumer<T> weakReferenceConsumer) {
        T t = this.get();
 
        if (t != null) {
            weakReferenceConsumer.accept(t);
        }
    }
 
    /**
     * 약한 참조 속 실제 객체의 사용
     *
     * <pre>
     *     call back 의 개념 사용
     * </pre>
     *
     * @param weakReferenceFunction
     */
    public Object call(IWeakReferenceFunction<T> weakReferenceFunction) {
        Object result = null;
 
        T t = this.get();
 
        if (t != null) {
            result = weakReferenceFunction.accept(t);
        }
 
        return result;
    }
 
    /**
     * 약한 참조 속 실제 객체의 사용
     *
     * <pre>
     *      call back 개념 사용
     *      defaultValue 사용
     * </pre>
     *
     * @param weakReferenceFunction
     * @param defaultValue
     * @return
     */
    public Object call(IWeakReferenceFunction<T> weakReferenceFunction, Object defaultValue) {
        final Object result = call(weakReferenceFunction);
 
        if (result == null) {
            return defaultValue;
        } else {
            return result;
        }
    }
 
    /**
     * WeakReference 의 내부객체를 사용하기 위한 Consumer 인터페이스
     * @param <T>
     */
    @FunctionalInterface
    public interface IWeakReferenceConsumer<T> {
        void accept(T t);
    }
 
    /**
     * WeakReference 의 내부객체를 사용하기 위한 Function 인터페이스
     * @param <T>
     */
    @FunctionalInterface
    public interface IWeakReferenceFunction<T> {
        Object accept(T t);
    }
}
 
cs


상속 후 새로 제작된 accept 는 위의 null 체크를 수행하도록 하며, 실제 비지니스 로직은 함수형 인터페이스로 동작파라미터화 시켰습니다. 


동작 파라미터화가 무엇인지 궁금하다면, 이곳으로 이동!!



이제 비지니스 로직은 다음과 같이 단순화하여 개발할 수 있습니다.


1
easyWeakReference.run(MyActivity::func1);
cs

함수형 프로그래밍을 조금만 알면 쉽게 작업은 단순화시킬 수 있습니다. 




반응형
Posted by N'

현재 공부하고 있는 도서의 Stream API 에 대한 자세한 설명이 나와 있어서, 같은 주제로 계속 블로깅을 하고 있습니다. 


하지만 대략적인 부분이 끝이 난 것 같습니다. Stream API 소개는 이번 포스팅이 마지막입니다.

지난 Stream API 가 궁금하다면, 하단 글들을 참고해주세요. :-)



오늘 포스팅은 보통 전공 과목에서 흔히 말하는 기타 기능에 대해 알아보려고 합니다. 그러나 유용할 수 있죠? :-)


1. 숫자형 스트림으로 변환


람다를 포스팅할 때도 있었지만, 스트림 역시 원시타입의 스트림을 지원합니다. 보다 더 정확히 말하면, 숫자들에 대한 스트림을 지원하며 이 기본형 특화 스트림들은 sum, max, min 등 자주 사용하는 리듀싱 메소드를 제공해줍니다.


숫자 스트림을 사용하기 위해서는 map 의 파생 메소드인 mapToInt, mapToDouble, mapToLong 등의 메소드를 사용해야 합니다.


예제부터 한번 봐볼까요?


1
2
3
4
5
6
7
8
9
List<Integer> numberList = Arrays.asList(1,2,3,4,5,6);
    
System.out.println(
        numberList.
            stream().
            mapToInt(Integer::intValue).
            sum());
 
// 출력 21
cs


간단한 Integer Collection 객체의 stream 에서 int value 의 stream 얻기 위해 mapToInt 메소드를 사용하였으며(언박싱), 리듀스 메소드 중 하나인 sum 을 사용하였습니다.


추가적으로 또다른 리듀싱 메소드인 max나 min 은 Optional 상태로 데이터가 제공됩니다. 컬렉션이 비어있는 상태에서 max 나 min 의 값이 무조건 존재한다는 것을 보장할 수 없기 때문이죠.



2. 숫자 범위 스트림


JAVA8 에서는 숫자형스트림에서 특정 범위안의 숫자 집합을 출력해주는 정적메소드인 rangerangeClosed 를 제공해줍니다. 두 메소드 모두 범위에 대한 인수를 받으며, 차이는 시작값과 종료값이 결과에 포함하는 여부입니다. (range 의 경우 결과에 포함되지 않습니다.)


사용예제는 다음과 같습니다. 아래 예제는 숫자 Stream 을 이용하여, 피타고라스의 수를 구하는 쿼리입니다.


1
2
3
4
5
6
7
IntStream.rangeClosed(1,100).
        boxed().
        flatMap(a -> IntStream.
                        rangeClosed(a, 100).
                        mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a *+ b * b)})).
        filter(v -> v[2]%1 == 0).
        forEach(v -> System.out.println(v[0+ " " + v[1+ " " + v[2]));
cs


flatMap 을 통해, 두 개의 숫자 스트림을 엮었습니다. 첫번째 범위에서는 Stream 연산의 결과를 객체로 받기 위해 boxed 메소드를 사용하였습니다. boxed 객체를 사용하지 않으면, 오직 중간연산 결과로 숫자타입의 스트림밖에 생산할 수 없습니다. (왜냐하면 IntStream 은 숫자 스트림이기 때문이죠. ㅡㅡ^)



3. 스트림 만들기


컬렉션이나 배열은 다음과 같이 임의의 값으로 데이터를 만들 수 있습니다.


1
2
String[] dataArray = new String[]{"남두현""윤석진""성지윤""오진명"}; 
List<String> dataList = Arrays.asList(dataArray);
cs


스트림 역시 임의의 값으로 스트림 만들기가 가능합니다. 꼭 컬렉션 클래스의 stream 키워드를 쓸 필요는 없는 것이죠. ㅡㅡ^


1
2
3
4
String[] dataArray = new String[]{"남두현""윤석진""성지윤""오진명"}; 
        
Stream<String> ofStream = Stream.of("남두현""윤석진""성지윤""오진명");
Stream<String> arrayStream = Arrays.stream(dataArray);
cs



4. 고정되지 않은 크기의 스트림 만들기


여태까지 Stream은 고정된 크기의 컬렉션을 통해서 만들곤 했습니다. 그러나 특정 Function, Supplier 타입의 메소드 레퍼런스 (람다 포함) 를 통해 무한한 Stream 을 만들 수 있습니다.


1
2
3
4
5
// iterate 는 Function type 람다를 사용 
Stream.iterate(0, n -> n + 2).forEach(System.out::println);
        
// generate 는 Supplier type 메소드 레퍼런스 사용
Stream.generate(Math::random).forEach(System.out::println);    
cs


만들 수 있는 key-word 메소드는 iterate 와 generate 로 보통 limit 로 제한을 두어 사용하곤 합니다. 제한을 두지 않은 스트림을 언바운드 스트림(Unbounded Stream) 이라 하며, 특정 로직에서 무한한 결과 때문에 제대로된 결과를 얻을 수 없을 지도 모릅니다. 

(그러나 위 예제 에서는 무한한 숫자가 출력이 될 것입니다. 컬렉션과 달리 Stream 은 즉시 생산을 하는 것이 특징이기 때문이죠. ㅡㅡ^)


것으로 Stream API 활용과 관련된 주제는 끝났습니다.


다음 포스팅 부터는 Stream 을 통한 데이터 수집과 관련된 주제로 진행을 하게 될 것 같습니다. 

(스포일러를 하자면, 언제나 최종 연산을 구할 때 사용했던 Collector 와 관련된 이야기 입니다. ^^)



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


반응형
Posted by N'

 지금까지는 Stream API 의 최종 연산에서 여러 종류의 값 (Boolean, filter 된 리스트, void 연산) 을 반환을 함을 알 수 있었습니다. 여러 중간 연산을 pipe 처럼 묶어서 사용하고, 그 결과를 취함으로 여러 로직의 이점을 많이 챙길 수 있었습니다.


오늘의 포스팅 주제는 조금 더 복잡한 질의를 표현할 수 있는 리듀싱(reducing)에 대해 알아보려고 합니다. 예를 들어, 컬렉션 내부에 대해 총합을 구한다거나 최대값, 최소값을 구하는 연산이 대표적이라고 할 수 있습니다. ㅡㅡ^


 간단한 예제 부터 한번 봐보도록 할까요? 아래 코드는 컬렉션 내부의 값 중, 짝수 만을 덧셈하는 연산입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
List<Integer> numberList = Arrays.asList(5103692);
        
int sum = 0;
        
for (Integer number : numberList) {
    if (number % 2 == 0) {
        sum += number;
    }
}
 
System.out.println("총 합 : " + sum);
 
// 총 합 : 108
cs


 이 간단한 코드는 Stream 연산을 통해 더욱 간단해질 수 있습니다. 약간의 중간연산과 리듀싱을 통해 처리할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
List<Integer> numberList = Arrays.asList(5103692);
 
System.out.println("총 합 : " + 
            numberList.
                stream().
                filter(n -> n % 2 == 0).
                reduce(0, (a, b) -> a + b)
            );
 
//총 합 : 108
cs


 filter 를 통해 짝수만 추출했으며, reduce 를 이용해 덧셈의 결과를 구했습니다.


위의 예제에서 사용한 reduce 는 초기 값을 0 을 했음을 알 수 있습니다. 물론, 초기값이 없는 연산을 수행할 수 있습니다. 이 때 결과는 Optional 객체로 나타나게 되며 해당 객체를 이용해 값이 있을 때만 처리하는 기능 등을 수행할 수 있습니다.


리듀싱의 응용은 다양하게 할 수 있습니다. 예를들어 특정 객체에 대한 컬렉션 중, 한 필드에 대한 합은 다음과 같이 구할 수 있습니다.


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
/**
 * Test 를 위한  VO
 * 
 * @author namduhyeon
 *
 */
class TestVo {
    private String name;
    private Integer value;
    
    public TestVo (String name, Integer value) {
        this.name = name;
        this.value = value;
    }
 
    public String getName() {
        return name;
    }
 
    public Integer getValue() {
        return value;
    }
}
 
List<TestVo> numberList = Arrays.asList(
                new TestVo("Doohyun Nam",7)
                , new TestVo("Ford Mill",9)
                , new TestVo("Hwang",2));
        
numberList.stream().
    map(v -> v.getValue()).
    reduce((v1, v2) -> v1 + v2).
    ifPresent(System.out::println);
 
// 18
cs


특정 필드를 추출하기 위해 map 을 사용하였으며, 이 Stream 을 통해 합을 구할 수 있었습니다. 


이러한 map 과 reduce 를 이용하여, 데이터를 가공하는 패턴을 map-reduce 패턴이라 하며 웹 검색 엔진 등에서 많이 사용하는 것을 볼 수 있습니다.


그 이유는 언제나 Stream API 에서 쉽게 볼 수 있는 병렬성이라는 특징 덕분입니다. 예를 들어 특정 컬렉션의 합을 병렬적으로 구하기 위해서는 임계영역간 스레드의 경쟁 비용이 발생하기 마련이지만, 리듀스 내부의 내부반복은 개발자에게는 추상화되어 제공됩니다. (분할하여 결과를 구하고 머징하는 과정이 아닐지...) 


하지만, 모든 기능이 만병통치약일 수는 없습니다. ㅡㅡ^ 

병렬스트림으로 리듀싱 결과를 일반 스트림과 동일하게 얻기 위해서는 연산이 어떤 순서로 연산되어도 결과가 보장되는 구조이며, 람다 내부 인스턴스는 변경되어서는 안되는 구조이어야 합니다. 즉 각 트랜잭션 중 병렬구조로 처리가 가능한 부분만 사용이 가능함을 알 수 있습니다.


복잡한 데이터의 질의를 쉽게 표현할 수 있는 리듀싱에 대해 알아보았습니다. 생각보다 자주 접해보면, 금방 눈에 들어오지 않을까 생각이 듭니다. 


점점 함수형 프로그래밍의 장점이 눈에 들어오지 않으시나요? :-) 


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




반응형
Posted by N'

안드로이드 스튜디오로 개발 시, 아래 주소의 설명과 같이 console 에서 ADB 를 사용할 수 있습니다.


http://technote.kr/83


특수한 상황 (DOZE) 모드를 만든다던지, 할 때 요긴하게 사용하는 ADB 입니다.

환경변수에 넣어 편리하게 사용해요. :-)



- DOZE 모드에 대한 내용은 이 곳에서 참고 ~.~





반응형

'개발이야기 > Tip&Tech' 카테고리의 다른 글

Git 관리 관련 포스팅 정리.  (0) 2017.03.08
eclipse 톰캣 힙 늘리기  (0) 2017.01.19
eclipse 초기 세팅 validating 설정하기  (0) 2017.01.19
eclipse 퀵 서치 설치하기  (0) 2017.01.19
Posted by N'


톰캣 힙은 언제나 늘리고 시작합시다. ㅡㅡ^

반응형
Posted by N'

Window > Preferences > Validation

 

에서 불필요 Validator 체크 해제할 것!

 

반응형

'개발이야기 > Tip&Tech' 카테고리의 다른 글

Git 관리 관련 포스팅 정리.  (0) 2017.03.08
안드로이드 스튜디오 ADB 위치 확인  (0) 2017.01.19
eclipse 톰캣 힙 늘리기  (0) 2017.01.19
eclipse 퀵 서치 설치하기  (0) 2017.01.19
Posted by N'

1. install New Software -> 
     
 Work with : http://dist.springsource.com/release/TOOLS/update/e4.3/

 

2. Core/ Eclipse Integration Commons 밑에  Eclipse Quicksearch 선택

 

 

출처는 이 곳입니다.

 

 

http://egloos.zum.com/chaehun467/v/6065038

반응형
Posted by N'