함수형 프로그래밍(functional programming) : 함수를 정의하고 이 함수를 데이터 처리부로 보내 데이터를 처리하는 기법
데이터 처리부는 데이터만 가지고 있고, 처리 방법이 정해져 있지 않아 외부에서 제공된 함수에 의존한다. 제공된 함수의 입력값으로 데이터를 넣고 함수에 정의된 처리 내용을 실행한다. 동일한 데이터라도 함수A를 제공해서 처리하는 결과와 함수B를 제공해서 처리하는 결과는 다를 수 있다.
➡ 함수형 프로그램의 특징, 데이터 처리의 다형성
람다식 : (매개변수, ...) -> {처리 내용}
람다식은 데이터 처리부에 제공되는 함수 역할을 하는 매개변수를 가진 중괄호 블록이다. 데이터 처리부는 람다식을 받아 매개변수에 데이터를 대입하고 중괄호를 실행시켜 처리한다.
자바는 람다식을 익명 구현 객체로 변환한다. 인터페이스의 익명 구현 객체를 람다식으로 표현해보자.
public interface Calculable {
void calculate(int x, int y);
}
// Calculabe 인터페이스의 익명 구현 객체
new Calculable() {
@Override
public void calculate (int x, int y) { 처리내용 }
};
// 람다식으로 표현하기
(x, y) -> { 처리 내용 };
람다식은 인터페이스의 익명 구현 객체이므로 인터페이스 타입의 매개변수에 대입될 수 있다. 예를 들어 Calculable 매개변수를 가진 action() 메소드를 람다식으로 변경해서 작성해보자.
public void action(Calculable calculable) {
int x = 10;
int y = 4;
calculable.calculate(x, y);
} // action 메소드
// 람다식으로 변경
action( (x, y) -> {
int reulst = x + y;
System.out.println(result);
}
여기서 action() 메소드는 제공된 람다식을 이용해서 내부 데이터를 처리하는 처리부 역할을 한다.
인터페이스가 단 하나의 추상 메소드를 가질 때, 이를 함수형 인터페이스(functional interface)라고 한다. 인터페이스가 함수형 인터페이스임을 보장하기 위해서는 @FunctionalInterface 어노테이션을 붙이면 된다. 이는 선택사항이지만, 컴파일 과정에서 추상 메소드가 하나인지 검사하기 때문에 정확한 함수형 인터페이스를 작성할 수 있게 도와주는 역할을 한다.
함수형 인터페이스의 추상 메소드에 매개변수가 없을 경우 람다식은 다음과 같이 작성할 수 있다. 실행문이 두 개 이상일 경우에는 중괄호를 생략할 수 없고, 하나일 경우에만 생략할 수 있다.
( ) -> {
실행문;
실행문;
}
// 실행문이 하나일 경우
( ) -> 실행문
함수형 인터페이스의 추상 메소드에 매개변수가 있을 경우 다양한 방법으로 람다식을 작성할 수 있다. 매개변수를 선언할 때 타입은 생략할 수 있고, 구체적인 타입 대신에 var를 사용할 수도 있다. 하지만 타입을 생략하고 작성하는 것이 일반적이다.
(타입 매개변수, ... ) -> 실행문
(var 매개변수, ... ) -> 실행문
(매개변수, ...) -> 실행문
함수형 인터페이스의 추상 메소드에 리턴값이 있을 경우 람다식은 다음과 같이 작성할 수 있다. return 문 하나만 있을 경우에는 중괄호와 함께 return 키워드를 생략할 수 있다. 리턴값은 연산식 또는 리턴값 있는 메소드 호출로 대체할 수 있다.
(매개변수, ... ) -> {
실행문;
return 값;
}
메소드 참조는 말 그대로 메소드를 참조해서 매개변수의 정보 및 리턴 타입을 알아내 람다식에서 불필요한 매개변수를 제거하는 것을 목적으로 한다. 두 개의 값을 받아 큰 수를 리턴하는 Math.max()의 정적 메소드를 호출하는 람다식은 다음과 같다.
(left, right) -> Math.max(left, right);
람다식은 단순히 두 개의 값을 Math.max() 메소드의 매개값으로 전달하는 역할만 하기 때문에, 이 경우에는 다음과 같이 메소드 참조를 이용하면 매우 깔끔하게 처리할 수 있다.
Math :: max;
정적 메소드를 참조할 경우에는 클래스 이름 뒤에 :: 기호를 붙이고 정적 메소드 이름을 기술한다.
인스턴스 메소드일 경우에는 먼저 객체를 생성한 다음 참조 변수 뒤에 :: 기호를 붙이고 인스턴스 메소드 이름을 기술한다.
클래스 :: 메소드 / 참조변수 :: 메소드
람다식에서 제공되는 a 매개변수의 메소드를 호출해서 b 매개변수를 매개값으로 사용하는 경우도 있다.
(a, b) -> { a.instanceMetod(b); }
이것을 메소드 참조로 표현하면 클래스 :: instanceMethod 가 된다. 작성 방법은 정적 메소드 참조와 동일하지만, a의 인스턴스 메소드가 사용된다는 점에서 다르다.
생성자를 참조한다는 것은 객체를 생성하는 것을 의미한다. 람다식이 단순히 객체를 생성하고 리턴하도록 구성된다면, 람다식을 생성자 참조로 대치할 수 있다.