지난번 포스팅에서는 람다(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'


포스트의 제목은 현재 읽고 있는 책인 "JAVA8 IN ACTION"의 첫 목차입니다.


자바8의 업데이트가 가장 빛나는 이유 중 하나는 함수형프로그래밍을 할 수 있기 때문이라 할 수 있을 것 같습니다. 

이를 지원하기 위해 여러 문법이 추가되었고, 향후 해당 코드들이 많이 쓰일 수 있기 때문에 봐야할 것이라는 생각이 드네요.


안드로이드 (안드로이드 N은 된다고 합니다. ㅡㅡ^) 나, 현재 회사 내에서 사용하는 spring 프레임워크에서는 JAVA8을 사용하고 있지 않지만 향후 몇년 안에 소스 상에 있는 함수형 문법을 많이 보게 될지도 모르죠 ㅎㅎ .

(벌써 자바스크립트는 많이 나왔죠..) 


그러나 해당 문법들이 단순히 알고있던 JAVA 문법으로만 보고 해석하기엔 난해합니다. ㅡㅡ^ (쌩뚱맞는 문법들이 많네용.... 모르면 뭐하는 놈인지 몰라용)


다른 언어들에는 이미 함수형 프로그래밍과 관련된 것들이 나왔는데 (파이썬의 lambda 등...) 자바에서 이번 버전에 이를 지원해주니 한번 주의 깊게 봐야할 필요가 있습니다.


크게 봐야할 내용 중 첫번째는 "메소드에 코드를 전달하는 것"인데요. 

이 개념이 목차만 봤지만 함수형 프로그래밍의 가장 중요한 요소인 것 같아 먼저 적어봤습니다.


"메소드에 코드를 전달한다"는 내용은 생각보다 큽니다. 이는 단순히 코드의 간결성뿐만 아니라, 아랫단(스레드라던지, 메모리라던지...)의 문제를 쉽게 해결할 수 있는 것 처럼 보입니다.


일단 "메소드에 코드를 전달한다" 는 내용은 무슨말인지도 모르겠네요. 

조금 풀어서 설명하면, 여태까지 넘기던 파라미터, 즉 원시값(int, double)이나 참조값(인스턴스에의 참조 값) 외에 함수라는 자체 역시 코드화하여 넘기자는 이야기인데요. 

이러한 기능을 통해 우리는 동작 파라미터화(behavior parameterization) 를 쉽게 처리 할 수 있습니다.


말로하면 어려우니, 자바8의 예제를 조금보자면 


1
2
3
4
5
6
7
public class Apple {
    private String color;
 
    public String getColor(){
        return color;
    }
}
cs


다음과 같이 Apple 클래스를 정의하고, 이를 분류하기 위하여 GetFilterApple 이라는 함수를 정의했다고 합시당.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    /**
     * 사과 객체를 분류하기 위한 함수
     * 
     * @param appleList
     * @return
     */
    public static List<Apple> GetFilterApple(List<Apple> appleList) {
        ArrayList<Apple> resultList = new ArrayList<>();
 
        for (Apple apple : appleList) {
            if ("green".equals(apple.getColor())) {
                resultList.add(apple);
            }
        }
 
        return resultList;
    }
cs


보기에는 단순해 보이는 코드이지만 여러 요구사항에 따라 필터로 하자는 조건이 늘어날 수 있습니다. 

(ㅡㅡ^ 변화를 사랑하는 개발자가 됩시다..)


그때마다 우리는 분류조건을 늘릴 것인가요?  

(선택지가 두개정도 보이네요...)


1. copy&paste


잘하는 것... 


각 메소드의 존재 이유는 해당 역할을 하기 위한 것이니, 위의 GetFilterApple 이라 하지말고,

GetFilterAppleForColor, GetFilterAppleForHeavy 등을 만들고 조건에 맞는 것을 Set으로 넘기면 되지 않나요? 


아래와 같이요..


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    /**
     * 사과 객체를 분류하기 위한 함수
     * 
     * @param appleList
     * @param wannaColor
     * @return
     */
    public static List<Apple> GetFilterApple(List<Apple> appleList, Set<String> wannaColor) {
        ArrayList<Apple> resultList = new ArrayList<>();
 
        for (Apple apple : appleList) {
            if (wannaColor.contain(apple.getColor())) {
                resultList.add(apple);
            }
        }
 
        return resultList;
    }
cs



명확한 해결일 수 있지만, copy&paste의 단점이라 할 수 있는, 한곳에 문제가 생기면 모두 변경해야한다. 아니 좋은 것 같네요....  


또한 이를 위해 끊임없는 메소드 를 정의해야할 것 같아 보입니다.



2. 일을 조금 더 추상화해보자.


일단 GetFilterApple이 filter라는 역할을 한다는 것에 중점을 맞출 때 변경될만한 부분은 필터 조건입니다. 이를테면 이 놈 -> "green".equals(apple.getColor())


이 부분을 추상화하고,  필요할 때마다 구체화시켜주면 해결될 문제자나요. 

OOP 에 많이 나오는 건데.. 


변하는 부분과 변하지 않는 부분을 분리하자.


맞습니다. 저 원리는 객체지향 개발론 책 한번이라도 봤으면 있는 원리 중 하나입니다.. 


이를 테면 다음과 같이 작업해 볼 수 있을 것 같네요.


사과를 분류하기 위한 인터페이스를 정의하고,


1
2
3
4
5
6
7
8
9
10
/**
 * 사과를 분류하기 위한 인터페이스
 * 
 * @author Doohyun
 *
 */
public interface AppleFilterAble {
    public boolean isContain(Apple apple);
}
 
cs


위에서 작성한 GetFilterApple이 해당 인터페이스를 사용하도록 하게 할게요. 이렇게 말이죠..


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    /**
     * 사과 객체를 분류하기 위한 함수
     * 
     * @param appleList
     * @param appleFilter
     * @return
     */
    public static List<Apple> GetFilterApple(List<Apple> appleList, AppleFilterAble appleFilter) {
        ArrayList<Apple> resultList = new ArrayList<>();
 
        for (Apple apple : appleList) {
            if (appleFilter.isContain(apple)) {
                resultList.add(apple);
            }
        }
 
        return resultList;
    }
cs


해당 메소드의 호출은 다음과 같이 익명 클래스를 정의하면 변화에 유연할 수 있을 것 같아 보이는 군요. 아래처럼 말이죠.


1
2
3
4
5
6
GetFilterApple(inventory, new AppleFilterAble(){
    @Override
    public boolean isContain(Apple apple){
        return "green".equals(apple.getColor());
    }
});
cs


좋은 방법입니다. 


하지만 이를 위해 인터페이스를 만들어야하고, 인스턴스를 생성해야합니다. 

귀차니즘......



3. 함수형 프로그래밍을 해보자


함수형프로그래밍에서 언급하는 메서드에 코드를 전달하는 기법의 대표적인 것은 함수 자체도 값으로써, 넘기자는 것입니다. 


여태까지 자바에서는 이를 지원하지 않았고, 자바8에서 비로소 지원을 할 수 있게 되었는데 이렇게 사용이 가능합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Apple {
    private String color;
 
    public String getColor(){
        return color;
    }
 
    /**
     * 초록색 사과인가를 분류하고자 함.
     * 
     * @param apple
     * @return
     */
    public static boolean IsGreenApple(Apple apple) {
        return "green".equals(apple.getColor());
    }
}
 
cs


다음과 같이 IsGreenApple이라는 static 메소드를 선언 후, 

GetFilterApple을 JAVA8에서 새롭게 추가된 Predicate interface를 이용하여 다음과 같이 작성해줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    /**
     * 사과 객체를 분류하기 위한 함수
     * 
     * @param appleList
     * @param p
     * @return
     */
    public static List<Apple> GetFilterApple(List<Apple> appleList, Predicate<Apple> p) {
        ArrayList<Apple> resultList = new ArrayList<>();
 
        for (Apple apple : appleList) {
            if (p.test(apple)) {
                resultList.add(apple);
            }
        }
 
        return resultList;
    }
cs


"JAVA8 IN ACTION"에 따르면, 


Predicate 란 수학에서 true나 false를 반환하는 함수로써, 


이를 사용하는 것이 표준적인 방식이라 합니다. 또한 boolean을 Boolean으로 변환하지 않으니 효율적입니다.


호출은 아래와 같이 진행을 합니다.


1
GetFilterApple(inventory, Apple::IsGreenApple);
cs


매우 simple해졌네요. filter 역할을 해주는 static 메소드를 정의해 사용하면 됩니다. 

그런데, 함수형 프로그래밍을 하려면 static 메소드를 정의해야 하나요? 그렇다면 일일이 static 메소드를 또 만들어야하는데.. 1번의 copy&paste와 같은 문제가 있지 않나요?



4. 람다표현식


변화에 유연하게 대처하기 위해, 2번에서는 익명 클래스를 선언하여 해야할 일을 정의했습니다. 함수형에서는 이를 위해 존재하는 것이 람다 표현식이라 할 수 있을 것 같네요.


다음과 같이 말이죠.


1
GetFilterApple(inventory, (Apple a) -> "green".equals(a.getColor()))
cs


즉 한번만 사용될 메소드를 위해 함수를 정의하지 않고, 간단히 람다로 처리할 수 있습니다. 그러나 식 자체가 복잡해 진다면 함수를 따로 선언하고 넘기는 것을 권장합니다.


* 이로써 나오는 효과들은?


1. 코드가 간결해지고, interface 등을 따로 만들지 않아도 됩니다. (귀찮은 일이 줄었다..) 

이 것은 객체로 메소드를 감지 않고, 자바8의 메소드 레퍼런스 문법을 이용하여 직접 보낼 수 있음을 의미합니다.


2. 함수를 값으로써, 넘기겠다는 말은 프로그램이 실행되는 동안 컴포넌트 간 상호작용이 일어나지 않는다는 뜻이 될 수 있다고 합니다. 

이는 후에 stream에서 언급할 주제로, 병렬성에서 이득을 볼 수 있는 여지를 줄 수 있는 것 처럼 보이네요. 

(자바8에서의 병렬성에 대한 이야기는 매우 중요합니다.) 




Functional_tutorial.zip




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





반응형
Posted by N'