본문 바로가기

자바

람다 표현식과 this 키워드에 관하여

@FunctionalInterface
interface MyFunction<T, R> {
	R apply(T t);
}

위와 같은 함수형 인터페이스가 있을 때 익명클래스를 작성하여 인터페이스의 인스턴스를 얻을 수 있다.

 

MyFunction<Integer, Integer> plus30 = new MyFunction<Integer, Integer>() {

    @Override
    public Integer apply(Integer t) {
        return t + 30;
    }
};

물론 람다표현식으로도 바꿀 수 있다.

MyFunction<Integer, Integer> plus30 = i -> i + 30;

 

보통의 많은 자료에서, 익명클래스를 람다표현식으로 바꿔가며 설명을 이어간다.

어그런데 혹시, 혹시.

람다를 익명클래스로 바꿀 수는...?

그럴 수 있다. 물론.

하지만 함수형 인터페이스 내에는 default 메소드 또한 다수 포함하며,

이들 내부 구현은 모두 람다표현식으로 구현되어 있다.

나는 이들을 익명클래스로 바꿔보고 싶었다.

결론부터 말하자면 그럴 수 없다.

왜냐하면 this라는 키워드 때문이다.

 

다음은 Function 인터페이스와 그것의 default 메소드인 compose를 표방한 MyFunction이다.

@FunctionalInterface
interface MyFunction<T, R> {
	R apply(T t);
	
	default <V> MyFunction<V, R> compose(MyFunction<? super V, ? extends T> before) {
		return (V v) -> apply(before.apply(v));
	}

}

이제 compose 메소드에 포함된 람다표현식을 (억지로) 익명클래스로 바꿔보자.

@FunctionalInterface
interface MyFunction<T, R> {
	R apply(T t);
	
	default <V> MyFunction<V, R> compose(MyFunction<? super V, ? extends T> before) {
		return new MyFunction<V, R>() {

			@Override
			public R apply(V v) {
				return apply(before.apply(v));
			}
			
		};
	}
}

놀랍게도,(뻥이다. 사실 막 놀랍진않다.)

이클립스에서는 컴파일에러를 내놓는다.

그 이유는 익명클래스의 apply 메소드의 return라인에서의 바깥 apply는 T타입을 인자로, R타입을 리턴타입으로 하는 apply가 아니고,

바로 V타입을 인자로 R타입을 리턴타입으로 하는 apply이기 때문이다.

물론 람다 표현식으로 작성한 compose의 바깥apply는 T타입을 인자로, R타입을 리턴타입으로하는 apply가 맞다.

 

이와 같은 차이는 this 키워드를 붙였을 때 그 차이가 명확히 들어나는데 다음 코드를 보자.

 

@FunctionalInterface
interface MyFunction<T, R> {
	R apply(T t);
	
	default <V> MyFunction<V, R> compose(MyFunction<? super V, ? extends T> before) {
		return (V v) -> this.apply(before.apply(v));
	}
	
	default <V> MyFunction<V, R> compose(MyFunction<? super V, ? extends T> before) {
		return new MyFunction<V, R>() {

			@Override
			public R apply(V v) {
				return this.apply(before.apply(v));
			}
			
		};
	}
}

람다표현식에서의 this.apply는 인터페이스에서 정의한 apply다.

하지만 익명클래스에서의 apply는, return 내에 new 로 정의한 클래스의 apply다.

 

이러한 차이때문에 람다표현식에서 익명클래스로의 변환(그 반대도)하는 리팩토링은 충분히 조심해야겠다.

 

이펙티브자바3의 아이템42의 뒷부분에서 다음과 같은 구절이 있다.

마지막으로, 람다는 자신을 참조할 수 없다. 람다에서의 this 키워드는 바깥 인스턴스를 가리킨다.
반면 익명 클래스에서의 this는 익명 클래스의 인스턴스 자신을 가리킨다.
그래서 함수 객체가 자신을 참조해야 한다면 반드시 익명클래스를 써야 한다.

 

다음은 jls 8버전에서, 람다표현식과 this 키워드에 관한 구절인데,

번역은 셀프..(ㅜ,ㅜ);

The keyword this may be used in a lambda expression
only if it is allowed in the context in which the lambda expression appears.
Otherwise, a compile-time error occurs.

 

'자바' 카테고리의 다른 글

8.3 Field Declarations (1)  (0) 2021.10.11
[자바] 8.2 클래스 멤버  (0) 2021.10.02
자바 Stream API  (0) 2021.09.25
[자바] 옵저버패턴 : java.util.Observer, Observable  (0) 2021.06.27
[자바] Chain of Responsibility 패턴  (0) 2021.06.16