lambda 표현식을 사용할 때 타입 추론이 작동하지 않는 별난 시나리오가 있습니다. 내 실제 시나리오의 근사치는 다음과 같습니다.
static class Value<T> {
}
@FunctionalInterface
interface Bar<T> {
T apply(Value<T> value); // Change here resolves error
}
static class Foo {
public static <T> T foo(Bar<T> callback) {
}
}
void test() {
Foo.foo(value -> true).booleanValue(); // Compile error here
}
마지막 줄에서 두 번째로 오는 컴파일 오류는 다음과 같습니다.
메소드 booleanValue ()가 Object 유형에 대해 정의되지 않았습니다.
내가 람다를에 던지면Bar<Boolean>
:
Foo.foo((Bar<Boolean>)value -> true).booleanValue();
또는 메소드 서명을 변경하면Bar.apply
원시 형식을 사용하는 방법 :
T apply(Value value);
그러면 문제는 사라집니다. 이것이 내가 기대할 수있는 방법은 다음과 같습니다.
Foo.foo
호출은 다음과 같은 반환 유형을 추론해야합니다.boolean
value
람다에서Value<Boolean>
.이 추론이 예상대로 작동하지 않는 이유는 무엇이며 어떻게이 API를 변경하여 예상대로 작동하게 할 수 있습니까?
일부 숨김 사용하기javac
기능을 사용하면 어떤 일이 일어나고 있는지에 대한 자세한 정보를 얻을 수 있습니다.
$ javac -XDverboseResolution=deferred-inference,success,applicable LambdaInference.java
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Foo.foo(value -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: <none>
with type-args: no arguments
candidates:
#0 applicable method found: <T>foo(Bar<T>)
(partially instantiated to: (Bar<Object>)Object)
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
Foo.foo(value -> true).booleanValue(); // Compile error here
^
instantiated signature: (Bar<Object>)Object
target-type: <none>
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: error: cannot find symbol
Foo.foo(value -> true).booleanValue(); // Compile error here
^
symbol: method booleanValue()
location: class Object
1 error
이것은 많은 정보입니다.
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Foo.foo(value -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: <none>
with type-args: no arguments
candidates:
#0 applicable method found: <T>foo(Bar<T>)
(partially instantiated to: (Bar<Object>)Object)
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
단계:방법 적용 단계
실제 : 전달 된 실제 인수
type-args : 명시 적 형식 인수
후보자 :잠재적으로 적용 가능한 방법
현재는<none>
암묵적으로 입력 된 람다가적용 가능성과 관련있다..
컴파일러는 당신의 호출을 해결합니다.foo
명명 된 유일한 방법으로foo
...에서Foo
. 그것은 부분적으로 인스턴스화되었습니다.Foo.<Object> foo
(실제 또는 형식 인수가 없었으므로) 지연 추론 단계에서 변경 될 수 있습니다.
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
Foo.foo(value -> true).booleanValue(); // Compile error here
^
instantiated signature: (Bar<Object>)Object
target-type: <none>
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
인스턴스화 된 서명 :의 완전히 인스턴스화 된 서명foo
. 이 단계의 결과입니다 (이 시점에서 더 이상의 형식 유추는foo
).
target-type : 호출이 수행되는 컨텍스트입니다. 메소드 호출이 할당의 일부이면 왼쪽이됩니다. 메소드 호출 자체가 메소드 호출의 일부이면 매개 변수 유형이됩니다.
메소드 호출이 매달 기 때문에 대상 유형이 없습니다. 목표 유형이 없으므로 추측을 더 이상 수행 할 수 없습니다.foo
과T
될 것으로 추측된다.Object
.
컴파일러는 추측 중에 암시 적으로 형식화 된 람다를 사용하지 않습니다. 어느 정도까지는 이것이 합리적입니다. 일반적으로 주어진param -> BODY
, 당신은 컴파일 할 수 없을 것입니다.BODY
당신이 타입을 가질 때까지param
. 타입을 추측하려고 시도했다면param
...에서BODY
, 닭고기와 계란 타입의 문제를 일으킬 수 있습니다. Java의 향후 릴리스에서이를 개선 할 가능성이 있습니다.
Foo.<Boolean> foo(value -> true)
이 솔루션은에 대한 명시적인 형식 인수를 제공합니다.foo
(with type-args
섹션 참조). 이렇게하면 메소드 서명의 부분 인스턴스화가 다음으로 변경됩니다.(Bar<Boolean>)Boolean
, 그것은 당신이 원하는 것입니다.
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Foo.<Boolean> foo(value -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: <none>
with type-args: Boolean
candidates:
#0 applicable method found: <T>foo(Bar<T>)
(partially instantiated to: (Bar<Boolean>)Boolean)
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: resolving method booleanValue in type Boolean to candidate 0
Foo.<Boolean> foo(value -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: no arguments
with type-args: no arguments
candidates:
#0 applicable method found: booleanValue()
Foo.foo((Value<Boolean> value) -> true)
이 솔루션은 람다를 명시 적으로 입력하므로 적용 가능성과 관련이 있습니다 (참고with actuals
이하). 이렇게하면 메소드 서명의 부분 인스턴스화가 다음으로 변경됩니다.(Bar<Boolean>)Boolean
, 그것은 당신이 원하는 것입니다.
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Foo.foo((Value<Boolean> value) -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: Bar<Boolean>
with type-args: no arguments
candidates:
#0 applicable method found: <T>foo(Bar<T>)
(partially instantiated to: (Bar<Boolean>)Boolean)
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
Foo.foo((Value<Boolean> value) -> true).booleanValue(); // Compile error here
^
instantiated signature: (Bar<Boolean>)Boolean
target-type: <none>
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: resolving method booleanValue in type Boolean to candidate 0
Foo.foo((Value<Boolean> value) -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: no arguments
with type-args: no arguments
candidates:
#0 applicable method found: booleanValue()
Foo.foo((Bar<Boolean>) value -> true)
위와 같지만 약간 다른 맛이납니다.
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Foo.foo((Bar<Boolean>) value -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: Bar<Boolean>
with type-args: no arguments
candidates:
#0 applicable method found: <T>foo(Bar<T>)
(partially instantiated to: (Bar<Boolean>)Boolean)
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
Foo.foo((Bar<Boolean>) value -> true).booleanValue(); // Compile error here
^
instantiated signature: (Bar<Boolean>)Boolean
target-type: <none>
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: resolving method booleanValue in type Boolean to candidate 0
Foo.foo((Bar<Boolean>) value -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: no arguments
with type-args: no arguments
candidates:
#0 applicable method found: booleanValue()
Boolean b = Foo.foo(value -> true)
이 솔루션은 메서드 호출에 대한 명시적인 대상을 제공합니다 (target-type
이하). 이것은 연기 된 인스턴스화가 타입 매개 변수가Boolean
대신에Object
(만나다instantiated signature
이하).
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Boolean b = Foo.foo(value -> true);
^
phase: BASIC
with actuals: <none>
with type-args: no arguments
candidates:
#0 applicable method found: <T>foo(Bar<T>)
(partially instantiated to: (Bar<Object>)Object)
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
Boolean b = Foo.foo(value -> true);
^
instantiated signature: (Bar<Boolean>)Boolean
target-type: Boolean
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
이것은 발생하는 동작입니다. 이것이 JLS에 지정되어 있는지 여부는 알 수 없습니다. 이 동작을 지정하는 정확한 섹션을 찾을 수 있는지 살펴볼 수는 있지만형식 유추표기법이 나에게 두통을줍니다.
이것은 또한 왜 변화하는지 완전히 설명하지 못한다.Bar
익지 않는 것을 사용하는Value
이 문제를 해결할 것입니다 :
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Foo.foo(value -> true).booleanValue();
^
phase: BASIC
with actuals: <none>
with type-args: no arguments
candidates:
#0 applicable method found: <T>foo(Bar<T>)
(partially instantiated to: (Bar<Object>)Object)
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
Foo.foo(value -> true).booleanValue();
^
instantiated signature: (Bar<Boolean>)Boolean
target-type: <none>
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: resolving method booleanValue in type Boolean to candidate 0
Foo.foo(value -> true).booleanValue();
^
phase: BASIC
with actuals: no arguments
with type-args: no arguments
candidates:
#0 applicable method found: booleanValue()
어떤 이유로 원시를 사용하도록 변경Value
연기 된 인스턴스 생성을 통해T
~이다.Boolean
. 만약 내가 추측해야만한다면, 나는 컴파일러가 람다를Bar<T>
, 그것은 그것이T
~이다.Boolean
람다의 몸을보고. 이것은 내가 이전에 분석 한 내용이 정확하지 않다는 것을 의미합니다. 컴파일러양철통람다 본문에 형식 유추를 수행하지만 형식 변수에 대해서만 수행합니다.만반환 유형에 나타납니다.
람다 매개 변수 유형에 대한 추론은 람다 본문에 의존 할 수 없습니다.
컴파일러는 암시적인 람다 식을 이해하려고하는 어려운 작업에 직면합니다.
foo( value -> GIBBERISH )
유형value
GIBBERISH가 컴파일되기 전에 먼저 추측되어야합니다. GIBBERISH의 해석은 일반적으로value
.
(당신의 특별한 경우에, GIBBERISH는value
.)
Javac은 유추해야 함Value<T>
매개 변수에 대한 첫 번째value
; 상황에 제약이 없습니다.T=Object
. 그런 다음, λ 본체true
부울로 컴파일되고 인식됩니다.T
.
함수 인터페이스를 변경 한 후에는 람다 매개 변수 유형에 유추가 필요하지 않습니다. T는 유익하지 않다. 다음으로 람다 본문이 컴파일되고 반환 형식이 부울로 나타나며이 부울은T
.
문제를 보여주는 또 다른 예
<T> void foo(T v, Function<T,T> f) { ... }
foo("", v->42); // Error. why can't javac infer T=Object ?
T는 유추 된 것으로 추측된다.String
; 람다의 몸은 추론에 참여하지 않았다.
이 예제에서 javac의 동작은 매우 합리적인 것처럼 보입니다. 프로그래밍 오류를 막을 가능성이 높습니다. 추론이 너무 강력하지 않기를 바랄뿐입니다. 우리가 작성한 모든 내용이 어떻게 든 컴파일되면 컴파일러가 오류를 찾는 데 대한 자신감을 잃어 버리게됩니다.
람다 바디가 확실한 제약 조건을 제공하는 것처럼 보이지만 컴파일러는 그 정보를 사용할 수없는 다른 예가 있습니다. Java에서 람다 매개 변수 유형은 먼저 확인 되어야만 본문을 볼 수 있습니다. 이는 신중한 결정입니다. 대조적으로, C #은 다른 매개 변수 유형을 시도하여 코드를 컴파일하게 만듭니다. Java는 너무 위험하다고 생각합니다.
어쨌든 암묵적 람다가 실패 할 때, 잦은 일이 발생하면 람다 매개 변수에 명시적인 유형을 제공하십시오. 너의 경우에,(Value<Boolean> value)->true
이것을 고치는 쉬운 방법은 메소드 호출에 대한 타입 선언이다.foo
:
Foo.<Boolean>foo(value -> true).booleanValue();
편집하다:왜 이것이 필요한지에 대한 구체적인 문서를 찾을 수 없습니다. 다른 모든 사람들과 거의 같습니다. 나는 그것이 원시적 인 유형들로 인한 것 같다고 생각했지만 그것은 옳지 않았다. 그럼에도 불구하고이 구문은 a를 사용하여 호출됩니다.대상 유형. 또한람다의 타겟 유형. 그 이유는 나를 도피하지만, 왜이 특별한 사용 사례가 필요한지에 대한 문서를 찾을 수 없습니다.
편집 2 :나는이 관련된 질문을 발견 :
여기에 방법을 연결하고 있기 때문에 그 것처럼 보입니다. 거기에 수용된 대답에서 참조 된 JSR 주석에 따르면, 컴파일러는 유추 된 일반 형식 정보를 양방향으로 연결된 메서드 호출간에 전달하는 방법이 없기 때문에 고의적 인 기능 생략이있었습니다. 결과적으로 전체 유형이 지워지고 시간이 지남에 따라booleanValue
. 대상 유형을 추가하면 컴파일러가 아래에 요약 된 규칙을 사용하여 결정을 내리는 대신 수동으로 제약 조건을 제공하여이 동작을 제거합니다.JLS §18, 이것은 전혀 언급하지 않는 것 같습니다. 이것은 내가 생각해 낼 수있는 유일한 정보입니다. 누구든지 더 나은 것을 발견하면 나는 그것을보고 싶다.
T apply(Value value);
어떤 긍정적 인 효과를 가지지 만, 그렇다. - sstanBoolean b = Foo.foo(value -> true);
설명하는 구문 (Foo.<Boolean> foo(value->true)
)는목격하다. - Jeffrey
다른 답변과 마찬가지로 나는 똑똑한 사람이 지적 할 수 있기를 바라고 있습니다.왜컴파일러는 그것을 추론 할 수 없다.T
~이다.Boolean
.
컴파일러를 도와주는 한 가지 방법은 기존 클래스 / 인터페이스 디자인을 변경하지 않고도 람다 식의 형식 매개 변수 형식을 명시 적으로 선언하는 것입니다. 따라서이 경우 명시 적으로value
매개 변수는Value<Boolean>
.
void test() {
Foo.foo((Value<Boolean> value) -> true).booleanValue();
}
Foo.foo
호출은 boolean의 반환 유형을 유추합니다.value
람다에서Value<Boolean>
. 이 API는 공개 API의 일부이므로 전송하고 싶지 않으며 캐스트를 요구하면 사용성 문제가 발생합니다. - Josh Stone
왜 그런지는 모르지만 별도의 반환 유형을 추가해야합니다.
public class HelloWorld{
static class Value<T> {
}
@FunctionalInterface
interface Bar<T,R> {
R apply(Value<T> value); // Return type added
}
static class Foo {
public static <T,R> R foo(Bar<T,R> callback) {
return callback.apply(new Value<T>());
}
}
void test() {
System.out.println( Foo.foo(value -> true).booleanValue() ); // No compile error here
}
public static void main(String []args){
new HelloWorld().test();
}
}
어떤 똑똑한 사람이 아마 그것을 설명 할 수 있습니다.
value
람다 표현식에서Value<Object>
대신에Value<Boolean>
여전히 람다 식 몸체에 더 많은 코드가 포함되어있는 실제 시나리오에서 문제를 일으킬 것입니다 ... - Josh Stonestatic class Value<T extends Boolean>, interface Bar<T extends Boolean,R>, public static <T extends Boolean,R> R foo(Bar<T,R> callback)
등 ... - fukanchik
값은 유형을 추측합니다.Value<Object>
왜냐하면 당신은 람다를 잘못 해석했기 때문입니다. 당신이 람다 (lambda)를 직접 호출하는 것처럼 적용 방법을 직접 생각해보십시오. 그래서 당신이하는 일은 :
Boolean apply(Value value);
이것은 정확하게 다음과 같이 추론됩니다.
Boolean apply(Value<Object> value);
Value에 대한 유형을 지정하지 않았기 때문입니다.
올바른 방법으로 람다에게 전화하십시오 :
Foo.foo((Value<Boolean> value) -> true).booleanValue();
이것은 유추 될 것입니다 :
Boolean apply(Value<Boolean> value);
당신의 솔루션은 좀 더 명확해야합니다. 콜백을 원한다면 리턴 될 타입 값이 필요하다.
제네릭 콜백 인터페이스, 일반 Value 클래스 및 UsingClass를 사용하여이를 사용하는 방법을 보여 줬습니다.
/**
*
* @param <P> The parameter to call
* @param <R> The return value you get
*/
@FunctionalInterface
public interface Callback<P, R> {
public R call(P param);
}
public class Value<T> {
private final T field;
public Value(T field) {
this.field = field;
}
public T getField() {
return field;
}
}
public class UsingClass<T> {
public T foo(Callback<Value<T>, T> callback, Value<T> value) {
return callback.call(value);
}
}
public class TestApp {
public static void main(String[] args) {
Value<Boolean> boolVal = new Value<>(false);
Value<String> stringVal = new Value<>("false");
Callback<Value<Boolean>, Boolean> boolCb = (v) -> v.getField();
Callback<Value<String>, String> stringCb = (v) -> v.getField();
UsingClass<Boolean> usingClass = new UsingClass<>();
boolean val = usingClass.foo(boolCb, boolVal);
System.out.println("Boolean value: " + val);
UsingClass<String> usingClass1 = new UsingClass<>();
String val1 = usingClass1.foo(stringCb, stringVal);
System.out.println("String value: " + val1);
// this will give you a clear and understandable compiler error
//boolean val = usingClass.foo(boolCb, stringVal);
}
}
value -> true
어떤 가치가 유추 될지Value<Boolean>
그리고 람다를 받아들이는 메소드는 리턴 할 것이다.Boolean
. 다른 모든 변경 사항은 괜찮지 만 API를 사용할 수 있도록 유지해야합니다. - Josh Stonefoo
이 일을하기 위해 하나의 주장을 받아들이십시오. - Josh Stone
Value<Object>
대신에Value<Boolean>
그게 내가 한 일이야. 그것이 작동하도록하는 방법에 대한 아이디어를 자유롭게 공유하십시오. - Josh Stone