[Java] 람다식 (Lambda expression)
모든 걸 객체로 생각하는 객체지향언어인 자바가 JDK1.8부터 람다식을 추가함으로써 자바는 객체지향언어인 동시에 함수형 언어가 됐다. 람다식을 어떻게 사용하는지에 초점을 맞춰 공부해보자.
함수(자바에서는 메서드)를 다룰 때 세 가지 특징이 있다.
1. 함수 자체를 변수로 저장할 수 있다.
2. 함수를 다른 함수의 매개변수로 쓸 수 있다.
3. 함수 자체를 반환할 수 있다.
위의 세 가지 특징을 만족하는 함수를 일급함수라고 한다.
함수형 언어는 일급함수를 지원하고, 지원하기 위한 다양한 특징이 있다.
자바도 일급함수를 지원하고 싶었고, 이 생각에 기반해 람다식이 등장했다.
함수 자체를 변수로 저장해야 하는데, 함수 자체에도 이름이 있다. 그렇기에 변수의 이름과 함수의 이름이 충돌이 있을 수 있다. 이를 해결하기 위해 이름 없는 함수를 만들고, 변수에 이 함수를 저장하자~ 라는 아이디어가 나왔고 여기서 이름없는 함수를 람다식이라고 이름붙였다.
람다식은 간단하게 메서드를 하나의 식으로 표현한 것이다. 함수를 간략하고 직관적으로 표현할 수 있게 해 주며, 메서드와 달리 객체를 생성하지 않고도 바로 사용할 수 있다는 장점이 있다.
(메서드와 함수는 같은 의미이지만, 메서드는 특정 클래스에 반드시 속해야 한다는 제약 때문에 함수와 다르다.)
왼쪽 함수의 이름은 max이고, 이 함수를 람다식으로 변형하면 오른쪽처럼 이름 없는 함수가 된다.
람다식으로 변형하는 방법에 대해 알아보자.
1. 메서드의 이름과 반환타입을 제거하고 ->를 {} 앞에 추가한다. (반환타입을 안붙여도 컴파일러가 알아서 해석함)
2. 반환값이 있으면 식이나 값만 적고 return문을 생략할 수 있다.
세미콜론을 붙이지 않는다. 조심.
보통 람다식을 쓸 때 이런 형식으로 사용한다. 한 줄 밖에 안써서 중괄호 {} 도 생략했다.
3. 매개변수의 타입이 추론할 수 있으면 생략할 수 있다. (대부분 생략 가능)
람다식을 가장 간단하게 작성한 경우이다.
타입을 붙일거면 다 붙이고, 붙이지 않을 거면 다 붙이지 않는게 좋다.
람다식 작성 시 유의사항
1. 매개변수가 하나이고 타입이 없는 경우에는 괄호 () 를 생략할 수 있다.
매개변수가 없는 함수일 경우에는 빈 괄호를 사용함. ex. () -> System.out.println();
2. 문장이 한 줄 짜리면 {}를 생략할 수 있는데, 하나뿐인 문장이 return문이면 {}를 생략할 수 없다. (이 경우 ; 도 생략할 수 없음)
람다식의 의도를 고려했을때, 리턴을 안쓰는게 더 합리적이다.
여러 가지 람다식을 살펴보자.
메서드와 람다식을 살펴보며 앞에서 배운 것들을 확인하자.
일급함수의 첫 번째 조건으로 함수 자체를 변수에 넣을 수 있다고 하네? 변수에 람다식을 대입해주고 싶은데... 그러면 변수의 타입은 어떻게 정의해야 할까?
함수의 리턴값을 대입하는게 아니라 함수 자체를 변수에 넣어야 하는데... 자바는 함수형 언어와 좀 다르고... 라는 생각을 할 수 있다.
자바는 람다식을 일종의 클래스에 속한 값. 클래스에 속한 메서드로 생각해주는데, 이 클래스를 특별히 함수형 인터페이스라고 부른다.
명확한 클래스는 아니고, 추상적인 클래스. 추상적인(abstract) 메서드만 정의돼있는 함수형 인터페이스에 속한 어떤 객체로 람다식을 정의하고, 여기에 람다식을 저장한다.
즉, 함수형 인터페이스를 정의하고 람다식을 다룰 때 기존 자바의 규칙들을 어기지 않으면서도 자연스럽게 처리된다.
함수형 인터페이스 : 단 하나의 추상 메서드만 정의돼있는 인터페이스 (하나의 추상 메서드만 정의돼있어야 람다식과 인터페이스의 메서드가 1:1로 연결된다.)
이 추상메서드는 인터페이스가 저장할 람다식의 형식을 저장한다.
여기서 f는 어떤 클래스의 객체라고 볼 수 있지만, 여기서는 함수형 인터페이스이기 때문에 함수를 저장한 것으로 생각해 줄 수 있다. (함수를 저장하고 싶은데, 그게 안되니까 추상메서드 하나 선언된 인터페이스를 함수로 봄)
굉장히 부자연스럽고 어거지로 맞춰간다는 느낌이 들 수 있는데, 일단 최대한 이해하고 받아들이고 넘어가자.
위의 함수를 람다식으로 재정의하면
(f는 참조변수 뒤의 람다식은 참조변수가 참조하는 람다식.)
위와 같은 형식으로 표현할 수 있다.
중요한 건 람다식을 함수형 인터페이스 변수에 저장할 수 있다는거다.
Comparator 인터페이스도 함수형 인터페이스로 생각할 수 있어 이것 또한 람다식으로 교체할 수 있다.
일급함수의 첫 번째 조건은 알겠고, 두번째와 세 번째 조건은 어떻게 처리해야 할까?
함수 자체를 함수형 인터페이스에 속한 Object로 생각했기 때문에, 그 Object를 Return 하고 매개변수에 넣어주는 방식으로 처리할 수 있다.
코드를 통해 이해하자.
참조변수 없이 직접 람다식을 매개변수로 지정하는 등 여러 예시를 볼 수 있다.
아~ 자바에서 일급함수를 구현하기 위해서 함수형 인터페이스를 구현하고 그 함수형 인터페이스에 속한 유일한 메서드를 세부정의함으로써 람다식을 변수에 저장할 수 있고, 매개변수로 쓸 수도 있고, 반환도 할 수 있구나~~ 라고 이해하면 잘 이해한거다.
짜깁기 느낌이 강하게 든다. 아다리는 딱 맞아 떨어지는데 이래도 되나? 같은 느낌... 하지만 괜찮다.
함수형 인터페이스로 람다식을 참조할 수 있지만, 람다식의 타입이 함수형 인터페이스의 타입과 일치하는 건 아니다. 람다식은 익명 객체이고, 타입이 없다. (타입이 있지만 컴파일러가 임의로 이름을 정해서 정확히 알 수 없다)
대입 연산자의 양변의 타입을 일치시키기 위해 형변환이 필요하다.
(Object로는 형변환이 안됨. 단, (Object)(MyFunction)은 가능)
java.util.function 패키지에 자주 쓰이는 함수형 인터페이스를 모아뒀다.
위와 같은 형식을 따를 경우, 이미 만들어진 함수형 인터페이스를 사용하자.
Predicate 인터페이스를 사용한다고 생각해보자.
Predicate<String> isEmptyStr = s -> s.length() ==0;
String s "";
if(isEmptyStr.test(s)) // if(s.length() == 0)
System.out.println("This is an empty String.");
메서드 이름을 호출해 사용하자.
+ 자바의 경우 함수형 언어가 좀 부자연스럽게 결합된 느낌이 있는데, 좀 더 자연스럽게 결합된 언어로 Scala 언어가 있다.
'Programming Language > Java' 카테고리의 다른 글
[Java] 객체지향 요약 (0) | 2021.10.29 |
---|---|
[Java] 스트림 (Stream) (0) | 2021.10.25 |
[Java] 컬렉션 프레임웍 (Collections Framework) (0) | 2021.10.19 |
[Java] 열거형 (enums) (0) | 2021.10.18 |
[Java] 지네릭스 (Generics) (0) | 2021.10.17 |
댓글
이 글 공유하기
다른 글
-
[Java] 객체지향 요약
[Java] 객체지향 요약
2021.10.29 -
[Java] 스트림 (Stream)
[Java] 스트림 (Stream)
2021.10.25 -
[Java] 컬렉션 프레임웍 (Collections Framework)
[Java] 컬렉션 프레임웍 (Collections Framework)
2021.10.19 -
[Java] 열거형 (enums)
[Java] 열거형 (enums)
2021.10.18