본문 바로가기
Programming Language/Java

[Java] 람다 표현식의 반환 타입 추론

by kimyoungrok 2025. 2. 13.
728x90

목차

  • 예시 - Arrays.sort()와 Comparator 람다 표현식
  • 함수형 인터페이스 설계와 람다 표현식
  • 람다 표현식의 반환 타입 추론 과정

Java 8부터 도입된 람다 표현식은 간결한 코드 작성과 함수형 프로그래밍 패러다임을 지원합니다. 람다 표현식을 사용할 때 가장 큰 장점 중 하나는 **타입 추론(type inference)**으로, 메서드의 매개변수나 반환 타입을 일일이 명시할 필요가 없다는 점입니다.

이번 글에서는 Comparator 인터페이스 예시와 직접 함수형 인터페이스를 설계하고, 어떻게 람다 표현식이 반환 타입을 추론하는지 자세히 알아보겠습니다.


예시 - Arrays.sort()와 Comparator 람다 표현식

Java에서는 배열을 정렬할 때, Arrays.sort() 메서드를 사용합니다.

Arrays.sort()는 오버로딩되어 있는데, 정렬 기준을 지정하기 위해 두 번째 인자로 Comparator를 전달할 수 있습니다.

예를 들어, Point 배열을 오름차순으로 정렬하고자 할 때 다음과 같이 작성할 수 있습니다

package pl.java.sort.Arrays;

import java.awt.*;
import java.util.Arrays;

public class SortObjectArray2 {
    public static void main(String[] args) {
        Point[] points = {
                new Point(3, 7),
                new Point(3, 5),
                new Point(2, 4),
        };

        Arrays.sort(points, (p1, p2) -> {
            if (p1.x != p2.x) return p1.x - p2.x;
            return p1.y - p2.y;
        });

        System.out.println(Arrays.toString(points));
    }
}
/*
[java.awt.Point[x=2,y=4], java.awt.Point[x=3,y=5], java.awt.Point[x=3,y=7]]
*/

위 코드에서 중요한 부분은 (p1, p2) -> { if (p1.x != p2.x) return p1.x - p2.x; return p1.y - p2.y;} 입니다.

어떻게 Arrays.sort의 두 번째 인자로 넘겨준 람다 표현식이 Comparator의 compare 메서드임을 올바르게 추론했을까요?

그 이유는 바로 함수형 인터페이스 덕분입니다.


함수형 인터페이스 설계와 람다 표현식

함수형 인터페이스

오직 하나의 추상 메서드만을 포함하는 인터페이스

함수형 인터페이스는 @FunctionalInterface 어노테이션을 사용해 명시할 수 있으며, 단 하나의 추상 메서드가 있어야 람다 표현식을 통해 해당 메서드의 구현체를 전달할 수 있습니다.

MyLambdaOperation.java

package pl.java.lambda;

@FunctionalInterface
public interface MyLambdaOperation {
    int operation(int a, int b);
}

위와 같이 단 하나의 추상 메서드operation 을 가진 인터페이스를 설계하면, 람다 표현식 (a, b) -> a + b는 내부적으로 operation메서드의 구현체로 자동 할당됩니다.

Evaluation of a lambda expression produces an instance of a functional interface (§9.8). Lambda expression evaluation does not cause the execution of the expression's body; instead, this may occur at a later time when an appropriate method of the functional interface is invoked.

Chapter 15. Expressions

 

Chapter 15. Expressions

