익명구현객체와 람다(Lambda)

이리·2025년 1월 24일
0
post-thumbnail

Java는 JS(약타입 언어)와 달리 강타입 언어입니다.

그 말은 모든 데이터와 객체는 타입이 정해져 있어야한다는 말이죠.

여기서 문제는 Java는 함수를 독립적인 데이터 타입으로 인정하지 않는다는 점입니다. 여러분들도 아시다시피 Java에서 함수는 반환타입이나 파라미터 타입으로 선언이 불가능한 것처럼 말이죠.

하지만, 함수형 프로그래밍을 위해서는 자바도 함수를 타입으로 쓸 수 있는 방법이 필요합니다. (함수형 프로그래밍의 필요성이나 장점에 대해서는 따로 이야기하기로..)

그럼 함수를 반환타입이나 파라미터 타입으로 사용하고 싶다면 어떻게 해야할까요?

바로 Lambda와 @FunctionalInterface를 사용하는 것입니다.

그럼 지금부터 익명구현객체 → 람다 → @FuntionalInterface 순으로 각각의 개념에 대해 알아보도록 하겠습니다.


익명구현객체

익명 구현 객체란 어떤 인터페이스나 추상 클래스를 이름 없이 정의하는 방식을 이야기합니다.

그럼 이게 왜 지금 익명구현객체를 먼저 설명하는 것이냐 궁금하실텐데 앞에 나왔던 함수형 프로그래밍을 위해서 필요한 것입니다.

잠깐 반복하자면, java는 함수형 프로그래밍을 진행하기에 적합하지 않습니다.

  • Java는 함수를 독립적으로 표현할 수 있는 타입이 없고
  • Java에서 함수의 형태를 정의하려면 인터페이스나 추상 클래스를 사용해야한다.

라는 부분 때문이죠. 익명 구현 객체는 이 문제를 해결하기 위해 등장했습니다. 인터페이스를 즉석해서 구현하는 객체를 생성하여 메서드의 반환값과 파라미터 타입을 명시적으로 정의하기 위해서죠.

add를 예로 들어보겠습니다.

(기존 Java)

interface Sum{
	int add(int a, int b);
}

// 별도 클래스 정의 
class SumImpl implements Sum{
	@Override
	public int add(int a, int b){
		return a + b;
	}
}

public class Main {
    public static void main(String[] args) {
        SumImpl sumImpl = new SumImpl();
        System.out.println(SumImpl.add(1,2)); 
    }
}

→ SumImpl을 별도로 작성해야한다는 부분이 굉장히 거추장스럽습니다. 클래스 선언이 많아질수록 유지보수도 어려워지죠.

여기에 익명 구현 객체를 적용하면 SumImpl이 필요없어집니다.

interface Sum{
	void add(int a, int b);
}

public class Main {
    public static void main(String[] args) {
        Sum sum = new Sum(){
		      @Override
		      public int add(int a, int b){
			      return a + b ;
		      }
        }
        
        System.out.println(sum.add(1,2));
    }
}

클래스 이름을 생략하고 인터페이스를 바로 구현할 수 있다는 점에서 1단계가 줄어들고, 코드도 간략해진다는 장점이 있죠.

하지만 여전히 @Override 부분에서 코드를 읽는데 어려움이 있고, 익명구현객체 구현 부분이 많아진다면 가독성이 떨어질 것입니다.


Lambda Expression

이러한 불편함에 등장한 것이 Lambda expression입니다. 말 그대로 익명 구현 객체를 더 간결하게 표현하기 위한 표현식이죠.

Lambda의 경우 메서드 이름, 반환 타입, 파라미터 타입을 생략할 수 있어 코드가 한 단계 더 간결해집니다.

위의 코드에 Lamda Expression을 적용해보죠.

interface Sum{
	int add(int a, int b);
}

public class Main {
    public static void main(String[] args) {
        Sum sum = (a, b) -> a + b;
        
        System.out.println(sum.add(1,2));
    }
}

익명 구현 객체를 구현하는 부분에서 거추장스러운 부분들이 모두 간결하게 표현된 것을 볼 수 있습니다.

여전히 interface나 추상 클래스가 필요하다는 점은 변함이 없지만 별도의 파일이 존재하지 않는다는 것만으로도 충분히 편리하다는 것을 알 수 있습니다.

Lambda Expression을 적용하기 위해서는 일반적인 인터페이스나 추상 클래스는 사용할 수 없습니다. Lambda는 별도의 메서드 명 없이 변수 타입을 참고한 추론으로 진행되기때문에, 인터페이스나 추상 클래스 내에 반드시 추상 메서드가 하나만 존재해야 하고, Lambda 표현식의 반환 타입은 해당 인터페이스의 추상 메서드의 반환 타입과 반드시 일치해야 합니다.

정리하자면 람다에 사용되는 익명구현객체의 경우

  • 인터페이스, 추상 객체 내에 하나의 메서드만 존재
  • Lambda Expression에서 파라미터 타입과 반환 타입이 해당 메서드와 일치

위 두가지 조건을 반드시 만족해야하는 것이죠.


@FunctionalInterface

@FunctionalInterface 어노테이션의 경우 이런 Lamda에 사용되는 인터페이스를 명시적으로 표현하고, 이를 놓쳤을 경우 컴파일 에러를 띄우기 위해 사용됩니다.

@FunctionalInterface
interface Sum{
	int add(int a, int b);
}

public class Main {
    public static void main(String[] args) {
        Sum sum = (a, b) -> a + b;
        
        System.out.println(sum.add(1,2));
    }
}

어노테이션을 통해 컴파일러가 해당 인터페이스가 함수형 이터페이스임을 확인하고 만약 추상 메서드가 2개 이상이라면 컴파일 에러를 발생시킴으로써 개발에 안정성을 증가시켜주는 것이죠.


제가 아직 많이 사용해본것은 아니지만 Lamba 표현식의 경우 코딩테스트를 준비하며 정렬하는 부분에서 가장 많이 사용했습니다. 다음 글에서는 정렬시 사용되는 Lambda Expression의 원리에 대해 알아보겠습니다.

0개의 댓글

관련 채용 정보