함수형 프로그래밍을 시작하는 첫 장에서는 


- 함수란 무엇인가?


- 다형성(Polymorphism)의 필요성과 이를 이용하기 위한 OOP 의 한계


- 이를 극복하기 위한, 기존 Java8 이전의 방법과 편리해진 람다와 메소드 레퍼런스


정도의 내용을 다루었습니다. (위의 개념에 대해 생각이 안난다면, 꼭 복습을 권장합니다.)


이에 대한 포스팅 정보는 아래에서 확인 :-)



그리고 이를 실습해보기 위한 과제가 있었고, 이번 포스팅에서는 과제에 대한 리뷰를 해보고자 합니다.


1. SwitchAndSwitch


첫 번째 숙제는 람다에 대한 튜토리얼 진행을 해보고자 했던 내용이었습니다.


요구사항은 아래의 소스를 리팩토링하는 것이었습니다.

메소드 내부에 존재하는 코드들은 일련의 공통적인 작업들이 많이 보입니다.


보통은 이런 작업에 대해서 따로 메소드 추출 등의 리팩토링을 하겠지만, 이 로직이 오직 switchAndSwitch 메소드에서만 사용될 것 같았기 때문에 다른 방법을 찾아보자는 것이었습니다.

(추 후, 다른 로직에서도 사용이 된다면 그 때 캡슐화하는 것으로...)


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
public void switchAndSwitch() {
 
    List<GenerateModel1> generateModel1List = Model1Dao.GetInstance().selectList(Arrays.asList(1,2,3));
 
    // something work1
    {
        List<Integer> memberSnList = Arrays.asList(2,3);
 
        for (GenerateModel1 model1 : generateModel1List) {
             if (memberSnList.contains(model1.getMemberSubjectSn())) {
                String name = model1.getName();
 
                switch (name) {
                case "강현지":
                    System.out.println("IF 란 사치임을 증명한 " + name);
                    break;
                
                case "유덕형":
                    System.out.println("한 수에 버그를 말살하는 " + name);
                    break;
                case "유형주":
                    System.out.println("한 메소드에 5줄 이면 충분한 " + name);
                    break;
                }
            }
        }
    }
 
    // something work2
    {
        List<String> filterNameList = Arrays.asList("강현지""유덕형");
 
        for (GenerateModel1 model1 : generateModel1List) {
            if (filterNameList.contains(name)) {
                String name = model1.getName();
            
                switch (name) {
                case "강현지":
                    System.out.println("IF 란 사치임을 증명한 " + name);
                    break;
                
                case "유덕형":
                    System.out.println("한 수에 버그를 말살하는 " + name);
                    break;
 
                case "유형주":
                    System.out.println("한 메소드에 5줄 이면 충분한 " + name);
                    break;
                }
            }
        }
    }
}
cs


우리는 일련의 동작 즉 함수를 값으로 가질 수 있다는 개념을 알았고, 이를 이용해서 굳이 메소드 추출을 안하고 이 문제를 해결할 수 있었습니다.


네, 람다를 이용해보는 것이죠.


그 전에 프로그래밍 원칙 중 중요한 원칙이 한 개 있습니다.


"변하는 부분과 변하지 않는 부분을 분리하라!"


이 법칙에 근거했을 때, 저는 해당 로직에 대해 다음과 같이 정의를 했습니다.


- 변하는 부분 : GenerateModel1 에 대한 필터 로직

- 변하지 않는 부분 : Loop 를 돌며, switch 문을 수행하는 과정.


이에 따라 저는 다음과 같이 리팩토링 할 수 있었습니다.


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
public void switchAndSwitch() {
 
    List<GenerateModel1> generateModel1List = Model1Dao.GetInstance().selectList(Arrays.asList(1,2,3));
 
    /**
     *  Predicate 를 받아, 일련의 공통작업을 수행하는 소비자.
     */
    Consumer<Predicate<GenerateModel1>> switchConsumer =
        (predicate) -> {
            for (GenerateModel1 model1 : generateModel1List) {
                
                if(predicate.test(model1)) {
 
                    String name = model1.getName();
                    
                    switch (name) {
                    case "강현지":
                        System.out.println("IF 란 사치임을 증명한 " + name);
                        break;
 
                    case "유덕형":
                        System.out.println("한 수에 버그를 말살하는 " + name);
                        break;
 
                    case "유형주":
                        System.out.println("한 메소드에 5줄 이면 충분한 " + name);
                        break;
                    }
                }
            }
        };
 
    // something work1
    {
        List<Integer> memberSnList = Arrays.asList(2,3);
 
        // 프리디케이트가 정의되는 부분이 변하는 부분.            
        switchConsumer.accept(model1 -> memberSnList.contains(model1.getMemberSubjectSn()));    
    }
 
    // something work2
    {
        List<String> filterNameList = Arrays.asList("강현지""유덕형");
 
        // 프리디케이트가 정의되는 부분이 변하는 부분.            
        switchConsumer.accept(model1 -> filterNameList.contains(model1.getName()));    
    }
}
cs


람다에 대한 첫 튜토리얼로 나름 나쁘지 않았다고 생각합니다. :-)


사실 개인적으로 람다표현식 보다도 위의 규칙이 더 중요하며, 저 규칙만 잘지켜도 좋은 프로그래머가 될 수 있을 것이란 생각이 드는군요.



2. Template-Method 의 극복? 이 방법은 정말로 좋은가?


동적 파라미터화를 이용하면, 굳이 클래스를 상속받아 구현하지 않아도 다형성을 사용할 수 있음을 알았습니다.


또한 JAVA8 에서는 기본 함수형 인터페이스까지 제공해주기 때문에 동적파라미터화를 하기 위해 따로 인터페이스를 제작하지 않아도 될 것 같아 보이는데요.


기본형 함수형 인터페이스에 대한 내용은 아래 포스팅에서 참고.



이 숙제는, 정말로 이 방법이 좋은 지에 대해 다뤄보는 내용이었습니다.


요구사항은 다음과 같았습니다.


ORM 으로 제작된 클래스들의 필드는 종종 비슷한 경우가 많지만, 아쉽게도 제너레이터에 의해 제작되기 때문에 이 곳을 수정하는 것은 문제가 있어보입니다.

(즉 상속 등 클래스 관계를 지어줄 수 없으며, 이는 꽤 골치아픈 문제가 될 수 있습니다.)


즉 이러한 이유로 다형성 등의 개념을 이용할 수 없는 것처럼 보이며, 이는 비슷한 로직도 재활용이 쉽지 않음을 의미합니다.


이를 극복하기 위한 여러가지 방법을 다뤘으며, 대표적인 방법 중 한 가지는 Template-Method 패턴을 이용해보는 것이었습니다.



하지만, 굳이 한 메소드 제작을 위해서 복잡한 클래스 구조를 가질 수 있어 보이는 Template-Method 를 사용하는 것은 부담이라 생각하였습니다.

(이런 생각은 귀차니즘에서 보통 비롯하곤 합니다. ㅡㅡ^)


그러던 중, 동적 파라미터화 및 기존 제공 함수형 인터페이스가 있는 것을 배웠고 이를 이용해 Template-Method 와 비슷한 효과를 낼 수 있을 것 같았습니다.


