你错误的关键在于类型
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
的第二个参数的相同类型。现在,让我们看看这是否适用于你所有的情况:
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
new Builder<MyInterface>().<Function<MyInterface, Number>, Number>with(MyInterface::getNumber, 4L);
new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
您可以通过以下选项“修复”此问题:
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
new Builder<MyInterface>().with(myInstance -> (Long) myInstance.getNumber(), 4L);
new Builder<MyInterface>().with(myInterface -> (Number) myInterface.getLong(), (Number) 4L);
在这个点之后,对于哪个选项能减少你特定应用程序的代码复杂性,这主要是一个设计决策,所以选择最适合你的。
你不能不进行转换就完成这个操作的原因在于,来自Java语言规范:
装箱转换将原始类型的表达式视为相应引用类型的表达式。具体而言,以下九个转换称为 装箱转换:
- 从 boolean 类型到 Boolean 类型
- 从 byte 类型到 Byte 类型
- 从 short 类型到 Short 类型
- 从 char 类型到 Character 类型
- 从 int 类型到 Integer 类型
- 从 long 类型到 Long 类型
- 从 float 类型到 Float 类型
- 从 double 类型到 Double 类型
- 从 null 类型到 null 类型
正如你所看到的,从长整型到数字类型之间没有隐式的装箱转换,而从Long到Number的扩展转换只能在编译器确定需要一个Number而不是Long时发生。由于需要一个Number和提供一个Long之间存在方法引用的冲突,编译器(因为某种原因???)无法进行逻辑推断,认为Long是一个Number,从而推断出
F
是一个
Function<MyInterface,Number>
。
相反,我通过轻微编辑函数签名来解决了这个问题:
public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
return null;//TODO
}
在这个更改之后,发生以下情况:
new Builder<MyInterface>().with(MyInterface::getLong, (Number), 4L);
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
new Builder<MyInterface>().with(MyInterface::getNumber, (Number)4L);
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
编辑:
花了更多时间后,发现通过getter实现类型安全非常困难。这里提供一个使用setter方法实现构建器类型安全的可行示例:
public class Builder<T> {
static public interface MyInterface {
void number(Number number);
void Long(Long Long);
void string(String string);
Number number();
Long Long();
String string();
}
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);
return this;
}
public static void main(String[] args) {
new Builder<MyInterface>().with(MyInterface::Long, 4L);
new Builder<MyInterface>().with(MyInterface::number, (Number) 4L);
new Builder<MyInterface>().with(MyInterface::Long, (Number) 4L);
new Builder<MyInterface>().with(MyInterface::Long, 4L);
new Builder<MyInterface>().with(MyInterface::number, (Number)4L);
new Builder<MyInterface>().with(MyInterface::number, 4L);
new Builder<MyInterface>().with(MyInterface::number, "blah");
}
}
提供了类型安全构建对象的能力,希望在未来的某个时候我们能够从构建器中返回一个不可变数据对象(可能通过向接口添加toRecord()
方法,并将构建器指定为Builder<IntermediaryInterfaceType, RecordType>
来实现),这样你甚至都不必担心结果对象被修改。说实话,获得类型安全的字段灵活构建器需要付出如此之多的努力,真是太可惜了,但可能无法实现没有一些新功能、代码生成或大量反射的情况下。
MyInterface
吗? - Maurice Perry<F extends Function<T, R>, R, S extends R> Builder<T> with(F getter, S returnValue)
但是出现了java.lang.Number cannot be converted to java.lang.Long
的错误,这让我很惊讶,因为我不知道编译器从哪里得到的信息需要将getter
返回值转换为returnValue
。 - jingxNumber getNumber()
更改为<A extends Number> A getNumber()
可以使事情正常运作。我不知道这是否是您想要的。正如其他人所说,问题在于MyInterface :: getNumber
可能是返回例如Double
而不是Long
的函数。您的声明不允许编译器根据其他信息缩小返回类型。通过使用通用返回类型,您允许编译器这样做,因此它可以工作。 - Giacomo Alzetta