class Point { int x, y; } class ColoredPoint extends Point { int color; } class Test { static int test(ColoredPoint p) { return p.color; } static String test(Point p) { return "Point"; } public static void main(String[] args) { ColoredPoint cp = new Colore

docs.oracle.com

 

MyLambdaOperationImpl.java

package pl.java.lambda;

public class MyLambdaOperationImpl {
    private static int myOperation(int a, int b, MyLambdaOperation o) {
        return o.operation(a, b);
    }
    public static void main(String[] args) {
        System.out.println("3 + 2 = " + myOperation(3, 2, (a, b) -> a + b));
        System.out.println("3 + 2 = " + myOperation(3, 2, (a, b) -> a - b));
    }
}
/*
3 + 2 = 5
3 - 2 = 1
*/

myOperation 의 두 번째 인자로 (a, b) -> a + b 와 같은 람다식 표현을 넘겨줌으로써, 컴파일러는 MyLambdaOperation 인터페이스의 단 하나의 추상 메서드 operation을 구현하는 익명 클래스(또는 익명 함수)를 생성합니다.

 

다시 돌아와서 Arrays.sort의 두 번째 인자로 넘겨준 람다 표현식이 Comparator의 compare 메서드임을 추론하는 과정을 코드를 통해 살펴보겠습니다.

sort의 두 번째 인자로 Comparator 을 받고 있기 때문에 위에서 정리한대로 타입 추론이 됩니다.

    public static <T> void sort(T[] a, int fromIndex, int toIndex,
                                Comparator<? super T> c) {
        if (c == null) {
            sort(a, fromIndex, toIndex);
        } else {
            rangeCheck(a.length, fromIndex, toIndex);
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, fromIndex, toIndex, c);
            else
                TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
        }
    }

또한, Comparator 의 유일한 추상 메서드는 compare 으로

public interface Comparator<T> {
    int compare(T o1, T o2);
    ...
}

legacyMergeSort 또는 TimSort.sort의 내부에서 compare를 호출하는 것을 확인할 수 있습니다.

private static void mergeSort(Object[] src,
                                  Object[] dest,
                                  int low, int high, int off,
                                  Comparator c) {
        int length = high - low;

        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i=low; i<high; i++)
                for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
        }
    	private static <T> void binarySort(T[] a, int lo, int hi, int start,
                                       Comparator<? super T> c) {
						...
            while (left < right) {
                int mid = (left + right) >>> 1;
                if (c.compare(pivot, a[mid]) < 0)
                    right = mid;
                else
                    left = mid + 1;
            }

람다 표현식의 반환 타입 추론 과정

람다 표현식의 반환 타입이 어떻게 추론되는지 그 과정을 단계별로 정리하면 다음과 같습니다.

1. 타깃 타입 결정

람다 표현식은 할당되거나 전달되는 컨텍스트에 의해 타깃 타입이 결정됩니다.

myOperation(3, 2, (a, b) -> a + b)에서, 타깃 타입은 MyLambdaOperation입니다.

2. 함수형 인터페이스의 단일 추상 메서드 분석

MyLambdaOperation 인터페이스에는 단 하나의 추상 메서드 operation(int a, int b)가 정의되어 있습니다.

따라서 람다 표현식은 이 메서드를 구현하는 것으로 간주됩니다.

3. 매개변수 타입 추론

람다 표현식의 매개변수 (a, b)는 함수형 인터페이스의 추상 메서드인 operation의 매개변수 타입에 의해 결정됩니다.

int operation(int a, int b)라면, a와 b는 모두 int로 추론됩니다.

4. 반환 타입 추론

람다 표현식의 본문에서 반환되는 값의 타입이, 함수형 인터페이스의 추상 메서드가 요구하는 반환 타입과 일치하는지 확인합니다.

(a, b) -> a + b의 경우, 두 int 값을 더한 결과가 int 타입이므로 올바르게 추론됩니다.

5. 타입 검증

컴파일러는 위 단계를 통해, 람다 표현식이 올바른 타입(매개변수 및 반환 타입)을 가진 함수형 인터페이스의 구현체로 변환되었음을 검증합니다.

이러한 과정을 통해, 별도로 매개변수나 반환 타입을 명시하지 않더라도 컴파일러가 자동으로 올바른 타입을 추론하여 람다 표현식을 처리할 수 있게 됩니다.

728x90