2012년 1월 26일 목요일

Java 8의 Project Lambda를 알아보자.


Java 8 - Project Lambda

project lambda란?: Project lambda 는 lambda표현식을  자바 문법에 맞춰서 사용가능하게 만드는 프로젝트입니다.
lambda 표현식은 Lisp같은 함수형 언어 문법을 사용하는것입니다.
대표적으로 Groovy는 closures로 대표되는 lambda표현식을 지원하게 해주는 java 계열의 언어입니다.

그렇다면 lambda 표현식이란 무엇인가?
그것은 코드 블록간에  값을 할당하거나 메서드에 인수로 할당하거나 다른 lambda 표현식으로 전달할수 있게 해주는것입니다.
해당 코드는 필요할때마다 불러올수가 있습니다.
이것의 주 모티브는 JAVA에서 API사용자로 하여금 코드에 필요한 특정 API사용하여 보일러 플레이트 코드를 제거하는것입니다.
그러나 그건 단지 자바 구문 요구사항을 내부 클래스를 사용하여 종료하는것에 불과한것입니다.
이런 대부분의 일반적인 API는 자바 쓰레딩 API로써, 우리가 새로운 쓰레드를 실행할때 필요한 API를 말하는것입니다.
그러나 결국 Runnable이 implementing(구현) 되야합니다.


이 규격은 아직도 아직도 지속적으로 발전하고 있다. 

함수형 인터페이스 : 자바 스펙 개발자들은 JVM 스펙을 좀처럼 수정할려 하지 않습니다. 마찬가지로 이 경우에도 그렇습니다.
lambda의 경우 이러한 JVM 스펙에 수정없이 구현할수 있도록 정의하고 있습니다. 
그러므로 당신은 1.8 버전의 소스 클래스 코드를 목표하는 1.5버전에 쉽게 컴파일 할수 있습니다.
그래서 lambda 코드는 하나의 메소드를 가진 인터페이스를 implementing한 익명클래스에 보관됩니다.


물론 그 인터페이스는  하나 이상의 메서드를 가질수는 있지만 그것은 하나의 메서드를 정의하는 클래스에 implementable 되야 합니다.

그러면 코드를 통해서 lambda에 대해서 살펴 보도록 합시다.

//단순한 경우로, 오직 하나의 메서드만을 가지고 있다.
//이것은 함수형 인터페이스이다.
public interface Test1{
    public void doSomething(int x);
}

//두개의 메서드를 가지고 있다 하지만 toString()의 경우는 기존에 있는것이다.
//어떤 오브젝트던지간에 java.lang.Object의 서브클래스이다.
//이것은 함수형 인터페이스이다.
public interface Test2{
    public void doSomething(int x);
    public String toString();
}

//이 메서드는 Test1의 메서드를 Test3에 오버라이드 한것이다. 
//그래서 아직은 함수형 인터페이스이다.
public interface Test3 extends Test1{
    public void doSomething(int x);
}

//이것은 함수형 인터페이스가 아니다. 
//이 경우, 명시적으로 두가지 메서드를 구현해야한다.
public interface Test4 extends Test1{ 
    public void doSomething(long x);
}


Lambda 표현식: 
자바8에서 지원되는것으로 lambda표현식은 단지 함수형 인터페이스들을 익명 클래스에 implement하는 문법과 구현 방법이 다릅니다.
lambda 구문을 구현하는 방법을 보도록합시다.

argumentList -> body 

저 argumentList는 단순히 자바 메서드 argument 리스트이다.
만약에 하나의 argument만 있다면 ()는 선택사항입니다. 또한 agument의 형에 대한 선언 부분도 선택사항입니다. 
이경우 형이 없습니다. 자동으로 추정합니다.
body의 경우 표현적 body와 코드 블락 body으로 두개의 타입이 있습니다.
표현적 body의 경우 단순히 자바 표현에 따라 값을 리턴해줍니다.
코드 블락 body의 경우 메서드 바디만을 포함하고 있습니다.
이 코드 블락 body는 필수적으로 중괄호 한쌍(]])을 포함해야 합니다.

하나 Thread에 lambda 구문을 구현한 예제를 봅시다.

//이 쓰레드는 Hello를 출력하게 됩니다.
new Thread(() -]]> { while(true){ System.out.println("Hello")}}).start();


위의 예제에 이어서 또 다른 예제를 보자면

public interface RandomLongs{
    public long randomLong();
}

RandomLongs randomLongs () -]]> ((long)(Math.random()*Long.MAX_VALUE));
System.out.println(randomLongs.randomLong());


Generic 과  lambda:  그러나 만약에 제네릭 메서드를 lambda에 implement한다면? 이에 표준 개발자들은 형 파라미터를  타입 인자전에 정의할수 있도록 멋진 구현식을 제공했습니다.

예를 보자면 이렇습니다.

public interface NCopies{
    public <extends Cloneable]]> List<T]]> getCopies(T seed, int num);
}

//제네릭 메서드의 인자에 타입 유추기능을 제공한다.
NCopies nCopies = <extends Cloneable]]> (seed, num) -]]> { 
                    List<T]]> list new ArrayList<]]>()
                    for(int i=0; i<num; i++) 
                        list.add(seed.clone())
                    return list;
                };


요점 요약 : 람다 표현식에 의해 구현된 실제 인터페이스 및 방법은 그것이 사용되는 시점에 따라 달라집니다.
컨텍스트도 할당 작업의 존재 또는 메서드 호출에서 매개 변수의 전달하여 설정하실 수 있습니다
컨텍스트 없이는  lambda 표현식은  의미가 없습니다. 단순히 lambda 표현식에 직접 하나의 메소드를 호출하는 목적이라면 맞지 않습니다. 

컴파일 에러를 내게되는 예를 들어보자면

public interface NCopies{
    public <extends Cloneable]]> List<T]]> getCopies(T seed, int num);
}

//이 코드는 컴파일 에러를 냅니다.
//lambda에 context가 없어서 그 의미가 없기 때문입니다.
(<extends Cloneable]]> (seed, num) -]]> { 
                    List<T]]> list new ArrayList<]]>()
                    for(int i=0; i<num; i++) 
                        list.add(seed.clone())
                    return list;
                }).getCopies(new CloneableClass(), 5);


그래서 옳게 쓰려면 아래와 같이 바꿔줘야합니다.

NCopies nCopies = <extends Cloneable]]> (seed, num) -]]> { 
                    List<T]]> list new ArrayList<]]>()
                    for(int i=0; i<num; i++) 
                        list.add(seed.clone())
                    return list;
                };
                
nCopies.getCopies(new CloneableClass(), 5);

댓글 없음:

댓글 쓰기