왜
public <R, F extends Function<T, R>> Builder<T> withX(F getter, R returnValue) {...}
더 엄격한
public <R> Builder<T> with(Function<T, R> getter, R returnValue) {...}
이것은 컴파일시 람다 리턴 유형이 점검되지 않는 이유 에 대한 후속 조치 입니다. 나는 withX()
같은 방법을 사용하여 발견했다.
.withX(MyInterface::getLength, "I am not a Long")
원하는 컴파일 시간 오류를 생성합니다.
BuilderExample.MyInterface 유형의 getLength () 유형이 길어서 디스크립터의 리턴 유형과 호환되지 않습니다.
방법을 사용하는 동안 with()
하지 않습니다.
전체 예 :
import java.util.function.Function;
public class SO58376589 {
public static class Builder<T> {
public <R, F extends Function<T, R>> Builder<T> withX(F getter, R returnValue) {
return this;
}
public <R> Builder<T> with(Function<T, R> getter, R returnValue) {
return this;
}
}
static interface MyInterface {
public Long getLength();
}
public static void main(String[] args) {
Builder<MyInterface> b = new Builder<MyInterface>();
Function<MyInterface, Long> getter = MyInterface::getLength;
b.with(getter, 2L);
b.with(MyInterface::getLength, 2L);
b.withX(getter, 2L);
b.withX(MyInterface::getLength, 2L);
b.with(getter, "No NUMBER"); // error
b.with(MyInterface::getLength, "No NUMBER"); // NO ERROR !!
b.withX(getter, "No NUMBER"); // error
b.withX(MyInterface::getLength, "No NUMBER"); // error !!!
}
}
javac SO58376589.java
SO58376589.java:32: error: method with in class Builder<T> cannot be applied to given types;
b.with(getter, "No NUMBER"); // error
^
required: Function<MyInterface,R>,R
found: Function<MyInterface,Long>,String
reason: inference variable R has incompatible bounds
equality constraints: Long
lower bounds: String
where R,T are type-variables:
R extends Object declared in method <R>with(Function<T,R>,R)
T extends Object declared in class Builder
SO58376589.java:34: error: method withX in class Builder<T> cannot be applied to given types;
b.withX(getter, "No NUMBER"); // error
^
required: F,R
found: Function<MyInterface,Long>,String
reason: inference variable R has incompatible bounds
equality constraints: Long
lower bounds: String
where F,R,T are type-variables:
F extends Function<MyInterface,R> declared in method <R,F>withX(F,R)
R extends Object declared in method <R,F>withX(F,R)
T extends Object declared in class Builder
SO58376589.java:35: error: incompatible types: cannot infer type-variable(s) R,F
b.withX(MyInterface::getLength, "No NUMBER"); // error
^
(argument mismatch; bad return type in method reference
Long cannot be converted to String)
where R,F,T are type-variables:
R extends Object declared in method <R,F>withX(F,R)
F extends Function<T,R> declared in method <R,F>withX(F,R)
T extends Object declared in class Builder
3 errors
확장 된 예
다음 예는 공급 업체로 분류 된 방법 및 유형 매개 변수의 다른 동작을 보여줍니다. 또한 유형 매개 변수의 소비자 행동과의 차이점을 보여줍니다. 그리고 이는 메소드 매개 변수에 대해 소비자 또는 공급 업체간에 차이를 만들지 않음을 보여줍니다.
import java.util.function.Consumer;
import java.util.function.Supplier;
interface TypeInference {
Number getNumber();
void setNumber(Number n);
@FunctionalInterface
interface Method<R> {
TypeInference be(R r);
}
//Supplier:
<R> R letBe(Supplier<R> supplier, R value);
<R, F extends Supplier<R>> R letBeX(F supplier, R value);
<R> Method<R> let(Supplier<R> supplier); // return (x) -> this;
//Consumer:
<R> R lettBe(Consumer<R> supplier, R value);
<R, F extends Consumer<R>> R lettBeX(F supplier, R value);
<R> Method<R> lett(Consumer<R> consumer);
public static void main(TypeInference t) {
t.letBe(t::getNumber, (Number) 2); // Compiles :-)
t.lettBe(t::setNumber, (Number) 2); // Compiles :-)
t.letBe(t::getNumber, 2); // Compiles :-)
t.lettBe(t::setNumber, 2); // Compiles :-)
t.letBe(t::getNumber, "NaN"); // !!!! Compiles :-(
t.lettBe(t::setNumber, "NaN"); // Does not compile :-)
t.letBeX(t::getNumber, (Number) 2); // Compiles :-)
t.lettBeX(t::setNumber, (Number) 2); // Compiles :-)
t.letBeX(t::getNumber, 2); // !!! Does not compile :-(
t.lettBeX(t::setNumber, 2); // Compiles :-)
t.letBeX(t::getNumber, "NaN"); // Does not compile :-)
t.lettBeX(t::setNumber, "NaN"); // Does not compile :-)
t.let(t::getNumber).be(2); // Compiles :-)
t.lett(t::setNumber).be(2); // Compiles :-)
t.let(t::getNumber).be("NaN"); // Does not compile :-)
t.lett(t::setNumber).be("NaN"); // Does not compile :-)
}
}
답변
이것은 정말 흥미로운 질문입니다. 대답은 복잡합니다.
tl; dr
차이점을 해결하려면 Java 유형 유추 사양 에 대한 깊이있는 독서가 필요 하지만 기본적으로 다음과 같이 요약됩니다.
- 다른 모든 것들이 동일하면 컴파일러는 가능한 가장 구체적인 유형을 유추합니다 .
- 그러나 모든 요구 사항을 충족시키는 형식 매개 변수 의 대체를 찾을 수 있으면 컴파일이 성공하지만 대체는 모호 합니다.
- 다음의
with
모든 요구 사항을 충족시키는 (분명히 모호한) 대체물이 있습니다R
.Serializable
- 를 위해
withX
추가 유형 매개 변수를 도입 하면 제약 조건을 고려하지 않고F
컴파일러가R
먼저 해결 하도록합니다F extends Function<T,R>
.R
(훨씬 더 구체적)으로 해석되어 실패String
추론을 의미F
합니다.
이 마지막 글 머리 기호가 가장 중요하지만 가장 손이 많이 듭니다. 더 간결한 표현 방법을 생각할 수 없으므로 자세한 내용을 보려면 아래의 전체 설명을 읽으십시오.
이것은 의도 된 행동입니까?
여기 사지에 나가서없고, 말할거야 더 .
나는 스펙에 버그가 있다고 제안하는 것이 아니다 withX
. 언어 디자이너가 손을 들어 “타입 추론이 너무 어려워서 실패 할 것”이라고 말했다 . 컴파일러의 동작 withX
이 원하는 것처럼 보이지만, 의도적으로 설계된 디자인 결정이 아니라 현재 사양의 부수적 인 부작용이라고 생각합니다.
이 문제,이 문제에 통지합니다 해야 내 응용 프로그램 디자인이 동작에 의존을?향후 버전의 언어가 계속 이런 식으로 작동한다고 보장 할 수 없기 때문에 사용하지 않아야한다고 주장합니다.
언어 디자이너가 사양 / 디자인 / 컴파일러를 업데이트 할 때 기존 응용 프로그램을 중단하지 않으려 고 노력하는 것은 사실이지만 문제는 의존하려는 동작이 현재 컴파일러 가 실패한 것 (즉 기존 응용 프로그램이 아님)이라는 것입니다. Langauge 업데이트는 항상 비 컴파일 코드를 컴파일 코드로 바꿉니다. 예를 들어, 다음의 코드가 될 수있는 보장 자바 7에서 컴파일하지,하지만 것 자바 8 컴파일 :
static Runnable x = () -> System.out.println();
사용 사례는 다르지 않습니다.
귀하의 withX
방법을 사용할 때주의해야 할 또 다른 이유 는 F
매개 변수 자체입니다. 일반적으로 메소드 의 일반 유형 매개 변수 (반환 유형에 표시되지 않음)는 서명의 여러 부분 유형을 함께 바인딩하기 위해 존재합니다. 그것은 말하고 있습니다 :
나는 무엇인지 상관하지 않지만 T
사용하는 곳마다 T
동일한 유형 인지 확인하고 싶습니다 .
논리적으로, 우리는 각 유형 매개 변수가 메소드 서명에서 적어도 두 번 나타날 것으로 예상합니다. 그렇지 않으면 “아무것도하지 않습니다”. F
당신에 withX
전용하지 인라인 나에게 형식 매개 변수의 사용을 제안 서명, 한 번 나타납니다 의도 언어의 기능을.
대체 구현
좀 더 “의도 된 행동”으로 이것을 구현하는 한 가지 방법은 with
메소드를 2 개의 체인으로 나누는 것 입니다.
public class Builder<T> {
public final class With<R> {
private final Function<T,R> method;
private With(Function<T,R> method) {
this.method = method;
}
public Builder<T> of(R value) {
// TODO: Body of your old 'with' method goes here
return Builder.this;
}
}
public <R> With<R> with(Function<T,R> method) {
return new With<>(method);
}
}
그런 다음 다음과 같이 사용할 수 있습니다.
b.with(MyInterface::getLong).of(1L); // Compiles
b.with(MyInterface::getLong).of("Not a long"); // Compiler error
여기에는 외부와 같은 유형의 매개 변수가 포함되어 있지 않습니다 withX
. 이 방법을 두 가지 서명으로 나누면 형식 안전성 관점에서 수행하려는 작업의 의도를 더 잘 표현할 수 있습니다.
- 첫 번째 방법은 다음
With
을 정의 하는 클래스 ( )를 설정 합니다. 메소드 참조를 기반으로 유형 . - scond 메소드 (
of
) 는 이전에 설정 한 것과 호환되도록 유형을 제한 합니다value
.
향후 버전의 언어에서이를 컴파일 할 수있는 유일한 방법은 구현 된 전체 오리 타이핑을하는 것입니다.
: 마지막으로 노트는이 모든 일이 무관 만들려면 내 생각 Mockito를 (특히 그 스터 빙 기능) 기본적으로 이미 당신이 당신의 “를 입력 안전 일반적인 빌더”를 달성하기 위해 노력하고 일을 할 수 있습니다. 어쩌면 그냥 대신 사용할 수 있습니까?
전체 설명
과에 대한 형식 유추 절차 를 진행하겠습니다 . 꽤 길어서 천천히 가져 가세요. 길지만, 나는 여전히 많은 세부 사항을 남겼습니다. 자세한 내용은 (링크를 따라) 사양을 참조하여 본인이 옳다는 것을 스스로에게 확신 시키십시오 (실수했을 수도 있음).with
withX
또한 약간 단순화하기 위해 더 적은 코드 샘플을 사용하겠습니다. 가장 큰 차이점은 스왑 아웃이다 Function
위해 Supplier
, 그래서 덜 유형과 놀이의 매개 변수가 있습니다. 다음은 설명 된 동작을 재현하는 전체 스 니펫입니다.
public class TypeInference {
static long getLong() { return 1L; }
static <R> void with(Supplier<R> supplier, R value) {}
static <R, F extends Supplier<R>> void withX(F supplier, R value) {}
public static void main(String[] args) {
with(TypeInference::getLong, "Not a long"); // Compiles
withX(TypeInference::getLong, "Also not a long"); // Does not compile
}
}
각 메서드 호출에 대한 형식 적용 가능성 유추 및 형식 유추 절차를 차례로 살펴 보겠습니다 .
with
우리는 :
with(TypeInference::getLong, "Not a long");
초기 경계 세트 B 0 은 다음과 같습니다.
R <: Object
모든 매개 변수 표현식은 적용 가능성 과 관련이 있습니다.
따라서, 초기 제약 세트 적용 추론은 , C가 있다 :
TypeInference::getLong
와 호환Supplier<R>
"Not a long"
와 호환R
이것은 다음의 경계 세트 B 2 로 줄어 듭니다 .
R <: Object
( B 0 부터 )Long <: R
(첫 번째 제약에서)String <: R
(두 번째 제약 조건에서)
여기에는 바운드 ‘ false ‘가 포함되어 있지 않으며 성공 ( 주어진)의 해결 방법 이 있으므로 호출이 가능합니다.R
Serializable
그래서 우리는 호출 타입 추론 으로 넘어갑니다. .
입력 및 출력 변수 와 관련된 새로운 제약 조건 세트 C 는 다음과 같습니다.
TypeInference::getLong
와 호환Supplier<R>
- 입력 변수 : none
- 출력 변수 :
R
여기에는 입력 변수 와 출력 변수 간에 상호 종속성이 없으므로 한 단계 로 줄일 수 있으며 최종 바운드 세트 B 4 는 B 2 와 같습니다 . 따라서 이전과 같이 해결이 성공하고 컴파일러는 한숨을 쉬게합니다!
withX
우리는 :
withX(TypeInference::getLong, "Also not a long");
초기 경계 세트 B 0 은 다음과 같습니다.
R <: Object
F <: Supplier<R>
두 번째 매개 변수 식만 적용 가능성과 관련이 있습니다. 첫 번째 ( TypeInference::getLong
)는 다음 조건을 충족하므로 그렇지 않습니다.
경우
m
일반적인 방법 및 메소드 호출은 명확한 형태 인수, 명시 적으로 입력 람다 식 또는 대응하는 타겟 타입 (의 특성에서 유래되는 정확한 방법 참조 발현 제공하지 않는다m
)의 입력 파라미터이다m
.
따라서, 초기 제약 세트 적용 추론은 , C가 있다 :
"Also not a long"
와 호환R
이것은 다음의 경계 세트 B 2 로 줄어 듭니다 .
R <: Object
( B 0 부터 )F <: Supplier<R>
( B 0 부터 )String <: R
(제약에서)
이 바운드 ‘가 포함되지 않기 때문에 다시 거짓 ‘및 해상도 의 R
성공 (주는 String
), 다음 호출을 적용한다.
호출 유형 추론 다시 한번 …
이번에는 입력 및 출력 변수 와 관련된 새로운 제약 조건 세트 C 가 다음과 같습니다.
TypeInference::getLong
와 호환F
- 입력 변수 :
F
- 출력 변수 : none
- 입력 변수 :
다시, 우리는 입력 변수 와 출력 변수 사이에 상호 의존성이 없습니다 . 그러나 이번에는 거기에 있다 입력 변수는 ( F
우리가 있어야하므로), 해결 하기 전에이 감소 . 따라서 경계 세트 B 2로 시작 합니다.
-
우리는
V
다음과 같이 부분 집합 을 결정 합니다.해석 할 추론 변수 세트가 주어지면
V
이 세트와이 세트에서 하나 이상의 변수의 분해능이 의존하는 모든 변수를 합치십시오.결합에 의해 상기 제 B (2) 의 해상도
F
에 의존R
하므로,V := {F, R}
. -
우리
V
는 규칙 에 따라 하위 집합을 선택합니다 .하자
{ α1, ..., αn }
의는 uninstantiated 변수의 비어 있지 않은 부분 집합V
모두 같은 내가 그)i (1 ≤ i ≤ n)
경우,αi
변수의 해상도에 따라β
, 다음 중 하나를β
인스턴스화되어 있거나이j
그와 같은β = αj
; 그리고 ii){ α1, ..., αn }
이 속성 에는 비어 있지 않은 적절한 하위 집합이 없습니다 .V
이 속성 의 유일한 하위 집합은 입니다{R}
. -
세 번째 바운드 (
String <: R
)를 사용하여이를 인스턴스화R = String
하고 바운드 세트에 통합합니다.R
이제 해결되고 두 번째 바운드는 효과적으로됩니다F <: Supplier<String>
. -
(수정 된) 두 번째 경계를 사용하여 인스턴스화
F = Supplier<String>
합니다.F
이제 해결되었습니다.
이제 F
해결되었으므로 새로운 제약 조건을 사용하여 축소를 진행할 수 있습니다 .
TypeInference::getLong
와 호환Supplier<String>
- … 와 호환이 줄어든다
Long
String
- … 거짓으로 줄어든다
… 그리고 컴파일러 오류가 발생합니다!
‘확장 예’에 대한 추가 참고 사항
질문 의 확장 예제 는 위의 작업에서 직접 다루지 않는 몇 가지 흥미로운 사례를 보여줍니다.
- 값 유형이 메소드 리턴 유형 의 하위 유형 인 경우 (
Integer <: Number
) - 기능적 인터페이스가 유추 된 유형에서 반 변형 인 경우 (즉,
Consumer
대신Supplier
)
특히, 주어진 호출 중 3 개는 설명에 설명 된 것과 다른 ‘다른’컴파일러 동작을 잠재적으로 제안하는 것으로 눈에 out니다.
t.lettBe(t::setNumber, "NaN"); // Does not compile :-)
t.letBeX(t::getNumber, 2); // !!! Does not compile :-(
t.lettBeX(t::setNumber, 2); // Compiles :-)
이러한 3의 두 번째 정확히 같은 추론 과정을 통해 이동합니다 withX
(바로 교체 위 Long
로 Number
와 String
함께 Integer
). 이것은 클래스 디자인에 실패한 형식 유추 동작에 의존해서는 안되는 또 다른 이유를 보여줍니다. 여기서 컴파일 실패 는 바람직한 동작 이 아닐 수 있습니다.
다른 2 개 (실제로 Consumer
작업하려는 사용자 와 관련된 다른 호출 )의 경우 위의 방법 중 하나에 대해 제시된 형식 유추 절차를 통해 작업하는 경우 (예 : with
첫 번째 withX
경우 제삼). 주의해야 할 작은 변경 사항이 하나 있습니다.
- 첫 번째 매개 변수 (의 제약
t::setNumber
과 호환되는Consumer<R>
) 것입니다 감소 에R <: Number
대신Number <: R
은을 위해처럼Supplier<R>
. 이것은 축소에 대한 링크 된 문서에 설명되어 있습니다.
독자가 위의 절차 중 하나를 통해 추가 지식으로 무장 한 연습을 통해 특정 호출이 컴파일되거나 컴파일되지 않는 이유를 정확하게 설명합니다.