즉, 어떠한 인터페이스나 추상클래스를 만들지 않고 Template-Method 를 흉내내는 것이 이 과제의 목적이었습니다.


아래 소스는 과제로 제출해준 한 분의 소스입니다.

(제작해주셔서 감사합니다.^^)


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
/**
 * 제너레이트 된 모델에 대한 비지니스 로직 정의.
 *
 *
 * @param <G>
 * @param addTargetMemberSnList
 * @param excludeTargetMemberSnList
 */
public <G> void createByGenerateModel(
    List<Integer> addTargetMemberSnList
    , List<Integer> excludeTargetMemberSnList
    , Function<Integer, G> toGenerateModel
    , Function<List<Integer>, List<G>> selectList
    , Function<G, Integer> toMemberSubjectSn
    , Consumer<G> insertData
    , Consumer<List<Integer>> deleteByMemberSnList) {
        
    final HashMap<Integer, G> groupByMemberSnMemberMap = new HashMap<>();
    {    
        for (Integer memberSn : addTargetMemberSnList) {
            // 일단은 MemberSn 만 넣는다고 가정.
                
            G generateModel = toGenerateModel.apply(memberSn);
            groupByMemberSnMemberMap.put(memberSn, generateModel);
        }
    }
 
    // 이미 존재하는 구성원이거나 제외대상자는 입력 대상에서 제외.
    {
        // 이미 존재하는 구성원순번 또는 제외 타겟 순번 집합.
        HashSet<Integer> excludeTargetMemberSnSet = new HashSet<>();
        {
            // 이미 존재하는 구성원 순번 목록 삽입.
            List<G> existList = selectList.apply(groupByMemberSnMemberMap.keySet().stream().collect(Collectors.toList()));
            for (G model : existList) {
                excludeTargetMemberSnSet.add(toMemberSubjectSn.apply(model));
            }
 
            // 제외 대상 파라미터도 추가.
            excludeTargetMemberSnSet.addAll(excludeTargetMemberSnList);
        }
 
        // 추가대상 그룹에서 제외 대상 집합을 삭제한다.
        groupByMemberSnMemberMap.keySet().removeAll(excludeTargetMemberSnSet);
    }
 
    // 데이터 트랜잭션
    {
        // 데이터 삽입.
        for (G model : groupByMemberSnMemberMap.values()) {
            insertData.accept(model);
        }
 
        // 제외대상 삭제.
        deleteByMemberSnList.accept(excludeTargetMemberSnList);
    }
}
 
// 메소드 사용 예
// Model1 에 대한 데이터 처리.
ModelSampleService.GetInstance().createByGenerateModel(Arrays.asList(12), Arrays.asList(3),
                    (Integer memberSn) -> {
                        GenerateModel1 generateModel1 = new GenerateModel1();
                        generateModel1.setMemberSubjectSn(memberSn);
                        return generateModel1;
                    },
                    (List<Integer> memberSnList) -> {
                        List<GenerateModel1> list = Model1Dao.GetInstance().selectList(memberSnList);
                        return list;
                    },
                    (GenerateModel1 generateModel1) -> {
                        Integer memberSn = generateModel1.getMemberSubjectSn();
                        return memberSn;
                    },
                    (GenerateModel1 generateModel1) -> {
                        Model1Dao.GetInstance().create(generateModel1);
                        return;
                    },
                    (List<Integer> targetMemberSnList) -> {
                        Model1Dao.GetInstance().deleteByMemberSnList(targetMemberSnList);
                        return;
                    }
);
cs


의도 했던 바와 같이 어떠한 [인터페이스, 추상메소드] 없이, OCP 를 지킨 코드는 나왔습니다.

추 후, GenerateModel 이 또 등장하였고, 비슷한 로직을 사용한다면 각 함수형 인터페이스를 구현해주면 됩니다.


하지만, 일단 메소드 사용성 면에서 많은 불편함을 느꼈을 것입니다.

특정 프로토콜로 묶인 추상메소드의 구현이 아니기 때문에, 각 람다를 구현할 때마다 무슨 기능을 했었는지 살펴봐야 합니다.

이는 가독성도 떨어진다고 볼 수 있을 것 같네요..

(인터페이스나 추상클래스로 구현 했다면, 보다 동적인 구현에 있어서 무슨 일을 하는지 명확했을 수 있습니다.)  


이 과제의 의도는 새로운 지식을 맹신하지 말라는 일종의 견제를 해주고 싶었고(패턴병과 유사한 함수병), 요구사항과 현재 상황에 따라 적절한 대처가 필요함을 느낄 수 있도록 하는 것이 목적이었습니다.

(요구사항이 기껏 한 두개의 함수형 인터페이스만 사용할 정도라면, 깔끔할 수 있었습니다.)



3. 계속 존재했던 동적 파라미터화


마지막 과제는 명령패턴을 이용해, undo/redo 를 구현해보고자 하였습니다.


이와 관련된 내용은 아래 포스팅을 참고.



과제 자체는 사실 이 패턴에 대한 이해도 중요했지만, 꼭 동적 파라미터화 같은 방법이 JAVA8 에서 등장한 것은 아니었다는 것에 대한 실습이었습니다.


명령패턴은 일종의 요청을 캡슐화하고 컨트롤러가 이를 관리하게 함으로써, 실제 요청자와 실행자 사이의 관계를 느슨하게 하게 하는 것을 목적으로 합니다.


대부분 이 패턴의 예제에서는 명령을 캡슐화하기 위해 인터페이스를 구현하는 구현클래스를 제작하지만, 이 과제에서는 굳이 클래스를 제작하지 않고 동적 파라미터화를 이용하여 즉시 구현하는 것을 목적으로 하였습니다.

(다양한 요청에 대해서, 재활용 안 할 구현클래스를 만드는 것은 일종의 낭비이지 않을까요?)


일단, 이 패턴을 구현하기 위한 interface 는 다음과 같이 제작했습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * 커맨드 인터페이스 정의.
 *
 * Created by Doohyun on 2017. 7. 10..
 */
public interface ICommand {
 
    /**
     * 어떤기능이든 undo 구현.
     */
    void undo();
 
    /**
     * 어떤기능이든 실행 구현.
     */
    void execute();
}
cs


이를 실행하기 위한 실행자(Receiver)와 컨트롤러(Controller) 는 다음과 같습니다.

(대부분의 내용은 앞써 언급한 포스팅에서 복사했습니다. 이 패턴이 궁금하다면 해당 링크를 참고하세용.)


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
/**
 * 명령을 관리하는 컨트롤러
 */
public class RemoteController {
     
    // 일반 명령을 위한 스택
    private Stack<ICommand> commandStack = new Stack<>();
    // UNDO 명령을 위한 스택
    private Stack<ICommand> undoStack = new Stack<>();
 
    // 명령을 추가
    public void setCommand(ICommand commandWithUndoable) {
        commandStack.push(commandWithUndoable);
    }
 
    /**
     * 일반적인 실행. (REDO 포함)
     */
    public void execute() {
        if (!commandStack.isEmpty()) {
            // [일반명령 스택]에서 가장 마지막에 담긴 명령객체를 추출 후 실행.
            ICommand command = commandStack.pop();
            command.execute();
 
            // 해당 명령을 UNDO 스택에 삽입.
            undoStack.push(command);
        }
    }
 
