Contents
람다 표현식(1)
   Sep 2, 2022     15 min read

Chapter03

람다 표현식

  • 람다란 무엇인가 ?
  • 어디에, 어떻게 람다를 사용하는가?
  • 실행 어라운드 패턴
  • 함수형 인터페이스, 형식 추론
  • 메서드 참조
  • 람다 만들기

람다란 무엇인가 ?

람다 표현식은 메서드로 전달할 수 있는 익명 함수를 단순화한 것이라고 할 수 있다.

람다 표현식에는 이름은 없지만, 파라미터 리스트, 바디, 반환형식, 발생할 수 있는 예외 리스트는 가질 수 있다.

  • 람다의 특징
    • 익명 : 보통의 메서드와 달리 이름이 없으므로 익명이라 표현한다. 구현해야 할 코드에 대한 걱정거리가 줄어든다.
    • 함수 : 람다는 메서드처럼 특정 클래스에 종속되지 않으므로 함수라고 부른다. 하지만 메서드처럼 파라미터 리스트, 바디, 반환형식, 가능한 예외 리스트를 포함한다.
    • 전달 : 람다 표현식을 메서드 인수로 전달하거나 변수로 저장할 수 있다.
    • 간결성 : 익명 클래스처럼 많은 자질구레한 코드르 구현할 필요가 없다.
  • 익명
Comparator<Apple> byWeight = new Comparator<Apple>() {
	public int compare(Apple a1, Apple a2){
		return a1.getWeight().CompareTo(a2.getWeight());
}
  • 람다
Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeight().CompareTo(a2.getWeight());

람다 표현식의 구성 : 파라미터, 화살표, 람다 바디

Untitled

  • 파라미터 리스트
    • Comparator의 compare 메서드 파라미터(사과 두 개)
  • 화살표
    • 화살표(→)는 람다의 파라미터 리스트와 바디를 구분한다.
  • 람다 바디
    • 두 사과의 무게를 비교한다. 람다의 반환값에 해당하는 표현식이다.

어디에, 어떻게 람다를 사용할까?

List greenApples = filter(inventory, (Apple a) → GREEN.equals(a.getColor()));

정확히 어디에서 람다를 사용할 수 있다는 건가 ?

함수형 인터페이스라는 문맥에서 람다 표현식을 사용할 수 있다.

위 예제에서는 함수형 인터페이스 Predicate를 기대하는 filter 메서드의 두 번째 인수로 람다 표현식을 전달했다.

public class Main {
    public static void main(String[] args) {

        // 익명 클래스 사용
        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello World2");
            }
        };

        Runnable r1 = () -> System.out.println("Hello World!!"); // 람다  사용

        process(r1); //출력 :  Hello World!!
        process(r2); //출력 : Hello World2
        process(() -> System.out.println("Hello World 3")); // 직접 전달된 람다 표현식으로 'Hello World 3' 출력

    }

    public static void process(Runnable r) {
        r.run();
    }

}

함수 디스크립터

함수형 인터페이스의 추상 메서드 시그니처(signature)는 람다 표현식의 시그니처를 가리킨다.

람다 표현식의 시그니처를 서술하는 메서드를 함수 디스크립터(function descriptor)라고 부른다.

예를 들어 Runnable 인터페이스의 유일한 추상 메서드 run()은 인수와 반환값이 없으므로(void 반환) Runnabel 인터페이스는 인수와 반환값이 없는 시그니처로 생각할 수 있다.

() → void 표기는 파라미터 리스트가 없으며 void를 반환하는 함수를 의미한다.

(Apple, Apple) → int는 두 개의 Apple을 인수로 받아 int를 반환하는 함수를 가리킨다.

람다 표현식은 변수에 할당하거나 함수형 인터페이스를 인수로 받는 메서드로 전달할 수 있으며, 함수형 인터페이스의 추상 메서드와 같은 시그니처를 갖는다는 사실을 기억하는 것으로 충분하다.

  • 왜 함수형 인터페이스를 인수로 받는 메서드에만 람다 표현식을 사용할 수 있을까?

