✅ Syntax of Lambda Expressions
➡️ 람다 표현식은 다음과 같은 구성 요소로 이루어져 있습니다.
- () 괄호로 둘러싸인 콤마로 구분된 formal 파라미터의 리스트
CheckPerson.test 메서드는 Person 클래스의 인스턴스를 나타내는 p라는 하나의 파라미터를 가지고 있습니다.
Note: 람다 표현식에서는 파라미터의 데이터 타입을 생략할 수 있습니다. 또한, 하나의 파라미터만 있는 경우에는 괄호를 생략할 수도 있습니다. 예를 들어, 다음 람다 표현식도 유효합니다.
p -> p.getGender() == Person.Gender.MALE && p.getAge() >= 18 && p.getAge() <= 25
- → (화살표 토큰)
단일 표현식 또는 명령문 블록으로 구성되어 있습니다. 아래는 예시입니다.
p.getGender() == Person.Gender.MALE && p.getAge() >= 18 && p.getAge() <= 25
단일 expression을 지정하면 Java 런타임은 expression을 평가하고 그 값을 반환합니다. 또는 return 문을 사용할 수도 있습니다.
p -> { return p.getGender() == Person.Gender.MALE && p.getAge() >= 18 && p.getAge() <= 25; }
return 문은 lamdba expression이 아닙니다. 람다 표현식에서 사용하기 위해서는 return문을 중괄호({})로 묶어야 합니다.
그러나 void 메서드 호출은 중괄호로 묶지 않아도 됩니다. 아래와 같이 사용할 수 있습니다.
email -> System.out.println(email)
람다 표현식은 메서드 선언과 매우 비슷하게 보입니다. 람다 표현식은 이름이 없는 익명 메서드로 생각할 수 있습니다.
- 두 개 이상의 formal 파라미터를 가지는 람다 표현식
public class Calculator { interface IntegerMath { int operation(int a, int b); } public int operateBinary(int a, int b, IntegerMath op) { return op.operation(a, b); } public static void main(String... args) { Calculator myApp = new Calculator(); IntegerMath addition = (a, b) -> a + b; IntegerMath subtraction = (a, b) -> a - b; System.out.println("40 + 2 = " + myApp.operateBinary(40, 2, addition)); System.out.println("20 - 10 = " + myApp.operateBinary(20, 10, subtraction)); } }
operateBinary 메서드는 두 개의 정수 피연산자에 대해 수학 연산을 수행합니다. 수학 연산은 IntegerMath 인터페이스의 인스턴스로 지정됩니다. 이 예제에서는 덧셈과 뺄셈 두 가지 연산을 람다 표현식으로 정의합니다. 예제는 다음과 같이 출력합니다
40 + 2 = 42 20 - 10 = 10
✅ Accessing Local Variables of the Enclosing Scope
- 람다 표현식의 타입 범위
로컬 및 익명 클래스와 마찬가지로, 람다 표현식은 변수를 캡처할 수 있습니다. 람다 표현식은 둘러싸는 범위의 로컬 변수에 대한 동일한 접근 권한을 갖습니다. 그러나 로컬 및 익명 클래스와 달리, 람다 표현식은 은폐(Shadowing) 문제가 없습니다. 람다 표현식은 어휘적으로 스코프가 지정됩니다. 이는 람다 표현식이 슈퍼타입으로부터 어떤 이름도 상속받지 않고, 새로운 스코프 수준을 도입하지 않는다는 것을 의미합니다. 람다 표현식 내의 선언은 둘러싸는 환경과 마찬가지로 해석됩니다. 다음 예제인 LambdaScopeTest가 이를 보여줍니다
import java.util.function.Consumer; public class LambdaScopeTest { public int x = 0; class FirstLevel { public int x = 1; void methodInFirstLevel(int x) { int z = 2; Consumer<Integer> myConsumer = (y) -> { // The following statement causes the compiler to generate // the error "Local variable z defined in an enclosing scope // must be final or effectively final" // // z = 99; // 다른 스코프에 있는 값은 읽을 수 있지만, 재정의할 수는 없다. System.out.println("x = " + x); //void methodInFirstLevel(int x) 메소드의 인수로 받은 x System.out.println("y = " + y); //myConsumer.accept() 함수를 호출할 때 x가 y가 됨. System.out.println("z = " + z); //methodInFirstlevel() 메소드의 지역변수 System.out.println("this.x = " + this.x); //FirstLevel 이너클래스의 인스턴스 변수 System.out.println("LambdaScopeTest.this.x = " + LambdaScopeTest.this.x); //LambdaScopeTest의 인스턴스 변수 }; myConsumer.accept(x); } } public static void main(String... args) { LambdaScopeTest st = new LambdaScopeTest(); LambdaScopeTest.FirstLevel fl = st.new FirstLevel(); fl.methodInFirstLevel(23); } }
x = 23 y = 23 z = 2 this.x = 1 LambdaScopeTest.this.x = 0
만약 람다 표현식 myConsumer의 선언에서 파라미터 y 대신에 x를 사용한다면, 컴파일러는 오류를 생성합니다.
Consumer<Integer> myConsumer = (x) -> { // ... }
컴파일러는 "람다 표현식의 파라미터 x는 둘러싸는 범위(enclosing scope)에서 정의된 다른 로컬 변수를 재선언할 수 없습니다"라는 오류를 생성합니다. 이는 람다 표현식이 새로운 스코프 수준을 도입하지 않기 때문입니다. 따라서 람다 표현식은 둘러싸는 범위의 필드, 메서드 및 로컬 변수에 직접적으로 접근할 수 있습니다. 예를 들어, 람다 표현식은 methodInFirstLevel 메서드의 매개변수 x에 직접적으로 접근합니다. 둘러싸는 클래스의 변수에 접근하기 위해서는 this 키워드를 사용합니다. 이 예제에서 this.x는 멤버 변수 FirstLevel.x를 참조합니다.
그러나 로컬 및 익명 클래스와 마찬가지로, 람다 표현식은 둘러싸는 블록의 로컬 변수와 파라미터에만 접근할 수 있으며, 해당 변수들은 final 또는 effectively final이어야 합니다. 이 예제에서 변수 z는 effectively final입니다. 초기화된 후에는 값이 변경되지 않습니다. 그러나 람다 표현식 myConsumer 내에 다음과 같은 대입문을 추가한다고 가정해 봅시다.
void methodInFirstLevel(int x) { int z = 2; //final이 생략됨 Consumer<Integer> myConsumer = (y) -> { z = 99; //final이므로 수정할 수 없어야 하는데 수정하려고 하니 오류가 발생함. // ... }
이 할당문 때문에 변수
z
는 더 이상 effectively final이 아닙니다. 그 결과로, 자바 컴파일러는 "둘러싸는 범위에서 정의된 로컬 변수z
는 final 또는 effectively final이어야 합니다"와 같은 오류 메시지를 생성합니다.
✅ Lambda Target Typing
람다표현식을 보면 타입을 생략한 경우가 많습니다. 이때 람다 표현식은 타입을 어떻게 유추해낼까요? 18세~에서 25세 사이의 남성 구원원을 선택한 람다 표현식을 다시 살펴보겠습니다.
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
➡️ 이 람다 표현식은 다음 두가지 메서드가 사용되었습니다.
public static void printPersons(List<Person> roster, CheckPerson tester)
public void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester)
printPersons 메서드를 호출할 때, CheckPerson 데이터 타입이 필요하므로 람다 표현식은 이 타입을 갖습니다. 그러나 자바 실행 시 printPersonsWithPredicate 메서드를 호출할 때는 Predicate<Person> 데이터 타입이 필요하므로 람다 표현식은 이 타입을 갖습니다. 이러한 메서드들이 기대하는 데이터 타입을 타겟 타입(target type)이라고 합니다. 람다 표현식의 타입을 결정하기 위해 자바 컴파일러는 람다 표현식이 발견된 문맥 또는 상황의 타겟 타입을 사용합니다. 이는 자바 컴파일러가 타겟 타입을 결정할 수 있는 상황에서만 람다 표현식을 사용할 수 있다는 것을 의미합니다.
✅ Method References
람다 표현식은 익명 메서드를 생성하는 데 사용됩니다. 그러나 때로는 람다 표현식이 기존의 메서드를 호출하는 일만을 수행하는 경우가 있습니다. 이러한 경우에는 기존의 메서드를 이름으로 참조하는 것이 보다 명확할 때가 많습니다. 메서드 참조를 사용하면 이를 가능하게 할 수 있습니다. 메서드 참조는 이미 이름이 있는 메서드에 대한 간결하고 읽기 쉬운 람다 표현식입니다.
- Person
public class Person { // ... LocalDate birthday; public int getAge() { // ... } public LocalDate getBirthday() { return birthday; } public static int compareByAge(Person a, Person b) { return a.birthday.compareTo(b.birthday); } // ... }
소셜 네트워킹 애플리케이션의 회원들이 배열에 포함되어 있고, 그들을 나이순으로 정렬하고 싶다고 가정해 봅시다. 다음과 같은 코드를 사용할 수 있습니 (이 섹션에서 설명하는 코드 조각은 예제인 MethodReferencesTest에서 찾을 수 있습니다).
//List에 정의되어있는 객체 요소를 [] 배열로 변환 Person[] rosterAsArray = roster.toArray(new Person[roster.size()]); class PersonAgeComparator implements Comparator<Person> { public int compare(Person a, Person b) { return a.getBirthday().compareTo(b.getBirthday()); } } Arrays.sort(rosterAsArray, new PersonAgeComparator());
➡️ 이 sort 호출의 메서드 시그니처는 다음과 같습니다.
static <T> void sort(T[] a, Comparator<? super T> c)
인터페이스 Comparator가 함수형 인터페이스라는 것을 알아차리셨을 것입니다. 따라서 Comparator를 구현하는 새로운 클래스를 정의하고 생성하는 대신 다음과 같이 람다 표현식을 사용할 수 있습니다.
Arrays.sort(rosterAsArray, (Person a, Person b) -> { return a.getBirthday().compareTo(b.getBirthday()); } );
그러나 Person 인스턴스의 생년월일을 비교하는 이 메서드는 이미 Person.compareByAge로 존재합니다. 람다 표현식의 본문에서 이 메서드를 대신 호출할 수 있습니다.
Arrays.sort(rosterAsArray, (a, b) -> Person.compareByAge(a, b) );
이 람다 표현식은 기존의 메서드를 호출하기 때문에, 람다 표현식 대신 메서드 참조를 사용할 수 있습니다.
Arrays.sort(rosterAsArray, Person::compareByAge);
이는 Person 객체의 정의되어 있는 compareByAge() 메소드를 호출하는 방법입니다. 람다식 표현에서 기존의 메서드를 호출할 때에 위와 같이 메소드 참조식으로 사용할 수 있습니다. 즉, 아래에서 보이는 람다 표현식과 메소드 참조는 동일합니다.
//람다 표현식 (a, b) -> Person.compareByAge(a, b) //메소드 참조 Person::compareByAge
🏷️이미지 출처 및 참고한 사이트
Uploaded by N2T