    /**
     * 작업 취소 (Undo)
     */
    public void undo() {
        if (!undoStack.isEmpty()) {
            // [UNDO 명령 스택]에서 가장 마지막에 담긴 명령객체를 추출 후 실행.
            ICommand command = undoStack.pop();
            command.undo();
 
            // 일반 실행 스택에 데이터 삽입.
            commandStack.push(command);
        }
    }
}
 
/**
 * 글씨를 입력하는 데모 클래스.
 *
 * Created by Doohyun on 2017. 7. 10..
 */
public class TextWatcherDemo {
 
    private StringBuilder builder = new StringBuilder("");
 
    /**
     * 텍스트 입력.
     *
     * <pre>
     *     텍스트를 입력하고, 현재 상태를 표시한다.
     * </pre>
     *
     * @param text
     */
    public void addText(String text) {
        builder.append(text);
        System.out.println(builder.toString());
    }
 
    /**
     * 텍스트 삭제.
     *
     * <pre>
     *     텍스트를 삭제하고, 현재 상태를 표시한다.
     * </pre>
     */
    public void deleteText() {
        builder.deleteCharAt(builder.length() - 1);
        System.out.println(builder.toString());
    }
}
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
36
37
38
39
40
41
42
43
44
45
46
47
RemoteController remoteController = new RemoteController();
TextWatcherDemo textWatcherDemo = new TextWatcherDemo();
 
        
 
// 텍스트를 입력받아, 컨트롤러에 명령을 세팅하고 실행하는 소비자 정의.
Consumer<String> InputText = (text) -> {
        // 명령 세팅.
        // 동적으로 행위를 정의. 즉 동적파라미터화
        remoteController.setCommand(
            new ICommand(){
        
                @Override                                                    
                public void undo(){
                    textWatcherDemo.deleteText();
                }
 
                @Override
                public void execute(){
                    textWatcherDemo.addText(text);
                }
            }
        );
 
        // 실행
        remoteController.execute();
};
 
// 메소드 레퍼런스
// undo 실행을 위한 함수 정의.
Runnable undo = remoteController::undo;
 
// 람다 버전의 REDO 함수.
// redo 실행을 위한 함수 정의.
Runnable redo = () -> remoteController.execute();
        
InputText.accept("안");
InputText.accept("녕");
 
undo.run();
redo.run();
 
// CONSOLE LOG
// 안
// 안녕
// 안
// 안녕
cs


이런 방식은 사실 이벤트 처리 기반 시스템을 만들다보면, 꽤 많이 해봤을 것입니다.

결국 [JAVA8 in Action] 에서 메소드를 파라미터로 넘긴다는 이야기는 이전부터 있던 개념이며, 람다나 메소드 레퍼런스는 이를 보다 쉽게 사용할 수 있는 개념이라 볼 수 있을 것 같네요.

(추 후 공부하게 될, Stream API 사용을 위해서는 이를 적극적으로 사용해야 합니다.)



이로써, 지난 주에 실습한 내용에 대한 리뷰가 끝났습니다.

첫 시간이지만, 꽤 많은 내용을 다뤘던 듯 합니다. 그에 따라 과제도 좀 많이 있었죠. ^^;


이 과제를 언급할 때, 최근에 시청한 한 프로그램의 출연자가 했던 대화 중 하나를 같이 말했습니다.


 Knowing is nothing, Doing is the best. 

(아는 것은 중요하지 않다. 하는 것이 가장 좋다.)


단순히 듣기만 하는 것이 아니라, 한번 해보는 것은 정말 중요한 듯 합니다.

(이런 행동들은 조금씩 우아한 방법을 찾아 보는 것에 도움이 되겠죠?.. 아마도...)


어쨌든 개인시간을 투자하며, 계속 지금과 같은 시간을 같이 보내준 여러분들께 감사합니다.

(맥주를 한 병 더 먹었다면 아마 더 감성글이 됐을지도...)



반응형
Posted by N'

카테고리가 기존 함수형 방법론 으로 분류해서


포스팅을 하고 있지만 계속 공부하는 책인 JAVA8 in Action 을 보며, 포스팅을 하고 있습니다.


분류의 목적을 따지자면, JAVA8 에서 가장 중요하다고 생각할 만한 튜토리얼은 Lambda 와 Stream API 라고 할 수 있겠는데요. 

이와 관련된 기초 튜토리얼은 모두 끝났기 때문에 조금 더 More 하게 공부를 할 부분이기 때문에 나누었습니다.


More 한 카테고리에서의 첫 포스팅에서는 함수형 프로그래밍으로 리팩토링하기를 소개하려 합니다.

소개하는 내용은 JAVA8 in Action  리팩토링 관련 목록을 따라가지만 중요하다 싶은 부분만 소개하려 합니다.


1. Stream 을 통한 컬렉션 처리 리팩토링.


JAVA8 in Action 에 따르면,  모든 Collection 처리는 Stream API 로 바꿔야 한다고 하고 있습니다. 


Stream API 는 데이터 처리 파이프라인의 의도를 더 명확히 하여 가독성 향상에 도움을 줍니다. 

또한 쇼트서킷 처리와 Lazy 처리로 최적화 되어 있으며, 쉽게 병렬로 처리하도록 변경할 수 있습니다.


그러나 모든 Collection 처리를 Stream API 로 변경하는 것은 쉽지 않으며, 특히 break, continue, return 등 제어 흐름문에 대한 연산 역시 쉽지는 않아보입니다. 


하지만 우회하는 법은 언제나 존재합니다. 다음과 같이 말이죠.


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
// break 리팩토링.
{
    for (Integer a : numberList) {
        if (a % 10 == 0 && a > 55) {
            System.out.println(a);
            break;
        }
    }
            
    // findFirst 활용 
    numberList.stream().filter(a -> a % 10 == 0 && a > 55).findFirst().ifPresent(System.out::println);
}
 
// continue 리팩토링.
{
    int size = 0;
    for (Integer a : numberList) {
        if (a % 10 == 0 && a > 55) {
            continue;
        }
                
        ++size;
    }
            
    // 해당 조건이 아닌 것에 대한 Predicate 정의. 
    numberList.stream().filter(a -> !(a % 10 == 0 && a > 55)).collect(Collectors.counting());
}
 
// return 
 
/**
 * 리턴 for-loop
 * 
 * @param numberList
 * @return
 */
public Integer returnExampleForLoop(List<Integer> numberList) {
    for (Integer a : numberList) {
        if (a % 10 == 0 && a > 55) {
            return a;
        }
    }
    
    return null;
}
 
/**
 * 리턴 Stream API
 * 
 * @param numberList
 * @return
 */
public Optional<Integer> returnExampleStream(List<Integer> numberList) {
    return numberList.stream().filter(a -> !(a % 10 == 0 && a > 55)).findFirst();
}
cs



2. 조건부 연기


예를 들어 아래와 같은 코드가 있다고 합시다.


1
2
3
4
5
6
7
Data data = new Data();
data.setNumber(12);
        
if (object == null) {
    String message  = RollBaMessage();
    throw new MesseageException(message);
}
cs


객체의 null 체크를 하는 유효성 검사 로직입니다. 

