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> |
T |
boolean |
Has this album been released yet? |
Consumer<T> |
T |
void |
Printing out a value |
Function<T,R> |
T |
R |
Get the name from an Artist object |
Supplier<T> |
None |
T |
A factory method |
UnaryOperator<T> |
T |
T |
Logical not(!) |
BinaryOperator<T> |
(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 '+' 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 |