본문 바로가기

나만의 작은 공간/Books

[Java 8 Lambda] Chapter 2 Lambda Expression





Your First Lambda Expression

Swing과 같은 Java GUI 라이브러리에서는 아래와 같은 방식을 액션 처리를 한다. 

button.addActionListener( new ActionListener() { 
              public void actionPerformed(ActionEvent  event){
                  System.out.println("button Click!"); } }); 

그러나 람다를 사용하면 아래와 같이 한 줄에 간단히 표현이 가능하다.

button.addActionListener( event -> System.out.println("button Click!"));

위의 두 코드를 비교해보면, 위의 전통적인 리스너 동작을 수행하기 위해서 파라미터의 타입을 지정해주어야만 했다. 

그러나 아래 람다식을 이용한 코드에서는 파라미터인 event 의 타입 정보가 없다. 

왜서 그럴가?

이는 java compiler가 이 파라미터의 타입을 추론하기 때문이다. 즉 addActionListener라는 메서드를 사용할 때 이 메서드의 파라미터 타입은 ActionEvent라는 것을 내부적으로 추론해주는 것이다. 



How to Spot a Lambda in a Haystack

람다식을 표현하는 데는 여러 가지 다양한 방식이 있다. 

Runnable noArguments = () -> System.out.println("Hello World");   // 1. 

ActionListener oneArgument =  event -> System.out.println("button clicked");   //2

Runnable multiStatement = () -> {  //3

System.out.print("Hello");                                                               

System.out.println(" World");

};

BinaryOperator<Long> add = (x, y) -> x + y;  // 4

BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y; // 5 


1.  첫번째 방식은 아규먼트 즉 파라미터가 없는 람다식을 표현하는 방식이다. 

    파라미터가 없으면  () 로써 파라미터를 대신할 수 있다. Runnable 인터페이스에서는 오직 하나의 run 에서드가 있는데, 이 run 메서는 파라미터가 없으며 또한 void 형이다. 이런 경우에 첫번째 람다 표현식을 사용할 수 있다. 

2. 파라미터가 하나 있는 람다 표현식이다. 위의 예제와 동일하다. 

3.  ({}) 은 코드 블럭을 가리킨다. 

  이 코드 블럭 내에서 값을 리턴하거나, 혹은 익샙션을 발생시킨다거나 하는 작업을 수행할 수 있다. 

4. 여러 개의 파라미터가 있을 때의 람다식이다. 

  이 람다식은 파라미터로 들어오는 두 값을 더해주지 않는다. 단지 두 값을 받아서 더해주는 함수를 만들어줄 뿐이다. 만들어진 이 함수는 add 라는 변수에 저장된다. 

5. 때로는 파라미터의 타입을 직접 기재해주는 것이 좋은 경우들이 있다. 

가독성의 경우 때문일가? 

위의 모든 람다식 예제에서 공통으로 볼 수 있는 것은 람다 식의 타입은 context dependent 하다는 것이다. 이런 개념은 자바에서 새로운 개념이 아니다. 아래와 같은 예제를 보면 알 수 있다. 

final String[] array = {"Hello", " world"};

위의 예제에서 array의 타입은 내부적으로 우측의 값에 의해서 추론되는 것이다. 따라서 이렇게 배열을 선언해도, 컴파일이 가능한 것이다 .



Using Values

이전의 방식대로 코딩할 경우, 메소드 내부에서 inner Class Method 외부에서 선언된 변수를 사용 할 경우를 만나게 될 것이다. 이런 경우 메서드 밖의 변수의 접근자를 final 로 지정하는 것으로 문제를 해결했다. 

final String name = getUserName();

button.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent event) {

System.out.println("hi " + name);

}

});

이러한 restriction은 java8에서 어느 정도 해소가 되었다. java 8 에서는 변수의 접근자를 final 로 지정해주지 않아도 된다. 