객체가 Null 일 때, 지정된 Roll back message 를 예외 메시지로 입력합니다.


하지만, Null 을 체크하는 유효성 검사 모듈은 범용적으로 쓰이고 싶어 메소드를 만들어 재활용을  하고 싶었습니다. 

그래서 만들었죠.


1
2
3
4
5
6
7
public static void NullCheck(Object object, String message) throws MesseageException{
    if (object == null) {
        throw new MesseageException(message);
    }
}
 
NullCheck(data, RollBaMessage());
cs


각 비지니스 상황에 따라 여러 에러 메시지를 유연하게 보낼 수 있게 되었습니다. 


호스트코드에서는 단지 NullCheck 메소드만 불러주면될 것 같아 보입니다. 


그런데, 특정 메시지를 보내기 위한 새로운 메소드 RollBackDelayedMessage 를 제작했습니다.

미리 만들어둔 NullCheck 유효성 검사 모듈이 있으니 다음과 같이 사용을 할 생각입니다.


1
2
3
4
5
6
7
public static void NullCheck(Object object, String message) throws MesseageException{
    if (object == null) {
        throw new MesseageException(message);
    }
}
 
NullCheck(data, RollBackDelayedMessage());
cs


그런데 문제가 생겼습니다. RollBackDelayedMessage 는 실행시간이 10초나 걸립니다. 


게다가 이 로직은 에러 메시지를 출력하기 전에 비지니스 로직을 수행을 하는데, 그 로직은 null 일때만 해야하죠.. 

if문을 사용하여 따로 제작을 해서 중복코드를 만들어야 하는건가요?


아닙니다! 메소드 파라미터화를 시켜서 넘기면되죠. 아래와 같이 말이죠.


1
2
3
4
5
6
7
8
// 파라미터에 String 대신, String 을 출력하는 공급자를 언급
public static void NullCheck(Object object, Supplier<String> messageSupplier) throws MesseageException{
    if (object == null) {
        throw new MesseageException(messageSupplier.get());
    }
}
 
NullCheck(data, () -> RollBackDelayedMessage());
cs


이제 Null Check 유효성 검사 모듈에서 실패할 때만 RollBackDelayedMessage 을 실행할 수 있습니다. 즉 위와 같이 특정 상황에서만 실행하도록 하는 조정하는 것을 조건부 연기라고 합니다. 


3. 의무 체인


Lambda 에 대하여 포스팅할 때, 두 Lambda 식을 조합할 때 사용하는 andThen, compose 메소드를 본 적이 있습니다.



해당 메소드를 이용하면 두 함수를 엮어 새로운 기능을 만들어낼 수 있었습니다. 


정확히 말하면, 한 객체가 어떤 작업을 처리한 다음에 다른 객체로 결과를 전달하고, 다른 객체도 작업을 처리한 다음 또 다른 객체로 전달하는 등 기능들을 엮는 것이며, 이를 의무 체인이라고 합니다.


예를 들어 다음과 같이 함수형 인터페이스를 제작하고, 의무체인을 할 수 있는 여지를 남길 수 있습니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@FunctionalInterface
interface IProcesser<T> {
    T accept(T t);
    
    default IProcesser<T> chain(IProcesser<T> t1) {
        return t -> {
            T result = accept(t);
            
            return t1.accept(result);
        };
    }
}
 
IProcesser<Integer> t1 = n -> n + 5;
IProcesser<Integer> t2 = n -> n - 2;
IProcesser<Integer> t3 = t1.chain(t2);
        
System.out.println(t3.accept(10));
 
// result : 13
cs


인터페이스에는 JAVA8 부터 사용가능한 디폴트메소드를 사용하여, 각 함수들을 체인으로 걸도록 하였습니다.



위와 같이 Lambda 를 사용하여 리팩토링할 수 있는 여지들을 보았습니다. 

리팩토링을 통하여, 우리의 코드는 보다 더 가독성이 개선되며, 유지보수에 유연해질 수 있을 것입니다. 


리팩토링 하고 싶어지지 않나요?  :-)



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


반응형
Posted by N'

현재 보고 있는 책인 JAVA8 IN ACTION 에서는 람다에 대해서 너무 자세히 다루고 있습니다. 

덕분에 많은 공부가 되었고, 포스팅 개수도 많이 늘어나고 있는 것 같습니다. ㅡㅡ^


오늘은 람다에 대한 마지막 포스팅은 람다를 연결해서 사용하는 방법에 대해 알아보려고 합니다. 


람다가 수학식에 대하여 추상화를 한 것이라 했을 때, 우리는 각 식을 조합하여 복잡한 식을 만들 수 있다는 것을 생각해 볼 수 있습니다. 

(잘 생각해보면, 굳이 람다가 아니더라도 if 절을 사용할 때 and 나 or 등을 사용하여 복잡한 컨디션을 만들고 있지 않나요?)


JAVA8은 람다 표현식을 더욱 적극적으로 사용할 수 있도록, 람다를 조합을 하거나 혹은 편리하게 사용하게 해줄 수 있는 메소드를 제공해줍니다.


1. Comparator 


- reverse 메소드를 이용하여 역정렬을 사용할 수 있습니다.


1
2
3
4
5
List<Apple> dataList = new ArrayList<>();
Comparator<Apple> compare = Comparator.comparing(Apple::getWeight);
dataList.sort(compare);
dataList.sort(compare.reversed());
 
cs


- thenComparing 메소드를 조건을 더 연결할 수 있습니다.


1
2
// 무게가 같다면, 컬러로 
dataList.sort(Comparator.comparing(Apple::getWeight).reversed().thenComparing(Apple::getColor));
cs



2. Predicate


- negate 메소드를 사용하여 반대 (!) 를 사용할 수 있습니다.


1
2
3
4
5
6
BiFunction<String, Integer, Apple> creator2 = Apple::new;
Apple apple2 = creator2.apply("green"100);
 
Predicate<Apple> isNotHeavy = (v) -> v.getWeight() > 150;
System.out.println(isNotHeavy.negate().test(apple2)); // true 
 
cs


- and나 or를 사용할 수 있습니다.


1
2
3
4
5
Predicate<Apple> isNotHeavy = (v) -> v.getWeight() > 150;
 
// 사과의 무게는 150 이상 200 이하 이거나, 색이 green 인 경우
isNotHeavy.and((v) -> v.getWeight() < 200).or((v) -> v.getColor().equals("green")).test(apple2);
 
cs



3. Function


우리가 보통 함수를 처음 배울 때, 이러한 표현을 배운적이 있습니다.


f(x) = x + 1

g(x) = x * 2


g(f(x)) = (x + 1) * 2


위와 같은 역할을 해줄 수 있는 메소드가 존재합니다. (andThen, compose)


1
2
3
4
5
6
Function<Integer, Integer> f = (x) -> x + 1;        // f(x)
Function<Integer, Integer> g = (x) -> x * 2;        // g(x)
        
Function<Integer, Integer> fg = f.andThen(g);     // g(f(x))
Function<Integer, Integer> gf = f.compose(g);    // f(g(x))
 
cs



람다를 이어줌으로써, 복잡한 식을 보다 쉽게 작성할 수 있지만 아무래도 복잡한 식에 대해서는 메서드 레퍼런스를 사용하는 것이 좋아보입니다.


