[Java] 람다식(Lambda Expression)

최지수·2022년 5월 5일
0

Java

목록 보기
26/27
post-thumbnail

람다식(lambda Expression)

람다식Lambda Expression은 메서드를 하나의 식으로 표현한 것이에요. 그리고 이로써 자바도 '메서드'가 아닌 '함수'를 구현할 수 있게 되었어요. 객체지향개념에서 '메서드'는 일종의 객체의 행위를 뜻하는 의미로, 기존 자바에선 클래스 내에서 '메서드'를 구현해야 하기 때문에 클래스에 종속적이에요. 이에 비해 람다는 객체에 종속되지 않고 독립적인 행위, 즉 함수function를 정의하기 때문에 자바는 메서드 뿐만 아니라 함수도 구현할 수 있게 되었어요.

작성법

단계적으로 진행해볼게요. 람다식은 자바의 '익명 함수'와 동일해요. 이 익명 함수를 람다식으로 아래와 같이 표현할 수 있어요.

// 예시를 위한 곱셈 함수
int pow2(int a) {
	return a * a;
}

// 람다식으로 변환, '->'를 붙이고 메서드 명과 반환 타입을 생략할 수 있어요.
(int a) -> {
	return a * a;
}

그리고 반환 값이 있는 함수의 경우, return을 생략하고 식의 연산 결과가 자동으로 반환 값이 되요. 이땐 ;를 붙이지 않아요.

(int a) -> a * a

또한 매개변수 타입이 추론할 수 있는 경우에 생략도 가능해요. 대부분의 경우에 생략이 가능해서 웬만하면 다 생략이 가능해요.

(a) -> a * a

그리고 선언된 매개변수가 하나라면 ()도 생략할 수 있어요. 이 때 매개변수 타입은 존재하면 안되요.

a -> a * a			// 이건 되요
//int a -> a * a	// 이건 안되요

윗처럼 문장이 하나라면 {} 생략이 가능해요. 이 때는 문장 끝에 ;가 붙으면 안되요.

// if문처럼 한 문장에 한해서 
// {}를 생략하는 것과 똑같이 생각하시면 되요
// a -> a * a; a *= 2;

람다식은 함수형 인터페이스

람다식을 함수형 인터페이스를 통해 타입 클래스를 지정할 수 있어요. 함수형 인터페이스란 추상 메서드가 하나만 존재하는 인터페이스에요. 그래서 함수형 인터페이스를 작성하여 아래와 같이 타입 선언이 가능해요.

@FunctionalInterface
interface MyFunc {
    public abstract int func(int a, int b);
}

MyFunc run = (int a, int b) -> { return a * b; };

위 코드에서 선언된 func 추상 메서드는 임의로 붙인 것이라 의미는 없어요. 어쨋든 람다식은 함수형 인터페이스에서 구현한 메서드 func와 매개변수, 타입 그리고 반환 값이 일치하기 때문에 타입 지정이 가능해요. 그렇게 람다식을 변수로 활용할 수 있게 되요. 이땐 함수형 인터페이스에 작성된 메서드를 호출하면 되요.

System.out.println(run.func(4, 2));

미리 선언된 함수형 인터페이스

자바에서 제공하는 디폴트 함수형 인터페이스가 존재해요. 자바에서 제공하는 라이브러리 중에 함수형 인터페이스를 매개변수로 받아 처리하는 것이 존재하기 때문이에요. 해당 인터페이스들은 java.lang.function 패키지에 구현되어 있어요.

Collection 클래스의 removeif가 대표적인 예 중 하나에요.

자바 인터페이스 중 하나인 Predicateboolean 타입을 반환하는 하나의 매개변수를 가진 메서드를 가져요. 이렇게 설계되었기 때문에 아래처럼 간단한 조건문을 작성해서 코드를 작성할 수 있어요.

List<Integer> list = new ArrayList<Integer>();
// 원소가 2의 배수인 경우 제거해요.
list.removeIf((a) -> a % 2 == 0);

자주 쓰이는 인터페이스를 나열해 봤어요.

인터페이스메서드설명
java.lang.Runnablevoid run()매개변수 X, 반환 X
Supplier<T>T get()매개변수 X, 반환 O
Consumer<T>void accept(T t)매개변수 O, 반환 X
Function<T, R>R apply(T t)매개변수 O, 반환 O
Predicate<T>boolean test(T t)매개변수 O, boolean 반환

그리고 매개변수가 2개인 인터페이스도 있어요. 위 함수 앞에 Bi라는 접두어가 붙어요.

인터페이스메서드설명
BiConsumer<T, U>void accept(T t, U u)매개변수 O, 반환 X
BiFunction<T, U, R>R apply(T t, U u)매개변수 O, 반환 O
BiPredicate<T, U>boolean test(T t, U u)매개변수 O, boolean 반환

3개 이상부턴 직접 제작해야 해요자바 라이브러리에선 안쓰는가 봅니다 ㅎㅎ. 구현 방식은 아래와 같겠죠?

@FunctionalInterface
public interface TriFunction<T, U, V, R> {
	public abstract R apply(T t, U u, V v);
}

Function 인터페이스의 변형으로 UnaryOperatorBinaryOperator가 있어요. 매개변수의 타입과 반환타입이 모두 일치한다는 차이가 있어요.

인터페이스메서드
UnaryOperator<T>T apply(T t)
BinaryOperator<T>T apply(T t1, T t2)

위에 있는 인터페이스는 지네릭 타입을 활용한 인터페이스들이에요. 그래서 기본형 타입을 사용할 수 있는 인터페이스도 존재해요.

인터페이스메서드설명
DoubleToIntFunctionint applyAsInt(double d)AToBFunction, A 타입을 B타입으로 변환해줘요
ToIntFunction<T>int applyAsInt(T t)ToAFunction, 모든 타입 매개변수를 A로 변환해줘요
IntFunction<R>R apply(T t, U u)AFunction, 입력은 A 타입, 출력은 R 타입
ObjIntConsumer<T>void accept(T t, U u)ObjAFunction, 입력은 A 타입, 출력은 없어요

함수형 인터페이스는 추상 메서드가 오직 하나만 있어야 하지만, static이나 default 메서드는 제약이 없어요. 이 점을 활용해 Predicate는 일부 메서드를 구현해 Predicate간에 결합이 가능해요. 기본적으로 and, or, negate(not)가 구현되어 있어요.

Predicate<Integer> p = i -> i < 100;
Predicate<Integer> q = i -> i < 200;
Predicate<Integer> r = i -> i % 2 == 2;

Predicate<Integer> notP = p.negate();
Predicate<Integer> lessThan200AndMultFor2 = p.and(r);

람다식은 극한으로 요약을 할 수 있어서 클래스의 메서드를 활용할 수 있어요. 먼저 문자열을 정수형으로 변환하는 람다식을 작성해봐요.

Function<String, Integer> f = (String s) -> Integer.parseInt(s);

람다식은 거추장스러운 래핑wrapping 작업 없이 직접 메서드를 호출하는 것이 가능해요.

// 이렇게 매개변수를 지정하지 않아도 되요. '.'대신 '::'를 써야해요
Function<String, Integer> f = Integer::parseInt;
BiFunction<Integer, Integer, Boolean> f = Integer::equals;

이를 정리하면 아래와 같아요.

종류람다메서드 참조
클래스 메서드(x) -> {class}.method(x){class}::method
멤버 메서드(x) -> {class}.method(x){class}::method
인스턴스 메서드(x) -> {obj}.method(x){obj}::method
profile
#행복 #도전 #지속성

0개의 댓글