[Java8] Chapter 1-3. 람다 표현식과 변수 캡쳐

 

 

✍️ 람다 표현식

Function<Integer, Integer> plus10 = (input) -> {
    return input + 10;
};

람다 표현식

  • (인자 리스트) -> {바디}

인자 리스트

  • 인자가 없을 때 : () 괄호 생략 불가 
  • 인자가 한 개 : (input) 또는 input
  • 인자가 여러 개 : (a, b)
  • 인자의 타입은 생략 가능, 컴파일러가 추론하지만 명시할 수 있다 : (Integer a, Integer b)

바디

  • 화살표 오른쪽에 메서드 본문을 정의한다.
  • 여러 줄인 경우 {}를 사용해서 묶어야 한다.
  • 한 줄인 경우 {} 생략 가능, return도 생략 가능
Function<Integer, Integer> plus10 = (input) -> {
    return input + 10;
};

Function<Integer, Integer> plus20 = (input) -> input + 20; // 괄호, return 생략 가능

 

 

🍊 변수 캡쳐

람다를 감싸고 있는 영역의 지역 변수(baseValue)가 람다의 바디에서 참조될 수 있을까?

정답을 말하면 변수가 (effective) final인 경우에 가능하다.

public class Capture {

	public static void main(String[] args) {
		Capture c = new Capture();
		c.run();
	}

	private void run() {
		int baseValue = 10;

		Consumer<Integer> consumer = input -> System.out.println(baseValue + input);
		consumer.accept(10);
	}
}

// 20

 

람다의 바디에서 지역 변수를 사용하게 되면 해당 지역변수가 캡처된다.

변수 캡처라는 개념은 Java8 이전에 익명 클래스와 내부 클래스에서도 쓰이던 기능으로 Java8 이전에는 변수 캡처를 위해선 final 키워드가 붙어야 했다. 

익명 클래스, 내부 클래스, 람다식과 final 지역변수

public class Capture {

	public static void main(String[] args) {
		Capture c = new Capture();
		c.run();
	}

	private void run() {
		final int baseValue = 10; // final

		// 익명 클래스
		Consumer<Integer> consumerA = new Consumer<Integer>() {
			@Override
			public void accept(Integer t) {
				System.out.println(baseValue);
			}
		};

		// 내부 클래스
		class LocalClass {
			void printBaseValue() {
				System.out.println(baseValue);
			}
		}

		// 람다식
		Consumer<Integer> consumerB = input -> System.out.println(baseValue + input);
		consumerB.accept(10);
	}
}

 

Java8부터 달라진 점은, 해당 지역 변수가 "사실상 final"일 때 final 키워드를 생략할 수 있고 이를 effective final이라 부른다.

사실상 final :
final 키워드는 없지만 변수를 어디서도 변경하지 않을 때 해당 변수를 사실상 final 또는 effective final이라 한다.

어찌 됐든, 익명 클래스, 내부 클래스, 람다식 모두 (effective) final 지역변수를 참조할 수 있다.

package functionalInterface;

import java.util.function.Consumer;

public class Capture {

	public static void main(String[] args) {
		Capture c = new Capture();
		c.run();
	}

	private void run() {
		int baseValue = 10; // effective final

		// 익명 클래스
		Consumer<Integer> consumerA = new Consumer<Integer>() {
			@Override
			public void accept(Integer t) {
				System.out.println(baseValue);
			}
		};

		// 내부 클래스
		class LocalClass {
			void printBaseValue() {
				System.out.println(baseValue);
			}
		}

		// 람다식
		Consumer<Integer> consumerB = input -> System.out.println(baseValue + input);
		consumerB.accept(10);
	}
}

 

Shadowing

 

익명 클래스 그리고 내부 클래스와 람다식의 차이점이 한 가지 존재하는데 바로 shadowing이다.

 

shadowing의 예시는 매우 쉬운데, Capture의 멤버 변수에도 number가 있고 shadowing 메서드의 지역 변수로 number가 있을 때 shadowing 메서드 내부에서 멤버 변수 number가 가려지는 현상을 shadowing이라 한다.

public class Capture {

	private int number = 10;
	
	public static void main(String[] args) {
		Capture c = new Capture();
		c.shadowing();
	}
	
	private void shadowing() {
		int number = 20;
		
		System.out.println(number); // 20
	}
}

 

 

다시 익명 클래스, 내부 클래스, 람다식으로 돌아오면,

익명 클래스와 내부 클래스는 shadowing이 가능하지만, 람다식은 shadowing이 불가능하다.

 

먼저 이유에 대해서 말해보면,

익명 클래스와 내부 클래스의 선언부가 그 자체로 별도의 스코프를 가지고 있기 때문에 가능하다. 

반면 람다는 람다를 감싸고 있는 메서드와 동일한 스코프를 가지고 있기에 shadowing이 불가능하다.

 

내부 클래스의 shadowing

public class Capture {

	public static void main(String[] args) {
		Capture c = new Capture();
		c.run();
	}

	private void run() {
		int baseValue = 10; // effective final

		// 내부 클래스
		class LocalClass {
			void printBaseValue() {
				int baseValue = 11;
				System.out.println(baseValue); // 11 shadowing!! 
			}
		}
	}
}

 

람다는 람다를 감싸는 메서드와 동일한 스코프다. 즉, 같은 스코프 안에 동일한 이름의 변수를 정의할 수 없다.

따라서 아래 코드는 컴파일 에러가 발생한다

Lambda expression's parameter baseValue cannot redeclare another local variable defined in an enclosing scope.
대충 변수를 재정의 할 수 없다는 말
public class Capture {

	public static void main(String[] args) {
		Capture c = new Capture();
		c.run();
	}

	private void run() {
		int baseValue = 10; // effective final

		// error!
		Consumer<Integer> consumerB = baseValue -> System.out.println(baseValue);
		consumerB.accept(10);
	}
}

 

본 내용은 백기선님의 자바8 강의 내용입니다.

 

더 자바, Java 8 - 인프런 | 강의

자바 8에 추가된 기능들은 자바가 제공하는 API는 물론이고 스프링 같은 제 3의 라이브러리 및 프레임워크에서도 널리 사용되고 있습니다. 이 시대의 자바 개발자라면 반드시 알아야 합니다. 이

www.inflearn.com