만드는 것은 간단해보일 수 있으나, 유지보수적인 면도 봐야겠죠. ㅎㅎ



이 것으로 람다에 대한 포스팅은 끝!!!!  :-)


다음 포스팅부터는 자바8의 꽃인 Stream API에 대해 다루어 보려 합니다.



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


반응형

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

Stream과 Collection 차이  (0) 2016.08.04
Stream API  (0) 2016.08.03
메서드 레퍼런스  (0) 2016.08.01
람다의 실제 형식  (0) 2016.07.28
클로저와 람다  (2) 2016.07.28
Posted by N'

계속해서 람다와 관련된 포스팅을 하고 있습니다. 


람다는 익명 클래스를 어느정도 대체를 해줄 수 있는 것처럼 보이며, 간단한 처리는 대부분 람다를 이용하게 되지 않을까요? 

(예를들어 안드로이드에서 이벤트 처리 시, 익명클래스의 대체가.. ㅡㅡ^ ) 


람다와 람다를 왜 써야하는지 보려면 이 곳으로...



오늘은 람다표현식과 더불어, 간결성과 더불어, 가독성까지 챙길 수 있는 메서드 레퍼런스에 대하여 포스팅하려 합니다.


JAVA 8 IN ACTION 에 따르면, 메서드 레퍼런스는 특정 메서드만을 호출하는 람다의 축양형 이라고 볼 수 있다고 합니다. 

즉 람다를 통해 어떻게 어떤식의 메서드를 사용하라가 아닌, 메서드의 이름만을 참조하여 처리한다고 볼 수 있습니다. (그래서 레퍼런스라고 하나 봅니다... ㅎㅎ)


예를 들어, 이런 것을 볼 수 있을 것 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
class Apple {
    private int weight;
 
    public int compareTo(Apple apple){
        if (weight >= apple.getWeight()) return 0;
        else if (weight <= apple.getWeight()) return 1;
        else return -1;
    }
}
 
inventory.sort((Apple a, Apple b) -> a.compareTo(b));
inventory.sort(Apple::compareTo); // -> 메서드 레퍼런스
 
cs


저는 이 것을 보면서 처음에 이해가 안됐던 부분이 파라미터는 어떻게 넣을 것이냐라는 것이었습니다. 

람다의 경우 파라미터 영역이 따로 명시되어 있지만 메서드 레퍼런스는 (Apple::compareTo) 다음과 같이 명시가 되어있지 않습니다. 

그러나 이 것은 sort 매개변수 넣을 당시, 람다표현식에 대한 형식검사를 함을 알 수 있습니다. sort의 파라미터로 람다 표현식을 사용 시, 람다 디스크립터를 맞춰야 한다는 것은 지난 포스팅 때 언급을 했었습니다.




즉 이런한 점을 이용해, 람다 디스크립터에 맞춰 메서드 레퍼런스를 사용해주면 됨을 알게 되었습니다.


JAVA8 IN ACTION 에서는 메서드 레퍼런스를 만드는 법은 크게 세가지로 보고 있습니다.


1. 정적 메서드 레퍼런스 (정적 메서드 - static 메서드라 생각합시다.)


예를들어 Integer 클래스의 parseInt 메서드는 정적 메서드로, String 객체를 Integer 형태로 변경해줍니다. 해당 메서드는 다음과 같이 인수로써 가질 수 있습니다.


1
2
3
Function<String, Integer> parse = Integer::parseInt;
int a = parse.apply("5");
 
cs



2. 다양한 형식의 인스턴스 메서드 레퍼런스 (일반 메서드라 생각합시다.)


클래스에 정의 된 일반 메서드를 인수로 가질 수 있음을 말합니다. 아래는 List의 메소드인 size() 를 인수로 가지는 예제입니다.


1
2
3
4
5
6
7
8
9
10
List<String> strList = new ArrayList<String>() {
    {
        add("A");
        add("B");
        add("C");
    }
};
 
Function<List, Integer> size = List::size;    // 일반 메서드의 메서드 레퍼런스
System.out.println(size.apply(strList));     // 3 
cs




3. 기존 객체의 인스턴스 메서드 레퍼런스 


인스턴스 객체를 이용하여, 메서드 레퍼런스를 사용할 수 있습니다. 

예를들면 이런식으로 사용이 가능해보입니다. 


1
2
3
4
5
6
7
8
9
10
List<String> strList = new ArrayList<String>() {
    {
        add("A");
        add("B");
        add("C");
    }
};
        
Consumer<String> consume = strList::add
consume.accept("E");
cs


메소드레퍼런스를 사용하려는 대상 메소드가 다형성에 의해 가려질 때 사용할 듯 합니다.



4. 생성자 레퍼런스


new 키워드를 사용하여, 생성자의 레퍼런스를 만들 수 있습니다. 아래와 같이 작성이 가능합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Apple {
    private String color;
    private int weight;
    
    public Apple(){}
    
    public Apple(String color, Integer weight){
        this.color = color;
        this.weight = weight;
    }
}
 
Supplier<Apple> creator = Apple::new;    // 기본 생성자에 대한 메서드 레퍼런스
Apple apple = creator.get(); 
 
// color 와 weight 를 받아 Apple을 만드는 생성자의 메서드 레퍼런스
BiFunction<String, Integer, Apple> creator2 = Apple::new
Apple apple2 = creator2.apply("green"100);
 
cs


위와 같이 다양한 시그니처 생성자에 대하여 메서드 레퍼런스를 사용할 수 있습니다.



이러한 메서드 레퍼런스를 람다표현식을 직접 사용하지 않고 사용하는 이유는 복잡한 람다식을 일일이 명시하는 것보다 가독성이 좋기 때문입니다. 


즉 람다식이 복잡하다면, 차라리 메소드로 정의하여 처리하는 것이 좋아보입니다.



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




반응형

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

Stream API  (0) 2016.08.03
람다 조합  (0) 2016.08.02
람다의 실제 형식  (0) 2016.07.28
클로저와 람다  (2) 2016.07.28
원시타입을 위한 함수형 인터페이스  (0) 2016.07.28
Posted by N'

계속해서 람다에 대한 포스팅을 하고 있습니다. 


람다 표현식의 사용을 통해 우리는 함수형 인터페이스의 인스턴스를 만들 수 있다는 것을 알게 되었습니다.



그러나 사실 람다표현식만 보게 되면 어느 함수형 인터페이스를 구현하는지에 대한 정보는 없습니다. 

단지 함수형 인터페이스에서 구현할 함수 디스크립터와의 형식 검사를 통해 유효성을 가질 수 있으며, 또한 어떻게 보면 추론까지 가능합니다. 


오늘은 이러한 내용에 대해 포스팅을 하려 합니다. ㅡㅡ^


1. 형식 검사


일단 아래 코드를 먼저 보도록 하겠습니다.


1
2
3
4
5
6
7
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}
 
Predicate<Apple> predicate = (Apple a) -> "green".equals(a.getColor());
 
cs


람다 표현식 (Apple a) -> "green".equals(a.getColor()); 이 Predicate<Apple>에 대입은 test 라는 한 개의 추상 메소드에 대한 함수 디스크립터에서 묘사해야할 내용과 같음을 알 수 있습니다. 


