>  기사  >  Java  >  Java의 람다 표현식 구문 설명

Java의 람다 표현식 구문 설명

高洛峰
高洛峰원래의
2017-01-23 15:42:101702검색

구문 설명

람다 표현식은 다음 부분으로 구성됩니다.

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 roster, CheckPerson tester) - 옵션 3

public void printPersonsWithPredicate(List roster, Predicate tester) - 옵션 6



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中文网!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.