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'

현재 보고 있는 책인 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'

이번 포스팅에서는 지난번에 포스팅했던 함수형 프로그래밍을 통한 방법과 기존의 객체지향 관점의 디자인 패턴과 비교를 해보고자 합니다.



이 포스팅을 올리려고 생각한 이유는 현재 보고 있는 책인 "JAVA8 IN ACTION" 에서는 메소드를 코드로 넘기는 것을 통해 간단히 동작 파라미터화를 하는 것을 보여줌으로써, 기존의 전략 패턴을 사장시킬 수 있을까에 대한 생각이 들었습니다.


하지만 꼭 그런 것은 아닌 경우가 몇가지가 있었습니다.


1. 전략 자체가 상태를 가지고 있어야 하는 경우.


보통 전략 패턴을 사용하여, 전략 객체를 만들 때, 필자의 경우에는 


* 전략이 상태를 가지고 있는 경우라면, interface를 만들고 concrete class 를 구현

* 전략이 상태를 가지지 않는 경우라면, enum을 사용해 singleton 으로 생성


으로 구현하였습니다.


함수형 프로그래밍에서 사용하는 메소드 역시, 특정 상태를 저장한 체로 유지할 수 없으며, 이는 상태가 필요한 전략에서는 사용할 수 없음을 말할 것 같습니다. 


2. 전략 자체가 구현해야할 추상 메소드가 다수 있는 경우.


AppleFilter 예제에서는 간단히 필터의 역할만을 Predicate 인터페이스를 통해 함수 자체로 넘기는 것이 가능했지만 구현해야할 메소드가 다수라면 기존 전략 패턴을 사용해야 할 것으로 보입니다.


물론 기능단위로 더욱 쪼개거나, 동작을 파라미터로 더 받을 수도 있겠지만, 아래 UML과 같이 한 전략에 대해서 각 구현 클래스가 concrete하게 가지고 있는 것이 유지보수성에서 더 좋아보입니다. 특히 한 전략에 대해서 해야할 다른 일들이 늘어난다면 더욱 이 방법을 해야할 것 같습니다. ㅡㅡ^.



아직 모든 장을 만나본 것은 아니지만, 간단하게 처리할 수 있는 비지니스 로직은 함수형프로그래밍으로, 보다 재사용성이 높고 복잡도가 있는 작업은 기존 객체지향적인 사고가 필요하지 않을까라는 생각이 드네요. 

(아직 책에서 이야기하는 함수형 사고가 안길러져서 그런 걸 수도 있습니다. ㅡㅡ^)



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


반응형
Posted by N'