하지만, 효과적인 측면에서 final 접근자 지정이 불가피 하다. 왜냐하면 람다식 내부에서 외부의 변수를 사용하려고 하면, final 로 접근자 지정을 해야지 접근이 가능하기 때문이다. 

만약 , 변수에 값을 여러 번 할당하고 또 람다 식 내부에서 해당 변수를 사용하면, compiler 애러가 있을 것이다.  

예를 들어 아래와 같다. 

String name = getUserName();

name = formatUserName(name);

button.addActionListener(event -> System.out.println("hi " + name));

이러한 이유 때문에 람다식을 "Closure"로 이해하는 사람들이 있기도 하다. 

모든 람다식은 전부 statically typed 하다. 이러한 람다식들의 타입들을 Functional Interfaces 라고 한다. 



Functional Interfaces: 오직 하나의 메서드만 포함하는 인터페이스로서 람다 표현식의 타입을 표현하는 데 사용된다. 



아래는 위에서 사용한 Functional Interface 들이다. 

public interface ActionListener extends EventListener {

public void actionPerformed(ActionEvent event);

}

위의 ActionListener는 하나의 파라미터와 return 값이 없는 형식이나, 파라미터나 리턴값이 있는 Functional Interface를 정의할 수도 있다. 

아래는 자주 사용되는 자바 Built-in Functional Interface 이다. 

 Interface Name

 Arguments 

Returns 

Example 

 Predicate<T>

boolean 

Has this album been released yet? 

 Consumer<T>

void 

Printing out a value 

 Function<T,R>

Get the name from an Artist object 

 Supplier<T>

None 

A factory method 

UnaryOperator<T> 

Logical not(!) 

BinaryOperator<T> 

(T, T) 

Multiplying two numbers(*) 



Type Inference

람다식에서 사용하는 타입 추론은 java 7에서의 타겟 타입추론의 확장이다. target Type Inference 의 예제는 아래와 같다. 

Map<String,String> old = new HashMap<String,String>();

Map<String,String> diamond = new HashMap<>();

아리의 자바 코드는 diamond operator 를 사용하였다. 그럼에도 컴파일 에러가 발생하지 않는 것은 타입 추론을 하기 때문이다. 

아래와 같은 사용법도 가능하다. 

useHashMap(new HashMap<>());

...

private void useHashmap(Map<String, String> values);

java 7에서 생성자의 제네릭 타입을 기입하지 않아도 됐었던 것처럼, java 8 에서는 람다식의 타입을 기입하지 않아고 변수로 받을 수 있도록 하게 하였다. 

아래와 같은 예제를 보자. 

1. test

Predicate<Integer> atLeast5 = x -> x > 5;

public interface Predicate<T> {

boolean test(T t);

}

2.  number의 계산을 위한 인터페이스

BinaryOperator<Long> addLongs = (x, y) -> x + y;

위의 람다식에서 타입정보를 제거하면 컴파일 에러가 발생한다. 

BinaryOperator addLongs = (x, y) -> x + y; // 엑스 

Operator '&#x002B;' cannot be applied to java.lang.Object, java.lang.Object



Key Point 


1. 람다식은 이름을 지정하지 않은 행위(behavior)를 정의하는 메서드로서 마치 이름을 정의하지 않는 데이터를 정의하는 메서드와 비슷하다. 

2. 람다식은 아래와 같이 생겨먹었다 .

BinaryOperator<Integer> add = (x, y) → x + y.

3. Functional Interface는 오직 하나의 메서드를 가지는 인터페이스로서 람다식의 타입을 정의한다. 








'나만의 작은 공간 > Books' 카테고리의 다른 글

오라클 SQL과 PL/SQL을 다루는 기술  (0) 2020.09.22
[Java 8 Labmda] 스트림  (0) 2017.04.21
세계문학전집 어떻게 살 것인가?  (0) 2017.04.19
수학의 눈을 찾아라  (0) 2016.10.10