즉 추상 메소드의 시그니처가 요구하는 요구사항을 맞춰 람다를 제작해주어야 합니다.


이 때, void 의 경우 특별한 호환 규칙이 존재합니다.


1
2
3
4
5
6
public boolean checkData(Integer a){
    return a != null;
}
 
Consumer<Integer> setData = (Integer a) -> checkData(a);
setData.accept(5);
cs


위의 예제의 경우 람다 표현식 (Integer a) -> checkData(a); 의 경우 Integer를 파라미터로 받아 boolean 을 출력함을 알 수 있습니다. 


하지만 이 것은 유효한 코드입니다. 

(프로시저에서 boolean 함수를 사용한 것과 비슷하다고 생각하면 자연스럽게 넘어갈 수 있는 문제인 것 같습니다. ㅡㅡ^)


2. 형식 추론


함수형 인터페이스가 선언된 대상 형식(ex. Consumer<Apple>)에 따라, 람다 표현식의 형식 추론을 할 수 있습니다. 

대상 형식을 통해 함수 디스크립터(ex. (Integer a) -> a +2)를 알 수 있으며, 다음과 같은 코드가 가능합니다.


1
Consumer<Integer> setData = (a) -> checkData(a) ;
cs


대상형식 Consumer<Integer> 에 의해 람다 파라미터 부분에 Integer 형태가 이미 들어갈 것이라는 것을 추론할 수 있으며, 그렇기 때문에 단순히 a를 쓰는 것으로 대체가 가능합니다.


우리는 이미 이런 것을 제네릭을 사용하면서 많이 봐왔습니다. 

아래와 같이 말이죠.


1
List<Integer> dataList = new ArrayList<>();
cs



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



반응형

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

람다 조합  (0) 2016.08.02
메서드 레퍼런스  (0) 2016.08.01
클로저와 람다  (2) 2016.07.28
원시타입을 위한 함수형 인터페이스  (0) 2016.07.28
JAVA8 에 정의된 함수형 인터페이스  (0) 2016.07.28
Posted by N'

자바스크립트를 사용하다보면 종종 클로저를 사용할 때가 있습니다. 아래와 같이 말이죠.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Vo() {
    var data;
    
    return {
        getData : function() {
            return data;
        },
 
        setData : function(value) {
            data = value;
        }
    }
}
 
var closure = Vo();
closure.setData('Hello world');
console.log(closure.getData()); // Hello world
cs


클로저의 구조를 보게되면 함수 안에 내부함수가 존재하며, 이 내부함수는 인스턴스같이 외부로 넘겨지기도 하고 비지역변수를 자유롭게 변경이 가능합니다. 


람다나 익명클래스 역시도 저 동작과 비슷한 작업을 수행할 수 있습니다. 

함수를 인수로 넘기는 것이 가능하며, 메소드 내부에 함수를 선언할 수 있습니다. 

(아래 예제는 메소드 내부에 람다를 표현했고, 외부변수 x를 변경하였습니다.)


1
2
3
4
5
6
7
8
9
private int x = 0;
    
public void func(){
    int data = 0;
        
    IntConsumer setData = (int a) -> x = a;
    setData.accept(5);
}
 
cs


"JAVA 8 IN ACTION" 에서는 이러한 점 때문에 람다가 클로저의 정의에 부합하는가에 대해서 다루고 있습니다. 


결론부터 말하면, 서로 비슷한 일을 하고는 있지만 람다의 경우 선언된 지역변수의 데이터를 변경할 수 없습니다. 

지역변수가 람다 내부에서 사용되려면 final 한 상태이어야 합니다. 혹은 final과 같은 동작을 하거나 말이죠 ㅡㅡ^

(아래 예제는 람다 표현식에서 지역변수 data를 변경하려 하지만, 이 것은 문법 오류라고 체크하게 됩니다.)


1
2
3
4
5
6
7
public void func(){
    int data = 0;
         // 문법 오류
    IntConsumer setData = (int a) -> data = a;
    setData.accept(5);
}
 
cs


위와 같은 이유는 지역 변수 값의 경우 스택에 존재하며, 해당 메소드의 스레드 생명주기와 동일하게 종료되어야 합니다. 

이것이 보장되지 않는다면 람다 내부 블럭이 실행 시, 안전하지 않은 동작이 수행되겠죠.

(멤버 변수의 경우 힙에 있으므로, 람다에 대해 특별한 제약이 없습니다.) 



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


반응형
Posted by N'

JAVA8에서는 람다 표현식을 조금 더 편리하게 사용할 수 있도록 미리 제네릭 형태의 interface를 제작했습니다.




그러나, 기존 제네릭을 쓰는 클래스 (이를테면 Collection 그룹) 들과 마찬가지로, 

원시타입의 함수형 인터페이스는 사용할 수 없는 것처럼 보였습니다. ㅜㅜ 


오늘 포스팅은 이런 점들을 위한 원시타입을 지원하는 함수형 인터페이스에 대해 알아보려 합니다.


일단 원시타입을 지원하지 않는다고 하더라도, 이미 자바에는 원시타입을 지원하기 위한 wrapper 클래스들이 존재합니다. 


(Integer, Boolean 등) wrapper 라는 이름에서 보듯이, 이 클래스를 이용하면,


원시타입을 wrapper 로 변경하는 박싱 (boxing) 작업

wrapper 클래스를 원시타입으로 변경하는 언박싱 (unboxing) 작업


을 수행할 수 있습니다. 


보통 파라미터로 데이터를 넘기거나, 타입변환 시 자동으로 해주는 작업(오토 박싱)이지만, 이러한 변환 과정은 결국 비용이 발생하는 작업입니다. 

(인스턴스를 생성한 작업이기 때문에 결국 힙 메모리에 저장하게 되고, 원시타입을 찾을 때 역시도 메모리를 탐색해야 합니다)


자바8에서는 많이 사용되는 원시타입이 오토박싱되는 작업을 피할 수 있도록 원시타입에 특화된 함수형 interface를 제공합니다.


예를들어, int형의 Predicate 를 사용한다고하면, 다음과 같이 사용할 수 있습니다.


1
IntPredicate predicate = (int a) -> a >= 50;
cs


위와 같이 원시타입의 함수형 인터페이스를 사용하고 싶다면, 앞에 원시타입의 이름을 붙인 인터페이스를 사용하면 됩니다. 

(double 형의 Consumer 면 DoubleConsumer 겠죠. ㅡㅡ^)



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


반응형

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

람다의 실제 형식  (0) 2016.07.28
클로저와 람다  (2) 2016.07.28
JAVA8 에 정의된 함수형 인터페이스  (0) 2016.07.28
함수형 인터페이스  (0) 2016.07.27
람다 표현식  (0) 2016.07.27
Posted by N'

우리는 람다가 왜 쓰이는 지에 대해서 알게 되었고, 람다를 사용하기 위해서는 함수형 인터페이스를 제작해야한다는 것을 알게 되었습니다. 




하지만 대부분 함수형에서 사용할 법한 함수 디스크립터는 비슷할 것이며, 그렇기 때문에 JAVA8 라이브러리 설계자들은 java.util.function에 미리 사용할 법한 함수 인터페이스를 정의했습니다. 