언어 설계자들은 자바에 함수 형식을 추가하는 방법도 대안으로 고려했다. 하지만 언어 설계자들은 언어를 더 복잡하게 만들지 않는 현재 방법을 선택했다. 또한 대부분의 자바 프로그래머가 하나의 추상 메서드를 갖는 인터페이스(예시 이벤트 처리 인터페이스)에 이미 익숙하다는 점도 고려했다.

람다 활용 : 실행 어라운드 패턴

람다와 동작 파라미터화로 유연하고 간결한 코드를 구현하는 데 도움을 주는 실용적인 예제를 확인해보자.

자원처리(예를 들면 데이터베이스의 파일 처리)에 사용하는 순환 패턴(recurrent pattern) 은 자원을 열고

처리한 다음에 자원을 닫는 순서로 이루어진다. 설정(setup)정리(cleanup)과정은 대부분 비슷하다. 즉, 실제 자원을 처리하는 코드를 설정과 정리 두 과정이 둘러싸는 형태를 갖는다.

아래의 그림과 같은 형식의 코드를 실행 어라운드 패턴(execute around pattern)이라고 부른다.

  • 중복되는 준비 코드와 정리 코드가 작업A와 작업B를 감싸고 있다.

Untitled 1

예제에서 파란 글씨는 파일에서 한 행을 읽는 코드이다.(예제는 자바 7에 새로 추가된 try-with-resources 구문을 사용했다. 이를 사용하면 자원을 명시적으로 닫을 필요가 없으므로 간결한 코드를 구현하는 데 도움을 준다.)

public String processFile() throws IOException{
		try(BufferedReader br = new BufferedReadr(new FileReader("data.txt"))){
			return br.readLine(); // <--실제 필요한 작업을 하는 행이다.
		}
}
  • 1단계
    • 동작 파라미터화를 기억하라.

    현재 코드는 파일에서 한 번에 한 줄만 읽을 수 있다. 한 번에 두 줄을 읽거나 가장 자주 사용되는 단어를 반환 하려면 어떻게 해야할까 ?

    기존의 설정, 정리 과정은 재사용하고 processFile 메서드만 다른 동작을 수행하도록 명령할 수 있다면 좋을 것이다. → processFile의 동작을 파라미터화

    BufferedReader를 이용해서 다른 동작을 수행할 수 있도록 processFile 메서드로 동작을 전달해야 한다.

    람다를 이용해서 동작을 전달할 수 있다.

    ➡️processFile 메서드가 한 번에 두 행을 읽게 하려면 코드를 어떻게 고쳐야 할까?

    1. BufferedReader를 인수로 받아서 String을 반환하는 람다가 필요
    2. BufferedReader에서 두 행을 출력하는 코드 작성
      String result = processFile(BufferedReader br) -> br.readLine() + br.readLine());
    
  • 2단계
    • 함수형 인터페이스를 이용해서 동작 전달

    함수형 인터페이스 자리에 람다를 사용할 수 있다. 따라서 BufferedReader → String과 IOException을 던질(throw)수 있는 시그니처와 일치하는 함수형 인터페이스를 만들어야 한다.

    이 인터페이스를 BufferedReaderProcessor라고 정의하자.

      @FunctionalInterface
      public interface BufferedReaderProcessor{
      	String process(BufferedReader b) throws IOException;
      }
    
    

    정의한 인터페이스를 processFile 메서드의 인수로 전달할 수 있다.

      public String processFile(BufferedReaderProcessor p) throws IOException{
      	....
      }
    
  • 3단계
    • 동작 실행

    이제 BufferedReaderProcessor 에 정의된 process 메서드의 시그니처(BufferedReader → String)와 일치하는 람다를 전달할 수 있다.

    람다의 코드가 processFile 내부에서 어떻게 실행될까?

    람다 표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있으며 전달된 코드는 함수형 인터페이스의 인스턴스로 전달된 코드와 같은 방식으로 처리한다.

    따라서 processFile 바디 내에서 BufferedReaderProcessor 객체의 precess를 호출할 수 있다.

      public String prcessFile(BufferedReaderProcessor p) throws IOException{
      	try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
      		return p.process(br); // <-- BufferedReader 객체 처리
      	}
      }
    
  • 4단계
    • 람다 전달

    이제 람다를 이용해서 다양한 동작을 processFile 메서드로 전달할 수 있다.

    다음은 한 행을 처리하는 코드

      String oneLine = processFile((BufferedReader br) -> br.readLine());
    

    다음은 두 행을 처리하는 코드

      String twoLine = processFile((BufferedReader br) -> br.readLine() + br.readLine());
    
  • 실행 어라운드 패턴을 적용하는 네 단계의 과정

