람다식(Lamda Expression)이란 함수를 하나의 식(expression)으로 표현한 것이다. 함수를 람다식으로 표현하면 메소드의 이름이 필요 없기 때문에, 람다식은 익명 함수(Anonymous Function)의 한 종류라고 볼 수 있다.
기존 방식으로 함수를 선언할 때
// 기존의 방식
반환티입 메소드명 (매개변수, ...) {
실행문
}
// 예시
public String hello() {
return "Hello World!";
}
// 람다 방식
(매개변수, ... ) -> { 실행문 ... }
// 예시
() -> "Hello World!";
이제 우리는 람다식으로 순수 함수를 선언할 수 있게 되었다. 하지만 Java는 기본적으로 객체지향 언어이기 때문에 순수 함수와 일반 함수를 다르게 취급하고 있으며, Java에서는 이를 구분하기 위해 함수형 인터페이스가 등장하게 되었다.
함수형 인터페이스란 함수를 1급 객체처럼 다룰 수 있게 해주는 어노테이션으로, 인터페이스에 선언하여 단 하나의 추상 메소드만을 갖도록 제한하는 역할을 한다. 함수형 인터페이스를 사용하는 이유는 Java의 람다식이 함수형 인터페이스를 반환하기 때문이다.
기존 방식으로 익명 함수 구현할 때
public class Lambda {
public static void main(String[] args) {
// 기존의 익명함수
System.out.println(new MyLambdaFunction() {
public int max(int a, int b) {
return a > b ? a : b;
}
}.max(3, 5));
}
}
@FunctionalInterface
interface MyLambdaFunction {
int max(int a, int b);
}
public class Lambda {
public static void main(String[] args) {
// 람다식을 이용한 익명함수
MyLambdaFunction lambdaFunction = (int a, int b) -> a > b ? a : b;
System.out.println(lambdaFunction.max(3, 5));
}
}
Supplier는 매개변수 없이 반환값 만을 갖는 함수형 인터페이스이다.
Supplier는 T get()을 추상 메소드로 갖고 있다.
// 정의
@FunctionalInterface
public interface Supplier<T> {
T get();
}
// 사용 예시
Supplier<String> supplier = () -> "Hello World!";
System.out.println(supplier.get());
// 정의
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
// 예시
Consumer<String> consumer = (str) -> System.out.println(str.split(" ")[0]);
consumer.andThen(System.out::println).accept("Hello World");
// 출력
Hello
Hello World
// 정의
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
// 예시, 메소드 참조로 간소화 가능(String::length;)
Function<String, Integer> function = str -> str.length();
function.apply("Hello World");
// 정의
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
// 예시
Predicate<String> predicate = (str) -> str.equals("Hello World");
predicate.test("Hello World");