(Observer 패턴을 많이 사용한다고 Observerable 을 정의한 것과 같은 맥락이겠죠. ㅡㅡ^)


오늘 포스팅은 자주 쓰일 법한 JAVA8의 함수형 인터페이스를 알아보려 합니다. (인터페이스에 대한 표기는 함수 디스크럽터만 정의합니다. 디폴트 메소드는 생략하겠습니다. ㅜㅜ)


1. Predicate

boolean 형식이 필요한 상황에서 별다른 구현 없이 람다식을 사용할 수 있습니다.


1
2
3
4
5
6
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}
 
Predicate<Apple> predicate = (Apple a) -> "green".equals(a.getColor());
cs


2. Consumer

interface의 이름대로, 소비자입니다. 즉 프로시저로써, void 형의 함수를 만든다고 생각하면 됩니다. 


1
2
3
4
5
6
7
8
9
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
 
People doohyun = obj.createObject(27"남두현");
 
Consumer<People> cosumer = (People people) -> System.out.println(people.toString());
cosumer.accept(doohyun);
cs


3. Function

Function 인터페이스의 경우 제네릭 형식의 T를 받아 R 객체를 반환하는 apply 라는 추상 메소드를 가지고 있습니다.


1
2
3
4
5
6
7
@FunctionalInterface
public interface Function<T, R> {
    R accept(T t);
}
 
Function<String, Integer> function = (String name) -> name.length();
Integer blogNameLength = function.apply("남두현 블로그 입니다.");
cs


4. Supplier

Consumer와 반대로 제네릭 형식의 T를 출력해주는 인터페이스입니다.  


1
2
3
4
5
6
7
8
9
10
@FunctionalInterface
public interface Supplier<T> {
    T accept();
}
 
Supplier<People> supplier = () -> {
    People ret = new People();
    ret.setName("기본 이름 지정");
    return ret;
};
cs


5. UnaryOperator

제네릭 형식의 T 타입을 파라미터로 받아 T를 return 하는 인터페이스다. 생각보다 쓸모는 없어보이네요다. 굳이 쓰자면 이런데에 쓸만하지 않을까요? (Clone 만들기)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@FunctionalInterface
public interface UnaryOperator<T> {
    T createObject(T t);
}
 
UnaryOperator<People> unaryOperator = (People people) -> {
    People clone = new People();
    clone.setAge(people.getAge());
    clone.setName(people.getName());
 
    return clone;
};
 
People doohyun = obj.createObject(27"남두현");
People cloneDoohyun = unaryOperator.apply(doohyun);
cs


6. BinaryOperator

제네릭 형식의 T 타입 두개를 받아, 조합하는 용도로 사용할 수 있다. 


1
2
3
4
5
6
7
@FunctionalInterface
public interface BinaryOperator<T> {
    T createObject(T t1, T t2);
}
 
BinaryOperator<Integer> binaryOperator = (Integer a, Integer b) -> a + b;
System.out.println(binaryOperator.apply(54));
cs


여기서 한가지 봐야할 부분은 UnaryOperator 와 BinayOperator 인터페이스는 compose 와 andThen 등의 디폴트 메소드를 가지고 있습니다. 해당 메소드를 통하여 다른 함수형 인터페이스를 만들수 있으니, 참고하세요.^^


1
2
3
4
5
6
7
UnaryOperator<String> unaryOperator = (String name) -> {
    return name;
};
Function<People, String> func = unaryOperator.compose((People people) -> people.getName());
 
People doohyun = obj.createObject(27"남두현");
System.out.println(func.apply(doohyun));
cs


Default method 로 등장장한 compose, andThen 에 대한 설명은 이 곳을 참고.



7. BiPredicate, BiConsumber, BiFunction

서로 다른 제네릭 형식 T, R 에 대한 비교를 수행할 수 있습니다. 

Bi가 붙은 인터페이스들은 서로 다른 제네릭에 대하여 수행을 합니다.


Bi 가 붙은 인터페이스는 함수디스크립터만 명시하겠습니다.


BiPredicate<L, R>   -->  (L, R)   --> boolean

BiConsumbe<T, U>   --> (T, U)   --> void

BiFunction<T, U, R>    --> (T, U)   --> R


1
2
3
4
5
6
7
@FunctionalInterface
public interface BiPredicate<T, R> {
    boolean test(T t, R r);
}
 
BiPredicate<Apple, String> biPredicate = (Apple apple, String type) -> type.equal(apple.getColor());
biPredicate.test(apple, "green");
cs



위와 같이, 이미 정의된 함수형 interface로 인해, 많은 기능을 사용할 수 있을 것 처럼 보입니다. 그러나 가만보니, 인터페이스에 붙어있는 제네릭 T에 의해 원사타입의 value 들(이를 테면 int, boolean 등)은 사용할 수 없는 것 처럼 보이네요.


그것에 대한 해결은 이 곳에서 확인 바랍니다.



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




반응형
Posted by N'

지난번 포스팅에서는 람다(Lambda) 표현식이라는 간단하게 함수를 정의하는 개념에 대해 알아보았습니다.



이번 포스팅에서는 JAVA8 에서 이러한 람다를 지원할 수 있겠금 등장한 함수형 인터페이스에 대해 알아보려 합니다.


JAVA8에서는 새로운 어노테이션이 생겼습니다!!!!


@FunctionalInterface


어노테이션의 이름에서 다들 눈치를 체셨을 겁니다. 

네, 바로 함수형 인터페이스를 선언을 위해 등장한 어노테이션 입니다. (종류는 Built in annotation)


그렇다고 모든 interface에 저 어노테이션을 붙일 수는 없습니다. 

저 어노테이션이 오직 한 개의 추상메소드가 있는 인터페이스만 선언할 수 있습니다. 다음 아래와 같이 말이죠.


1
2
3
4
@FunctionalInterface
public interface CreateObjInterface {
    People createObject(int age, String name);
}
cs


왜 꼭 한개의 추상메소드를 가진 인터페이스만 선언이 가능하게 만들었을까요? 제가 읽고 있는 JAVA 8 IN ACTION 에서는 함수 형식을 표현하기 위해서라는 이유와 이미 많은 JAVA 프로그래머들이 추상 메소드 한개를 갖는 인터페이스에 익숙하기 때문이라고 합니다.


인터페이스에 여러 메소드가 정의되어야 한다면, 차라리 클래스로써, concrete 하게 가지고 있는게 더 좋겠죠. 이 것에 대한 내용은 아래 포스팅에 조금 더 자세히 적었습니다.



우리는 함수형 인터페이스를 사용하여 람다표현식을 사용할 수 있게 되었습니다. 앞서, 정의한 CreateObjInterface를 사용하여 다음과 같이 코드를 작성할 수 있습니다. 


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
/**
 * 사람의 정보를 저장하는 Vo 클래스
 * 
 * @author Doohyun
 *
 */
public class People {
    private int age;
    private String name;
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
}
 
// 객체 생성을 하는 방식의 람다식을 사용하여, 인스턴스를 생성
CreateObjInterface obj = (int age, String name) -> {
    People ret = new People();
    ret.setAge(age);
    ret.setName(name);
    return ret;
};
 
