[java] 이 Java 8 람다가 컴파일에 실패하는 이유는 무엇입니까?

다음 Java 코드는 컴파일에 실패합니다.

@FunctionalInterface
private interface BiConsumer<A, B> {
    void accept(A a, B b);
}

private static void takeBiConsumer(BiConsumer<String, String> bc) { }

public static void main(String[] args) {
    takeBiConsumer((String s1, String s2) -> new String("hi")); // OK
    takeBiConsumer((String s1, String s2) -> "hi"); // Error
}

컴파일러는 다음을보고합니다.

Error:(31, 58) java: incompatible types: bad return type in lambda expression
    java.lang.String cannot be converted to void

이상한 점은 “OK”로 표시된 줄은 잘 컴파일되지만 “Error”로 표시된 줄은 실패한다는 것입니다. 본질적으로 동일 해 보입니다.



답변

람다는와 일치해야합니다 BiConsumer<String, String>. JLS # 15.27.3 (Lambda 유형) 을 참조하는 경우 :

다음 사항이 모두 참인 경우 람다 식은 함수 유형과 일치합니다.

  • […]
  • 함수 형식의 결과가 void이면 람다 본문은 문 식 (§14.8) 또는 void 호환 블록입니다.

따라서 람다는 명령문 표현식이거나 무효 호환 블록이어야합니다.

  • 생성자 호출은 명령문 표현식 이므로 컴파일됩니다.
  • 문자열 리터럴은 명령문 표현식이 아니며 무효 호환이되지 않으므로 ( 15.27.2의 예제 참조 ) 컴파일되지 않습니다.


답변

기본적으로 new String("hi")는 실제로 무언가를 수행하는 실행 가능한 코드 조각입니다 (새 문자열을 생성 한 다음 반환합니다). 반환 된 값은 무시할 수 있으며 new String("hi")새 문자열을 생성하기 위해 void-return 람다에서 계속 사용할 수 있습니다.

그러나은 "hi"자체적으로 아무것도하지 않는 상수 일뿐입니다. 람다 본문에서 그것으로 할 수있는 유일한 합리적인 일은 그것을 반환 하는 것입니다. 그러나 람다 메서드는 반환 유형 String또는 을 가져야 하지만를 Object반환 void하므로 String cannot be casted to void오류가 발생합니다.


답변

첫 번째 경우는 “특별한”메서드 (생성자)를 호출하고 실제로 생성 된 객체를 사용하지 않기 때문에 괜찮습니다. 더 명확하게하기 위해 람다에 선택적 중괄호를 넣겠습니다.

takeBiConsumer((String s1, String s2) -> {new String("hi");}); // OK
takeBiConsumer((String s1, String s2) -> {"hi"}); // Error

그리고 더 명확하게, 나는 그것을 이전 표기법으로 번역 할 것입니다.

takeBiConsumer(new BiConsumer<String, String>(String s1, String s2) {
    public void accept(String s, String s2) {
        new String("hi"); // OK
    }
});

takeBiConsumer(new BiConsumer<String, String>(String s1, String s2) {
    public void accept(String s, String s2) {
        "hi"; // Here, the compiler will attempt to add a "return"
              // keyword before the "hi", but then it will fail
              // with "compiler error ... bla bla ...
              //  java.lang.String cannot be converted to void"
    }
});

첫 번째 경우 생성자를 실행하고 있지만 생성 된 객체를 반환하지 않고 두 번째 경우에는 String 값을 반환하려고하지만 인터페이스의 메서드 BiConsumer가 void를 반환하므로 컴파일러 오류가 발생합니다.


답변

JLS는 다음을 지정합니다.

함수 형식의 결과가 void이면 람다 본문은 문 식 (§14.8) 또는 void 호환 블록입니다.

이제 자세히 살펴 보겠습니다.

귀하의 takeBiConsumer메서드가 무효 유형이므로 람다 수신 new String("hi")은이를 다음과 같은 블록으로 해석합니다.

{
    new String("hi");
}

이것은 void에서 유효하므로 첫 번째 케이스가 컴파일됩니다.

그러나 람다가 -> "hi"인 경우 다음과 같은 블록

{
    "hi";
}

Java에서 유효한 구문이 아닙니다. 그러므로 “hi”로 할 수있는 유일한 일은 그것을 돌려 보내는 것입니다.

{
    return "hi";
}

무효로 유효하지 않으며 오류 메시지를 설명합니다.

incompatible types: bad return type in lambda expression
    java.lang.String cannot be converted to void

더 나은 이해를 위해 유형을 takeBiConsumerString으로 변경 -> "hi"하면 단순히 문자열을 직접 반환하려고 시도하므로 유효합니다.


처음에는 람다가 잘못된 호출 컨텍스트에 있기 때문에 오류가 발생했음을 확인 했으므로이 가능성을 커뮤니티와 공유하겠습니다.

JLS 15.27

할당 컨텍스트 (§5.2), 호출 컨텍스트 (§5.3) 또는 캐스팅 컨텍스트 (§5.5)가 아닌 다른 위치의 프로그램에서 람다식이 발생하면 컴파일 타임 오류입니다.

그러나 우리의 경우 우리 는 올바른 호출 컨텍스트에 있습니다.


답변