이 포스팅의 코드 및 정보들은 강의를 들으며 정리한 내용을 토대로 작성한 것입니다.
위 사진처럼 multiply15라는 추상메서드에 (i) -> i * 15
를 대입하고 있다.
이때 (i)
에 들어있는 i
는 인자 리스트
이며, i * 15
인 부분을 바디
라고 한다.
바디가 한 줄이면 중괄호를 없앨 수 있다. 2줄 넘어가면 중괄호로 감싸야 한다.
위의 i * 15를 중괄호로 감싸면
UnaryOperator<Integer> multiply15 = (i) -> {
return i * 15;
};
이렇게 중괄호 안에 i * 15 값을 return을 한다.
인자 리스트는 인자가 없을 때, 인자가 한 개일 때, 인자가 여러 개일 때로 나뉜다.
Supplier<Integer> get20 = () -> 10;
괄호 ()
안에 아무 인자도 넣지 않고 오로지 반환만 한다.
Supplier 인터페이스가 대표적이다.
타입은 하나인데 구현에 쓰이는 타입은 세 개이다.
이는 하나의 타입으로 세 개 모두 타입을 동일하게 쓴다는 의미이다.
저렇게 선언한 제네릭 타입을 가지고 컴파일러가 추론(infer)할 수 있지만
(Integer i, Integer j)
이런 식으로 각 인자의 타입을 지정할 수 있다.
하지만 컴파일러가 추론할 수 있도록 생략하면 보기에도 문제 없기에, 굳이 인자에까지 타입을 명시할 필요는 없다.
타입은 이미 변수 선언부에 정의 되어있기 때문이다.
3개의 타입이 있는 인터페이스가 있다. 이 3개가 모두 인자는 아니고, 앞의 두 개는 인자, 남은 한 개는 반환 타입이다.
이는 인자의 개수를 하나로 줄여 BinaryOperator<T>
로 활용할 수 있다.
람다를 감싸고 있는 영역에 있는 local variable이 있다고 가정해보자.
local variable인 baseNumber
가 캡처
가 된다.
익명/내부 클래스에서 쓰이던 기능이며, Java 8 이전에는 위와 같은 상황에서 항상 local variable은 앞에 final을 붙여야 했다.
// After Java 8
int afterEight = 10;
// Before Java 8
final int beforeEight = 10;
내부 클래스 간단하게 알아보기
메서드 내부에 정의한 local class
private void foo() { class LocalClass { // 내부 클래스 void bar() { ... } } }
익명 클래스 간단하게 알아보기
Comsumer<Integer> integerConsumer = new Consumer<Integer>() { @Override public void accept(Integer integer) { ...; } };
익명 클래스에서 local variable(baseNumber)을 참고하는 것이다.
private void printIntValue() {
final int baseNumber = 20;
// 로컬 클래스
class LocalClass {
void printBaseNumber() {
System.out.println(baseNumber);
}
}
// 익명 클래스
Consumer<Integer> integerConsumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(baseNumber);
}
};
// 람다
IntConsumer printInt = (i) -> {
System.out.println(i + baseNumber);
};
printInt.accept(20);
}
로컬 클래스
, 익명 클래스
, 람다
이 셋의 공통점은 final인 baseNumber
를 참조할 수 있다.
Java 8부터는 (예를 들어)baseNumber라는 변수가 사실상 final인 경우, final이라는 키워드를 생략할 수 있는 경우가 있다.
이는 final이라는 키워드는 없지만, 이 변수를 더이상 어디서도 변경하지 않는 경우이다.
자바 document에서는 effective final이라고도 하는데, 이는 방금 설명한 것과 같이 final 없이도 참조가 가능하다.
로컬 클래스와 익명 클래스는 shadowing이 가능하지만, 람다는 shadowing이 되지 않는다.
로컬 클래스나 익명 클래스에 선언한 변수는 그 변수하고 같은 이름의 변수가 printIntValue() 안에 있거나 Main 클래스 안에 있으면 로컬/익명 클래스에 있던 변수가 가린다.
이 두 종류의 클래스는 별도의 scope이 있기 때문이다.
하지만, 람다는 람다를 감싸고 있는 메서드랑만 같게 된다. 즉, shadowing이 일어나지 않는다.
로컬 클래스에 있던 baseNumber가 printIntValue() 안에 있던 baseNumber를 가린 것이다.
익명 클래스에서는 accept()의 파라미터 이름을 baseNumber로 바꾸면 위 사진처럼 printIntValue() 안에 있던 baseNumber가 아닌 파라미터로 받는 baseNumber를 참조하게 된다.
오로지 익명 클래스는 자기 scope안에 있는 파라미터 이름, 즉 printInt.accept(20)
에서 넘어 전달되는 그 파라미터를 그대로 쓰게 된다.
람다 안에 있던 baseNumber는 printIntValue() 안에 미리 선언되어 있던 baseNumber랑 같은 scope이다. 즉, 같은 scope 내에 같은 이름의 변수가 두 개 이상 올 수 없으며, shadowing 현상이 발생되지 않는다는 것이다.
람다식에 있는 인자는 그 람다를 감싸고 있던 printIntValue()에 있던 baseNumber와 같은 범위에 있다는 것이다.
baseNumber의 값을 변경하려고 시도하면, final처럼 쓰고 있었는데 effective final이 아니게 되기 때문에 더이상 참조할 수 없게된다.
람다
인자 리스트
바디
변수 캡처 (Variable Capture)
로컬 변수 캡처
effective final
익명 클래스 구현체와 달리 'shadowing'하지 않는다.