Untitled 2

함수형 인터페이스 사용

함수형 인터페이스의 추상 메서드는 람다 표현식의 시그니처를 묘사한다. 함수형 인터페이스의 추상 메서드 시그니처를 함수 디스크립터(function descriptor) 라고 한다. 다양한 람다 표현식을 사용하려면 공통의 함수 디스크립터를 기술하는 함수형 인터페이스 집합이 필요하다.

이미 자바 API는 Comparable, Runnable, Callable 등의 다양한 함수형 인터페이스를 포함하고 있다.

자바 8 라이브러리 설계자들은 java.util.function 패키지로 여러 가지 새로운 함수형 인터페이스를 제공한다. Predicate, Consumer, Function 인터페이스를 배우고 더 다양한 함수형 인터페이스를 배우자.

Predicate

java.util.function.Predicate 인터페이스는 test라는 추상 메서드를 정의하며 test는 제네릭 형식 T의 객체를 인수로 받아 불리언을 반환한다.

Chap2에서 만들었던 인터페이스와 같은 형태인데 따로 정의할 필요 없이 바로 사용할 수 있다는 점이 특징이다.

  • 코드를 통해서 알아보자.
@FunctionalInterface
public interface Predicate<T> {
	boolean test(T t);
}

public <T> List<T> filter(List<T> list, Predicate<T> p) {
		List<T> result = new ArrayList<>();
		for(T t : list){
			if(p.test(t)){
				results.add(t);
			}
		}
		return results;
}

**Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();**
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);

Consumer

java.util.function.Consumer 인터페이스는 제네릭 형식 T 객체를 받아서 void를 반환하는 accept라는 추상 메서드를 정의한다. T 형식의 객체를 인수로 받아서 어떤 동작을 수행하고 싶을 때 Consumer 인터페이스를 사용할 수 있다.

예를 들어 Integer 리스트를 인수로 받아서 각 항목에 어떤 동작을 수행하는 forEach 메서드를 정의할 때 Consumer를 활용할 수 있다.

  • 예제 forEach와 람다를 이용해서 리스트의 모든 항목을 출력하는 예제
@FunctionalInterface
public interface Consumer<T> {
	void accept();
}

public <T> void forEach(List<T>, Consumer<T> c) {
	for(T t : list){
		c.accept(t);
	}
}

forEach(Arrays.asList(1,2,3,4,5),(Integer i -> System.out.println(i)) // <- Consumer의 accpet메서드를 구현하는 람다
};

Function

java.util.function.Function<T, R> 인터페이스는 제네릭 형식 T를 인수로 받아서 제네릭 형태 R 객체를 반환하는 추상 메서드 apply를 정의한다. 입력과 출력으로 매핑하는 람다를 정의할 때 Function 인터페이스를 활용할 수 있다.( 예를 들면 사과의 무게 정보를 추출하거나 문자열의 길이와 매핑)

  • String 리스트를 인수로 받아 각 String의 길이를 포함하는 Integer 리스트로 변환하는 map메소드를 정의하는 예제다.
@FunctionalInterface
public interface Function<T, R> {
	R apply(T t);
}

public <T, R> List<R> map(List<T> list, Function<T, R> f) {
	List<R> result = new ArrayList<>();
	for(T t : list){
		result.add(f.apply(t));
	}
	return result;
}

