당신의 오류의 핵심은 유형의 일반 선언에 F: F extends Function<T, R>. 작동하지 않는 진술은 다음과 같습니다. new Builder<MyInterface>().with(MyInterface::getNumber, 4L);첫째, 새로운 Builder<MyInterface>. 따라서이 클래스의 선언은 다음을 의미합니다 T = MyInterface. 당신의 선언에 따라 with, F해야 Function<T, R>을 인 Function<MyInterface, R>이 상황에서. 따라서 매개 변수 getter는 매개 변수 MyInterface로 메소드 ( MyInterface::getNumber및 메소드 참조에 의해 만족 됨)를 가져와 MyInterface::getLong리턴 R해야합니다 with. 이는 함수에 대한 두 번째 매개 변수와 동일한 유형이어야합니다 . 이제 이것이 모든 경우에 해당되는지 확인하십시오.
// T = MyInterface, F = Function<MyInterface, Long>, R = Long
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L explicitly widened to Number
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L implicitly widened to Number
new Builder<MyInterface>().<Function<MyInterface, Number>, Number>with(MyInterface::getNumber, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L implicitly widened to Number
new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Long
// F = Function<T, not R> violates definition, therefore compilation error occurs
// Compiler cannot infer type of method reference and 4L at the same time,
// so it keeps the type of 4L as Long and attempts to infer a match for MyInterface::getNumber,
// only to find that the types don't match up
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
다음 옵션으로이 문제를 "수정"할 수 있습니다.
// stick to Long
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// stick to Number
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// explicitly convert the result of getNumber:
new Builder<MyInterface>().with(myInstance -> (Long) myInstance.getNumber(), 4L);
// explicitly convert the result of getLong:
new Builder<MyInterface>().with(myInterface -> (Number) myInterface.getLong(), (Number) 4L);
이 시점을 넘어 서면 대부분의 옵션이 특정 응용 프로그램의 코드 복잡성을 줄이는 옵션으로 결정되므로 가장 적합한 것을 선택하십시오.
캐스트하지 않고이 작업을 수행 할 수없는 이유 는 Java 언어 사양에서 다음과 같습니다 .
복싱 변환은 기본 유형의 표현을 해당 참조 유형의 표현으로 처리합니다. 특히 다음 9 가지 변환을 권투 변환 이라고합니다 .
- 부울 형식에서 부울 형식으로
- 바이트 유형에서 바이트 유형으로
- 짧은 유형에서 짧은 유형으로
- char 타입에서 Character 타입으로
- int 유형에서 Integer 유형으로
- 긴 유형에서 긴 유형으로
- float 유형에서 Float 유형으로
- 더블 타입에서 더블 타입으로
- 널 유형에서 널 유형으로
분명히 알 수 있듯이 long에서 Number 로의 암시 적 boxing 변환은 없으며 Long에서 Number 로의 확장 변환은 컴파일러가 Long이 아닌 Number가 필요하다고 확신하는 경우에만 발생할 수 있습니다. 긴-있는 번호와를 추론하는 논리적 도약 할 수없는 (어떤 이유로 ???) 숫자와 롱을 제공 4L, 컴파일러를 필요로하는 방법 참조 사이에 충돌이이기 때문에 FA는가 Function<MyInterface, Number>.
대신 함수 서명을 약간 편집하여 문제를 해결했습니다.
public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
return null;//TODO
}
이 변경 후 다음이 발생합니다.
// doesn't work, as it should not work
new Builder<MyInterface>().with(MyInterface::getLong, (Number), 4L);
// works, as it always did
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// works, as it should work
new Builder<MyInterface>().with(MyInterface::getNumber, (Number)4L);
// works, as you wanted
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
편집 :
그것에 더 많은 시간을 보낸 후 게터 기반 유형 안전을 시행하는 것은 성가신 일입니다. 다음은 setter 메소드를 사용하여 빌더의 유형 안전성을 적용하는 실제 예제입니다.
public class Builder<T> {
static public interface MyInterface {
//setters
void number(Number number);
void Long(Long Long);
void string(String string);
//getters
Number number();
Long Long();
String string();
}
// whatever object we're building, let's say it's just a MyInterface for now...
private T buildee = (T) new MyInterface() {
private String string;
private Long Long;
private Number number;
public void number(Number number)
{
this.number = number;
}
public void Long(Long Long)
{
this.Long = Long;
}
public void string(String string)
{
this.string = string;
}
public Number number()
{
return this.number;
}
public Long Long()
{
return this.Long;
}
public String string()
{
return this.string;
}
};
public <R> Builder<T> with(BiConsumer<T, R> setter, R val)
{
setter.accept(this.buildee, val); // take the buildee, and set the appropriate value
return this;
}
public static void main(String[] args) {
// works:
new Builder<MyInterface>().with(MyInterface::Long, 4L);
// works:
new Builder<MyInterface>().with(MyInterface::number, (Number) 4L);
// compile time error, as it shouldn't work
new Builder<MyInterface>().with(MyInterface::Long, (Number) 4L);
// works, as it always did
new Builder<MyInterface>().with(MyInterface::Long, 4L);
// works, as it should
new Builder<MyInterface>().with(MyInterface::number, (Number)4L);
// works, as you wanted
new Builder<MyInterface>().with(MyInterface::number, 4L);
// compile time error, as you wanted
new Builder<MyInterface>().with(MyInterface::number, "blah");
}
}
미래에 어느 시점에서 객체를 생성하는 형식 안전 기능을 제공하면 빌더에서 불변 데이터 객체 를 반환 할 수 있습니다 ( toRecord()인터페이스에 메소드를 추가 하고 빌더를로 지정 Builder<IntermediaryInterfaceType, RecordType>). 결과 객체가 수정되는 것에 대해 걱정할 필요조차 없습니다. 솔직히 말해서, 유형 안전 필드 유연한 빌더를 구현하는 데 많은 노력이 필요한 것은 절대적인 수치이지만 새로운 기능, 코드 생성 또는 성가신 성찰이 없다면 불가능할 것입니다.
MyInterface?