구문 설명
람다 표현식은 다음 부분으로 구성됩니다.
1. 괄호 안의 쉼표로 구분된 형식 매개변수 목록입니다. CheckPerson.test 메소드에는 Person 클래스의 인스턴스를 나타내는 매개변수 p가 포함되어 있습니다. 참고: 람다 식에서 매개 변수의 유형은 생략할 수 있으며, 매개 변수가 하나만 있는 경우 괄호도 생략할 수 있습니다. 예를 들어 이전 섹션에서 언급한 코드:
p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25
2. 화살표 기호: ->. 매개변수와 함수 본문을 구분하는 데 사용됩니다.
3. 함수 본체. 표현식 또는 코드 블록으로 구성됩니다. 이전 섹션의 예에서는 다음과 같은 표현식이 사용되었습니다.
p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25
표현식이 사용되면 Java 런타임은 표현식의 값을 계산하고 반환합니다. 또한 코드 블록에서 return 문을 사용하도록 선택할 수도 있습니다.
p -> { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; }
그러나 return 문은 표현식이 아닙니다. 람다 식에서는 문을 중괄호로 묶어야 합니다. 그러나 null 값을 반환하는 메서드를 호출하는 경우에는 문을 중괄호로 묶을 필요가 없으므로 다음과 같이 쓰는 것도 맞습니다. >람다 표현식과 메소드 선언은 매우 비슷해 보입니다. 따라서 람다 식은 익명 메서드, 즉 정의된 이름이 없는 메서드로 간주될 수도 있습니다.
위에서 언급한 람다 표현식은 모두 하나의 매개변수만을 형식 매개변수로 사용하는 표현식입니다. 다음 인스턴스 클래스인 Caulator는 여러 매개변수를 형식 매개변수로 사용하는 방법을 보여줍니다.
email -> System.out.println(email)
코드의 OperaBinary 메소드는 두 개의 정수 매개변수를 사용하여 산술 연산을 수행합니다. 여기서 산술 연산 자체는 IntegerMath 인터페이스의 인스턴스입니다. 위 프로그램에서는 람다 식을 사용하여 두 가지 산술 연산인 덧셈과 뺄셈을 정의합니다. 실행 프로그램은 다음을 인쇄합니다.
package com.zhyea.zytools; 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)); } }
외부 클래스의 지역 변수에 액세스
로컬 클래스나 익명 클래스와 마찬가지로 람다 식도 외부 클래스의 지역 변수에 액세스할 수 있습니다. 차이점은 람다 식을 사용할 때 덮어쓰기 등의 문제를 고려할 필요가 없다는 점입니다. 람다 식은 단지 어휘 개념일 뿐입니다. 즉, 슈퍼클래스에서 이름을 상속받을 필요도 없고 새로운 범위를 도입할 필요도 없습니다. 즉, 람다 식 내의 선언은 외부 환경의 선언과 동일한 의미를 갖습니다. 이는 다음 예에서 설명됩니다.
40 + 2 = 42 20 - 10 = 10
이 코드는 다음을 출력합니다.
package com.zhyea.zytools; import java.util.function.Consumer; public class LambdaScopeTest { public int x = 0; class FirstLevel { public int x = 1; void methodInFirstLevel(int x) { //如下的语句会导致编译器在statement A处报错“local variables referenced from a lambda expression must be final or effectively final” // x = 99; Consumer<integer> myConsumer = (y) ->{ System.out.println("x = " + x); // Statement A System.out.println("y = " + y); System.out.println("this.x = " + this.x); System.out.println("LambdaScopeTest.this.x = " + LambdaScopeTest.this.x); }; myConsumer.accept(x); } } public static void main(String... args) { LambdaScopeTest st = new LambdaScopeTest(); LambdaScopeTest.FirstLevel fl = st.new FirstLevel(); fl.methodInFirstLevel(23); } }
예제에서 람다 식 myConsumer의 매개 변수 y가 x로 바뀌면 컴파일러는 오류를 보고합니다.
x = 23 y = 23 this.x = 1 LambdaScopeTest.this.x = 0
컴파일러 오류 메시지는 " 변수 x는 이미 methodInFirstLevel(int) 메소드에 정의되어 있습니다. 이는 변수 x가 메소드 methodInFirstLevel에 정의되었음을 의미합니다. 람다 표현식이 새 범위를 도입하지 않기 때문에 오류가 보고됩니다. 따라서 람다 식에서 외부 클래스의 필드 필드, 메서드 및 형식 매개 변수에 직접 액세스할 수 있습니다. 이 예에서 람다 식 myConsumer는 methodInFirstLevel 메서드의 형식 매개 변수 x에 직접 액세스합니다. 외부 클래스의 멤버에 액세스할 때 this 키워드를 직접 사용할 수도 있습니다. 이 예에서 this.x는 FirstLevel.x를 나타냅니다.
그러나 로컬 클래스나 익명 클래스와 마찬가지로 람다 식은 final(또는 final과 동일)로 선언된 로컬 변수나 외부 멤버에만 액세스할 수 있습니다. 예를 들어 샘플 코드의 methodInFirstLevel 메소드에서 "x=99" 앞에 있는 주석을 제거합니다.
Consumer<integer> myConsumer = (x) ->{ // .... };
여기서 매개변수 x의 값이 수정되기 때문입니다. 명령문, methodInFirstLevel 매개변수 x는 더 이상 최종으로 간주될 수 없습니다. 따라서 Java 컴파일러는 람다 표현식이 로컬 변수 x에 액세스하는 경우 "람다 표현식에서 참조되는 지역 변수는 최종이거나 사실상 최종이어야 합니다"와 같은 오류를 보고합니다.
대상 유형
람다 표현식의 유형을 결정하는 방법. 군 복무 연령 인사를 심사하는 코드를 살펴보겠습니다:
//如下的语句会导致编译器在statement A处报错“local variables referenced from a lambda expression must be final or effectively final” x = 99; Consumer<integer> myConsumer = (y) ->{ System.out.println("x = " + x); // Statement A System.out.println("y = " + y); System.out.println("this.x = " + this.x); System.out.println("LambdaScopeTest.this.x = " + LambdaScopeTest.this.x); };
이 코드는 두 곳에서 사용되었습니다:
public static void printPersons (List
printPersons 메소드를 호출할 때, this 이 메소드는 CheckPerson 유형의 매개변수를 기대하며 위의 표현식은 CheckPerson 유형의 표현식입니다. printPersonsWithPredicate 메소드를 호출할 때 Predicate8abf60ac54173a2785e603c7a1f95b4e 유형의 매개변수가 예상되며 동일한 표현식은 Predicate8abf60ac54173a2785e603c7a1f95b4e 유형입니다. 이처럼 메소드에서 기대하는 타입에 따라 결정되는 타입을 타겟 타입(target type)이라고 합니다. (사실 여기서는 Scala의 타입 추론이 더 적합하다고 생각합니다.) Java 컴파일러는 대상 유형의 컨텍스트 또는 람다 표현식이 있는 위치를 통해 람다 표현식의 유형을 결정합니다. 이는 Java 컴파일러가 유형을 추론할 수 있는 경우에만 람다 표현식을 사용할 수 있음을 의미합니다.
할당;
배열 초기화;
메서드 또는 생성자 매개변수
람다 표현식 메소드 본문;
예외가 발생하는 경우.
대상 유형 및 메소드 매개변수对于方法参数,Java编译器还需要依赖两个语言特性来决定目标类型:重载解析和类型参数推断。
看一下下面的这两个函数式接口( java.lang.Runnable and java.util.concurrent.Callabled94943c0b4933ad8cac500132f64757f):
public interface Runnable { void run(); } public interface Callable<v> { V call(); }
Runnable.run()方法没有返回值,而Callable.call()方法有。
假设我们像下面这样重载了invoke方法:
void invoke(Runnable r) { r.run(); } <t> T invoke(Callable<t> c) { return c.call(); }
那么在下面的语句中将会调用哪个方法呢:
String s = invoke(() -> "done");
调用的是invoke(Callable8742468051c85b06f0a0af9e3e506b5c),因为这个方法有返回值,而invoke(Runnable8742468051c85b06f0a0af9e3e506b5c)没有返回值。在这种情况下lambda表达式(() -> “done”)的类型是Callable8742468051c85b06f0a0af9e3e506b5c。
序列化
如果一个lambda表达式的目标类型还有它调用的参数的类型都是可序列化的,那么lambda表达式也是可序列化的。然而就像内部类一样,强烈不建议对lambda表达式进行序列化。
更多java中lambda表达式语法说明相关文章请关注PHP中文网!