//[7, 2, 6]
List<Integer> l = map(Arrays.asList("lambdas", "in", "action")
									,(String s) -> s.length(); // <-- Funtion의 apply 메서드를 구현한 람다

기본형 특화

특화된 형식의 함수형 인터페이스를 확인해보자.

자바의 모든 형식은 참조형(reference type) (예를들면 Byte, Integer, Object, List) 아니면 기본형(primitive type)(예를 들면 int, double, byte, int)에 해당한다. 하지만 제레닉 파라미터(예를들면 Consumer의 T)에는 차조형만 사용할 수 있다. 제네릭의 내부 구현 때문에 어쩔 수 없는 일이다.

자바에서는 기본형을 참조형으로 변환하는 기능을 제공한다. 이 기능을 박싱(Boxing)이라고 한다. 참조형을 기본형으로 변환하는 반대 동작을 언박싱(unboxing)이라고 한다.

Boxing : 기본형 -> 참조형
unboxing : 참조형 -> 기본형

autoboxing : 기본형 <-> 참조형  (박싱과 언박싱이 자동으로 이루어짐)
  • int가 Integer로 박싱되는 예시
List<Integer> list = new ArrayList<>();
for(int i = 300; i < 400; i++){
	list.add(i);
}

이런 변환 과정은 비용이 소모된다. 박싱한 값은 기본형을 감싸는 래퍼며 힙에 저장된다.

따라서 박싱한 값은 메모리를 더 소비하여 기본형을 가져올 때도 메모리를 탐색하는 과정이 필요하다.

  • 자바8에서는 기본형을 입출력으로 사용하는 상황에서

오토박싱 동작을 피할 수 있도록 특별한 버전의 함수형 인터페이스를 제공한다.

public interface IntPredicate{
	boolean test(int t);
}

IntPredicate evenNumbers = (int i ) -> i % 2 == 0;
evenNumbers.test(1000); //<-- 참(박싱 없음)

Predicate<Integer> oddNumbers = (Integer i) -> i % 2 !=0;
oddNumbers.test(1000); //<-- 거짓(박싱)

일반적으로 특정 형식을 입력으로 받는 함수형 인터페이스의 이름 앞에는 DoublePredicate, IntConsumer, LongBinaryOperator, IntFunction처럼 형식명이 붙는다. Function 인터페이스는 ToIntFuntion, IntToDoubleFunction 등의 다양한 출력 형식 파라미터를 제공한다.

함수형 인터페이스함수 디스크립터기본형 특화
PredicateT→ booleanIntPredicate, LongPredicate, DoublePredicate
ConsumerT → voidIntConsumer. LongConsumer, DoubleConsumer
Function<T, R>T → RIntFunction, IntToDoubleFunction, IntToLongFuntion
  LongFunction, LongToDoubleFunction, LongToIntFunction
  DoubleFunction, DoubleToIntFunction, DoubleToLongFunction
  ToIntFunction, ToDoubleFunction, ToLongFunction
Supplier() → TBooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier
UnaryOperatorT → TIntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator
BinaryOperator(T, T) → TIntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator
BiPredicate<L, R>(T, U) → boolean 
BiConsumer<L, U>(T, U) → voidObjIntConsumer, ObjLongConsumer, ObjDoubleConsumer
   
BiFunction<T, U, R>(T, U) → RToIntBiFunction<T, U> ToLonBiFunction<T, U>, ToDoubleBiFunction<T, U>

람다와 함수형인터페이스 예제

사용 사례람다 예제대응하는 함수형 인터페이스
불리언 표현(List list) → list.isEmpty()Predicate<List>
객체 생성() → new Apple(10)Supplier
객체에서 소비(Apple a → System.out.println(a.getWeight())Consumer
객체에서 선택/추출(String s) → s.length()Function<String, Integer> 또는 ToIntFunction
두 값 조합(int a, int b) → a * bIntBinaryOperator
두 객체 비교(Apple a1, Apple a2) → a1.getWeight().compareTo(a2.getWeight())Comparator 또는 BiFunction<Apple, Apple, Integer> 또는 ToIntBiFunction<Apple, Apple>
👉예외, 람다, 함수형 인터페이스의 관계
함수형 인터페이스는 확인된 예외를 던지를 동작을 허용하지 않는다.
즉, 예외를 던지는 람다표현식을 만들려면 확인된 예외를 선언하는 함수형 인터페이스를 직접 정의하거나 람다를 try/catch 블록으로 감싸야 한다.

전에 다뤘던 IOException을 명시적으로 선언하는 함수형 인터페이스BufferedReaderProcessor를 살펴보자.

@FunctionalInterface
public interface BufferedReaderProcessor{
	String process(BufferedReader b) throws IOExceptio;

}

BufferedReaderProcessor p = (BufferedReader br) -> br.readLine();

그러나 Function<T, R>형식의 함수형 인터페이스를 기대하는 API를 사용하고 있으며 직접 함수형 인터페이스를 만들기 어려운 상황이다.
이런 상황에서는 다음 예제처럼 명시적으로 확인된 예외를 잡을 수 있다.

Function<BufferedReader, String> f = (BufferedReader b) -> {
		try{
			return b.readLine();
		}
		catch(IOException e)
			throw new RuntimeException(e);
		}
};

컴파일러가 람다의 형식을 어떻게 확인할까? 그리고 피해야 할 상항은 무엇인지?

형식 검사, 형식 추론, 제약

람다로 함수형 인터페이스의 인스턴스를 만들 수있다고 언급했다. 람다 표현식 자체에는 람다가 어떤 함수형 인터페이스를 구현하는지의 정보가 포함되어 있지 않다. 따라서 람다 표현식을 더 제대로 이해하려면 람다의 실제 형식을 파악해야한다.

형식 검사

람다가 사용되는 콘텍스트(context)를 이용해서 람다의 형식(type)을 추론할 수 있다. 어떤 콘텍스트에서 기대되는 람다 표현식의 형식을 대상 형식(target type)이라고 부른다. 람다 표현식을 사용할 때 실제 어떤 일이 일어나는지 예제를 통해서 확인해보자.

List<Apple> heavierThan150g = filter(inventory, (Apple apple) -> apple.getWeight() > 150);

위 코드의 형식 확인 과정을 보여준다.

아래와 같은 순서로 형식 확인 과정이 진행된다.

  1. filter 메서드의 선언을 확인한다.
  2. filter 메서드는 두 번째 파라미터로 Predicate 형식(대상 형식)을 기대한다.
  3. Predicate은 test라는 한 개의 추상 메서드를 정의하는 함수형 인터페이스다.
  4. test 메서드는 Apple을 받아 boolean을 반환하는 함수 디스크립터를 묘사한다.
  5. filter 메서드로 전달된 인수는 이와 같은 요구사항을 만족해야한다.

Untitled 3

위 예제에서 람다 표현식은 Apple을 인수로 받아 boolean을 반환하므로 유효한 코드다 !!.

람다 표현식이 예외를 던질 수 있다면 추상 메서드도 같은 예외를 던질 수 있도록 throws로 선언해야 한다.

같은 람다, 다른 함수형 인터페이스

대상 형식(target typing)이라는 특징 때문에 같은 람다 표현식이더라도 호환되는 추상 메서드를 가진 다른 함수형 인터페이스로 사용될 수 있다.

예를 들어 Callable과 PrivilegedAction 인터페이스는 인수를 받지 않고 제네릭 형식 T를 반환하는 함수를 정의한다. 따라서 다음 두 할당문은 모두 유효한 코드다.

Callable<Integer> c= () -> 42;

PrivilegedAction<Integer> p = () -> 42;
  • 대상 형식 : Callable
  • 대상 형식 : PrivilegedAction
																	***※다이아몬드 연산자***

자바 7에서도 다이아몬드 연산자(<>)로 콘텍스트에 따른 제네릭 형식을 추론할 수 있다는 사실을 기억할 것이다.
(제네릭 메서드에서 이런 개념을 쉽게 찾아볼 수 있다).
주어진 클래스 인스턴스 표현식을 두 개 이상의 다양한 콘텍스트에 사용할 수 있다.
이때 인스턴스 표현식의 형식 인수는 콘텍스트에 의해 추론된다.

List<String> listOfStrings = new ArrayList<>();
List<String> listOfIntegers = new ArrayList<>();
																	***※특별한 void 호환 규칙***

람다의 바디에 일반 표현식이 있으면 void를 반환하는 함수 디스크립터와 호환된다.(물론 파라미터 리스트도 호환되어야 함).
예를 들면 다음 두 행의 예제에서 List의 add메서드는 Consumer 콘텍스트(T -> void)가 기대하는 void 대신 boolean을 반환하지만 유효한 코드이다.

//Predicate는 불리언 반환값을 갖는다.
Predicate<String> p = s -> list.add(s);

//Consumer는 void 반환값을 갖는다.
Consumer<String> b = s -> list.add(s);

지금까지 언제, 어디서 람다 표현식을 사용할 수 있는지 배워보았다.

  • 할당문 콘텍스트
  • 메서드 호출 콘텍스트(파라미터, 반환값)
  • 형변환(cast) 콘텍스트

등등 …

앞에 나온 콘텍스트 등으로 람다 표현식의 형식을 추론할 수 있다.

형식 추론

자바 컴파일러는 람다 표현식이 사용된 콘텍스트(대상 형식)를 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론한다. 즉 대상 형식을 이용해서 함수 디스크립터를 알 수 있으므로 컴파일러는 람다의 시그니처도 추론할 수 있다.

결과적으로 컴파일러는 람다 표현식의 파라미터 형식에 접근할 수 있으므로 람다 문법에서 이를 생략할 수 있다.

  • 자바 컴파일러는 람다 파라미터 형식을 추론할 수 있다.
//파라미터 a에는 형식을 명시적으로 지정하지 않는다.
List<Apple> greenAppels = filter(inventory, apple -> GREEN.equals(apple.getColor()));

여러 파라미터를 포함하는 람다 표현식에서는 코드 가독성이 향상된다.

예제

// 형식을 추론 하지 않음
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

// 형식을 추론함
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());