People doohyun = obj.createObject(27"남두현");
 
cs


여기에서 주목할 점은 람다표현식이 CreateObjInterface 인스턴스로 취급이 된다는 것입니다. (객체지향적으로 봤을 때는 interface를 구현(implement)하는 concrete 클래스를 만든 것이라고 할 수 있습니다.)


즉 이러한 특성을 이용해 함수를 인수로 가지고 있으며, 코드로 넘길 수 있는 것이 가능해졌다는 것을 알 수 있습니다.


이렇게, 람다를 사용하기 위해 함수형 인터페이스를 정의해야하는 이유는 사용할 람다의 시그니처가 되기 때문입니다. 시그니처란 파라미터 형식, 반환값 등을 말할 수 있는데요. (객체지향에서는 Access 수준, 메소드명, final 정도가 더있을 것 같습니다.람다가 어떻게 쓰일 것이다라는 규칙을 명시한 것이라 볼 수 있을 것 같습니다. 함수형 프로그래밍에서는 이것을 함수 디스크럽터 라고 합니다.


즉 람다가 익명의 추상 메소드라는 점에서, 기존 메소드 정의해야할 대부분을 똑같이 따라가줘야함을 알 수 있습니다. 그렇기 때문에 기존 익명 클래스 제작 시, 생겼던 규칙 역시 그대로 안고 갑니다.


1. checked exception

람다 내부에서 checked exception이 발생할 수 있는 로직의 경우, 해당 예외를 어떻게 처리를 할 것인지 명시해주어야합니다. 먼저 상위로 throw 하기 위해서는 아래와 같이 interface에 넘길 exception 명시를 해주어야 합니다. 


1
2
3
4
5
6
 
@FunctionalInterface
public interface CreateObjInterface throws NameEmptyException {
    People createObject(int age, String name);
}
 
cs


물론 람다 내에서 try-catch 문으로 처리가 가능합니다.


1
2
3
4
5
6
7
8
9
10
(int age, String name) -> {
    People ret = new People();
    try {
        ret.setName(name);
        ret.setAge(age);
    } catch (NameEmptyException e) {
        e.printStackTrace();
    }
    return ret;
};
cs


2. 변수 접근

기존 익명 클래스를 만들어 인터페이스를 제작했던 것 처럼 람다 역시 외부에 정의된 자유 변수(파라미터로 넘어가지 않는 변수)를 사용할 수 있습니다. 이와 같은 동작을 람다 캡처링이라고 부른다고 합니다. 


이러한 변수는 static 변수나 인스턴스 변수의 값을 자유롭게 변경할 수 있지만 지역변수 (함수 내부에 선언된 변수) 는 final인 상태로 밖에 가지고 있지 못합니다.


기존 자바에서는 익명 클래스 내부 구현 메소드에서 지역변수의 final을 강제화했지만 람다는 그렇지 않습니다. 람다의 경우 final 를 강제화 하지는 않지만 지역변수의 값을 변경 시 문법오류를 일으키게 됩니다.


이 것에 대한 자세한 포스팅은 람다와 함수형 인터페이스간의 해석 방법에 대한 포스팅에서 자세히 다루도록 하겠습니다.



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



반응형
Posted by N'

이번 포스팅은 함수형 프로그래밍의 꽃이라고 할 수 있는 람다 표현식에 대해 알아보려 합니다.


람다를 사용하는 이유는 계속 전 포스팅부터 봐왔지만 동작파라미터화를 통한 코드의 유연함을 가질 수 있도록 하고, 실행 시 유연하게 작동할 블록에 대하여 간결하게 만들자는 의미에서 등장했습니다.


동작파라미터화가 무엇인지 모른다면? 이곳으로 가서 먼저 보시길 ㅎㅎ



일단 람다(Lambda)가 무엇일까요?


람다는 미적분학의 기호 중 λ (람다 대수)에서 유래되었습니다. 이 것은 함수 정의, 함수 적용, 귀납적 함수를 추상화한 형식 체계를 나타내는데요. 이 뜻대로, 람다가 JAVA8에 구현되었습니다. (익명 클래스를 대체하여, 추상메소드를 전달한다는 점에서 비슷한 것 같네요. ㅡㅡ^)


람다 표현식은 메소드로 전달하는 익명 함수를 단순화 시킨 것으로, 파라미터 리스트, 반환 형식, checked exception 등 기존 메소드 정의 시 해야 했던 것을 모두 해야합니다. JAVA8 IN ACTION에서는 람다의 특징을 다음과 같이 정의했습니다.


1. 익명

보통의 메소드와 달리 이름이 없으니 익명입니다. 


2. 함수

클래스에 종속되는 메소드와 달리 함수라고 부릅니다.


3. 전달

매개변수로 전달이 가능합니다. 또한 함수를 변수에 저장할 수 있습니다. 

(자바가 자바스크립트 처럼 되어가네요. 이렇게요.)


1
2
3
var func1 = function() {
    console.log('변수로 가지고 있는 함수입니다.');
}
cs


4. 간결성

이건 뭐, 너무 언급해서... 굳이 객체로 감싸지 않아도 됩니다.



람다는 크게 세 부분으로 나누어져 있습니다. (파라미터 리스트, 화살표, 바디)

파라미터 리스트는 말 그대로 매개변수 정의부, 바디는 메소드의 바디, 화살표는 파라미터 리스트와 바디를 연결해주는 것을 말합니다.


(parameter) -> expression 


이라 보면 됩니다. expression이 길어진다면 {}을 붙일 수 있습니다.


람다를 통해서, 보통 다음과 같은 일을 할 수 있습니다.


1. boolean 표현식 

아래는 파라미터 한개를 받아, boolean을 표현합니다.


1
(int a) -> a > 5
cs


2. 객체 생성

아래는 나이와 이름을 받아, People 인스턴스를 만들고 있습니다.


1
2
3
4
5
6
 (int age, String name) -> {
            People ret = new People();
            ret.setAge(age);
            ret.setName(name);
            return ret;
        };
cs


3. 객체 소비 (프로시저)

보통 void 함수와 같은 역할이라 보면 됩니다.


1
2
3
 (People people) -> {
            System.out.println(people.toString());
        };
cs


4. 객체에서 선택/추출

객체에서 특정 내용을 추출합니다. (people 객체에서 나이를 선택해서 뽑아냅니다.


1
 (People people) -> people.getAge()
cs


5. 두 값을 조합

객체에서 두 값을 조합한 결과를 출력합니다.


1
(int a, int b) -> a + b
cs


6. 두 값을 비교

두 값을 비교한 결과를 출력합니다.


1
(int a, int b) -> a >= b
cs


보통 위와 같은 6가지 패턴으로 주로 사용하고 있으며, 이러한 방법들은 우리가 보통 함수를 만들고 사용하던 방식과 차이가 없어보입니다. (즉 처음 프로그래밍을 배우기 시작하며, 함수라는 개념을 배우던 그 때로 돌아간 것이라 볼 수 있네요. ㅡㅡ^)


다음 포스팅에서는 JAVA8 에서 람다 표현식에 개념과 함께 등장한 함수형 인터페이스와 편의성을 위해 미리 구현된 몇가지의 인터페이스를 보고자 합니다.





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

 

반응형
Posted by N'