람다식(Lambda Expression)

ppp·2025년 7월 21일

Java 공부

목록 보기
12/13
post-thumbnail

람다식이란?

  • 간단히 말해서 메서드를 하나의 식(expression)으로 표현한 것이다. 메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로, 람다식을 익명 함수(anonymous function)라고도 한다.
int[] arr = new int[5];
Arrays.setAll(arr, (i) -> (int)(Math.random() * 5) + 1);
  • 자바에서는 모든 메서드는 클래스에 포함되어야 하므로 클래스도 새로 만들어야 하고, 객체도 생성해야만 비로소 메서드를 호출할 수 있다. 그러나 람다식은 이 모든 과정이 필요없다.

  • 람다식은 값처럼 다룰 수 있어 메서드의 매개변수, 반환값이 될 수 있다. (함수형 프로그래밍)

  • 람다식은 익명 클래스의 객체와 동등하다. (MyFunction은 함수형 인터페이스)

(a, b) -> a > b ? a : b

new MyFunction() {
	int max(int a, int b) {
		return a > b ? a : b;
	}
}

함수형 인터페이스(Functional Interface)

  • 함수형 인터페이스를 구현한 익명 객체를 람다식으로 대체 가능하다. 대체가 가능한 이유는, 람다식도 실제로는 익명 객체이고, 함수형 인터페이스를 구현한 익명 객체의 메서드와 람다식의 매개변수의 타입과 개수 그리고 반환값이 일치하기 때문이다.
// 함수형 인터페이스
@FunctionalInterface
interface MyFunction {
  int max(int a, int b); // 추상 메서드
}

// 1. 함수형 인터페이스를 구현한 익명 객체
MyFunction f = new MyFunction() {
	public int max(int a, int b) {
		return a > b ? a : b;
	}
};
int big = f.max(5, 3); // 익명 객체의 메서드 호출

// 2. 람다식
MyFunction f = (int a, int b) -> a > b ? a : b;
int big = f.max(5, 3) // 익명 객체의 메서드 호출
  • 하나의 메서드가 선언된 인터페이스를 정의해서 람다식을 다루는 것은 기존의 자바의 규칙들을 어기지 않으면서도 자연스럽다. 그래서 인터페이스를 통해 람다식을 다루기로 결정되었으며, 람다식을 다루기 위한 인터페이스를 함수형 인터페이스라고 부르기로 했다.

  • 함수형 인터페이스에는 오직 하나의 추상 메서드만 정의되어 있어야 한다는 제약이 있다. 그래야 람다식과 인터페이스의 메서드가 1:1로 연결될 수 있다.

매개변수와 반환 타입

  • 메서드의 매개변수가 함수형 인터페이스 타입이면 매개변수에 람다식을 전달할 수 있다.
@FunctionalInterface
interface MyFunction {
	void run();
}

static void execute(MyFunction function) {
	function.run();
}

MyFunction f1 = () -> System.out.println("f1.run()");
execute(f1);
  • 메서드의 반환타입이 함수형 인터페이스 타입이면 람다식을 반환할 수 있다.
static MyFunction getMyFunction() {
	MyFunction function = () -> System.out.println("f3.run()");
	return function;
}

java.util.function 패키지

  • java.util.function 패키지에 일반적으로 자주 쓰이는 형식의 메서드를 함수형 인터페이스로 정의되어 있다.

  • 매번 새로운 함수형 인터페이스를 정의하지 말고, 가능하면 이 패키지의 인터페이스를 활용하는 것이 좋다. 그래야 함수형 인터페이스에 정의된 메서드 이름도 통일되고, 재사용성이나 유지보수 측면에서 좋다.

Supplier<Integer> s = () -> (int)(Math.random() * 100) + 1;
Consumer<Integer> c = i -> System.out.print(i + ", ");
Predicate<Integer> p = i -> i % 2 == 0;
Function<Integer, Integer> f = i -> i / 10 * 10;
함수형 인터페이스메서드 시그니처설명
Runnablevoid run()매개변수와 반환값이 모두 없음
Supplier<T>T get()매개변수는 없고, 반환값만 있음
Consumer<T>void accept(T t)매개변수만 있고, 반환값은 없음
Function<T, R>R apply(T t)하나의 매개변수를 받아 결과를 반환
Predicate<T>boolean test(T t)하나의 매개변수를 받아 조건을 판별 (boolean 반환)
BiConsumer<T, U>void accept(T t, U u)두 개의 매개변수를 받아 처리하고 반환값은 없음
BiFunction<T, U, R>R apply(T t, U u)두 개의 매개변수를 받아 하나의 결과를 반환
BiPredicate<T, U>boolean test(T t, U u)두 개의 매개변수를 받아 조건을 판별 (boolean 반환)
UnaryOperator<T>T apply(T t)Function의 특수 형태. 입력과 출력의 타입이 동일
BinaryOperator<T>T apply(T t1, T t2)BiFunction의 특수 형태. 두 입력과 결과의 타입이 동일
  • 다른 함수형 인터페이스가 필요하다면 직접 만들어서 써야한다.

Predicate의 결합

  • 여러 Predicate를 and(), or(), negate()로 연결해서 하나의 새로운 Predicate로 결합할 수 있다.
Predicate<Integer> p = i -> i < 100;
Predicate<Integer> q = i -> i < 200;
Predicate<Integer> r = i -> i % 2 == 0;
Predicate<Integer> notP = p.negate();

System.out.println(notP.and(q.or(r)).test(150)); // true
  • static 메서드인 isEqual()은 두 대상을 비교하는 Predicate를 만들 때 사용한다.
String str1 = "abc";
String str2 = "abc";

Predicate<String> p2 = Predicate.isEqual(str1);
System.out.println(p2.test(str2)); // true

메서드 참조

  • 하나의 메서드만 호출하는 람다식은 ‘클래스이름::메서드이름’ 또는 ‘참조변수::메서드이름’으로 바꿀 수 있다.
Function<String, Integer> f = (String s) -> Integer.parseInt(s); // 람다식
Function<String, Integer> f = Integer::parseInt // 메서드 참조

// 생성자의 메서드 참조
// 매개변수 없을 때
Supplier<MyClass> s = () -> new MyClass(); // 람다식
Supplier<MyClass> s = MyClass::new; // 메서드 참조

// 매개변수 1개일 때
Function<Integer, MyClass> f = (i) -> new MyClass(i); // 람다식
Function<Integer, MyClass> f = MyClass::new; // 메서드 참조

// 매개변수 2개일 때
BiFunction<Integer, String, MyClass> bf = (i, s) -> new MyClass(i, s); // 람다식
BiFunction<Integer, String, MyClass> bf = MyClass::new; // 메서드 참조

// 배열 생성
Function<Integer, int[]> f = x -> new int[x]; // 람다식
Function<Integer, int[]> f = int[]::new // 메서드 참조

0개의 댓글