Untitled 4

항상 추론해서 생략하는건 무조건 가독성이 좋다고 말하지 못한다. 오히려 명시적으로 형식을 포함하는 것이 가독성이 더 좋게 될 수도 있다.

명심하자 어떤 방법이 좋은 방법인지는 정해진게 아니다.!!

지역 변수 사용

모든 람다 표현식은 인수를 자신의 바디 안에서만 사용했다. 하지만 람다 표현식에서는 익명 함수가 하는 것처럼 자유변수(Free variable) 를 활용 할 수 있다.

간략히 자유변수란 파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수를 말한다.

이와 같은 동작을 람다 캡처링(capturing lambda)라고 부른다.

그렇다면 람다 캡처링이란 무엇일까?

int portNumber = 1337;
Runnable r= () -> System.out.println(portNumber);

자유 변수라고 해도 약간의 제약은 있다. 람다인스턴스 변수정적 변수를 자유롭게 캡처(자신의 바디에서 참조할 수 있도록)할 수 있다.

하지만 그러려면 지역 변수는 명시적으로 final로 선언되어 있어야 하거나 실질적으로 final로 선언된 변수와 똑같이 사용되어야한다.

즉, 람다 표현식은 한 번만 할당할 수 있는 지역 변수를 캡처할 수 있다.

※참고※ : 인스턴스 변수 캡처는 final 지역 변수 this를 캡처하는 것과 마찬가지이다.

예제를 통해서 이해해보자.

  • portNumber에 값을 두 번 할당하므로 컴파일 할 수없는 코드
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber); //<-- 에러가 난다.
portNumber = 31337;

위 코드에서 에러가 나는 이유는 람다에서 참고하는 지역 변수는 final로 선언되거나 실질적으로 final처럼 취급되어야 한다.

실질적으로 final처럼 쓰인다는 말은 그 변수가 변하지 않는 것을 말한다.

지역 변수의 제약

왜 지역 변수에 이런 제약이 필요할까???

우선 내부적으로 인스턴스 변수와 지역 변수는 저장되는 곳부터가 다르다.

  • 인스턴스 → 힙
  • 지역 변수 - > 스택

람다에서 지역 변수에바로 접근할 수 있다는 가정하에 람다가 스레드에서 실행된다면 변수를 할당한 스레드가 사라져서 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는 해당 변수에 접근하려 할 수 있다.

따라서 자바 구현에서는 원래 변수에 접근을 허용하는 것이 아니라 자유 지역 변수의 복사본을 제공한다.

복사본의 값이 바뀌지않아야 하므로 지역 변수에는 한 번만 값을 할당해야 한다는 제약이 생긴 것이다.

글쓰기에 대한 회고

너무 길어서 메서드참조는 3-(2)로 넘어가겠다. 작성하는데 너무 많은 시간이 걸리고 이해하는데도 너무 오래걸려서 동영상 참고, 서적, doc …등등 이해하려고 많이 찾아보고 있다. 처음 볼 때 보다는 정말 많이 이해가 된다.

아침에 새벽스터디를 하는데 이것들을 간단히 요약해서 발표하고 싶은 욕구가 크다. 더 익숙해질 시 다른 예제를 만들어서 발표해보자.