我们都知道Long扩展了Number
。那么为什么不编译呢?
以及如何定义方法with
,使程序无需任何手工强制转换就可以编译?
import java.util.function.Function;
public class Builder<T> {
static public interface MyInterface {
Number getNumber();
Long getLong();
}
public <F extends Function<T, R>, R> Builder<T> with(F getter, R returnValue) {
return null;//TODO
}
public static void main(String[] args) {
// works:
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// works:
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// works:
new Builder<MyInterface>().<Function<MyInterface, Number>, Number> with(MyInterface::getNumber, 4L);
// works:
new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
// compilation error: Cannot infer ...
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
// compilation error: Cannot infer ...
new Builder<MyInterface>().with(MyInterface::getNumber, Long.valueOf(4));
// compiles but also involves typecast (and Casting Number to Long is not even safe):
new Builder<MyInterface>().with( myInterface->(Long) myInterface.getNumber(), 4L);
// compiles but also involves manual conversion:
new Builder<MyInterface>().with(myInterface -> myInterface.getNumber().longValue(), 4L);
// compiles (compiler you are kidding me?):
new Builder<MyInterface>().with(castToFunction(MyInterface::getNumber), 4L);
}
static <X, Y> Function<X, Y> castToFunction(Function<X, Y> f) {
return f;
}
}
- 无法推断
<F, R> with(F, R)
的类型参数- Builder.MyInterface类型的getNumber()类型为Number,这与描述符的返回类型不兼容:Long
有关用例,请参见:Why is lambda return type not checked at compile time
答案 0 :(得分:10)
此表达式:
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
可以改写为:
new Builder<MyInterface>().with(myInterface -> myInterface.getNumber(), 4L);
考虑方法签名:
public <F extends Function<T, R>, R> Builder<T> with(F getter, R returnValue)
R
将被推断为Long
F
将是Function<MyInterface, Long>
,然后传递一个将被推断为Function<MyInterface, Number>
的方法引用-这是关键-编译器应如何预测您实际上想从具有此类签名的函数中返回Long
? / strong>不会为您进行向下转换。
由于Number
是Long
的超类,而Number
不一定是Long
(这就是为什么它不会编译)的原因-您必须显式地将其强制转换为自己的:
new Builder<MyInterface>().with(myInterface -> (Long) myInterface.getNumber(), 4L);
使F
成为Function<MyIinterface, Long>
或像您一样在方法调用期间显式传递通用参数:
new Builder<MyInterface>().<Function<MyInterface, Number>, Number> with(MyInterface::getNumber, 4L);
并且知道R
将被视为Number
,并且代码将编译。
答案 1 :(得分:4)
错误的关键在于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);
除了这一点之外,这主要是一个设计决定,即哪个选项可以降低特定应用程序的代码复杂度,因此请选择最适合的选项。
不进行强制转换就无法执行此操作的原因在于from the Java Language Specification:
装箱转换将原始类型的表达式视为相应引用类型的表达式。具体来说,以下九种转化称为装箱转化:
- 从布尔类型到布尔类型
- 从字节类型到字节类型
- 从short类型到Short类型
- 从char类型到Character类型
- 从int类型转换为Integer类型
- 从long类型到Long类型
- 从float类型到Float类型
- 从double类型转换为Double
- 从null类型到null类型
您可以清楚地看到,没有从long到Number的隐式装箱转换,并且只有在编译器确定它需要Number而不是Long时,才可能发生从Long到Number的扩大转换。由于需要Number的方法引用和提供Long的4L之间存在冲突,因此编译器(由于某种原因?)无法做出Long is-a Number的逻辑飞跃并推论{{1} }是F
。
相反,我设法通过稍微编辑功能签名来解决该问题:
Function<MyInterface, Number>
此更改后,将发生以下情况:
public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
return null;//TODO
}
修改:
在花了更多时间之后,很难实现基于getter的类型安全。这是一个使用setter方法强制执行构建器的类型安全性的工作示例:
// 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);
提供了构造对象的类型安全功能,希望在将来的某个时候,我们能够从构建器中返回immutable data object(也许通过向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()
),因此您甚至不必担心会修改生成的对象。老实说,要获得类型安全的字段灵活的生成器需要大量的精力,这是绝对的耻辱,但是如果没有一些新功能,代码生成或令人讨厌的反射,这可能是不可能的。
答案 2 :(得分:1)
似乎编译器使用值4L来确定R为Long,而getNumber()返回一个Number,不一定是Long。
但是我不确定为什么值优先于方法...
答案 3 :(得分:0)
Java编译器通常不擅长推断多个/嵌套的泛型类型或通配符。通常,如果不使用辅助函数来捕获或推断某些类型,我就无法编译某些东西。
但是,您真的需要将Function
的确切类型捕获为F
吗?如果没有,也许下面的方法,如您所见,似乎也适用于Function
的子类型。
import java.util.function.Function;
import java.util.function.UnaryOperator;
public class Builder<T> {
public interface MyInterface {
Number getNumber();
Long getLong();
}
public <R> Builder<T> with(Function<T, R> getter, R returnValue) {
return null;
}
// example subclass of Function
private static UnaryOperator<String> stringFunc = (s) -> (s + ".");
public static void main(String[] args) {
// works
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
// works
new Builder<String>().with(stringFunc, "s");
}
}
答案 4 :(得分:0)
我认为最有趣的部分在于这两行之间的区别:
// works:
new Builder<MyInterface>().<Function<MyInterface, Number>, Number> with(MyInterface::getNumber, 4L);
// compilation error: Cannot infer ...
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
在第一种情况下,T
明确为Number
,因此4L
也是Number
,没问题。在第二种情况下,4L
是Long
,所以T
是Long
,因此您的函数不兼容,Java无法知道您的意思是{{1} }或Number
。
答案 5 :(得分:0)
具有以下签名:
public <R> Test<T> with(Function<T, ? super R> getter, R returnValue)
您的所有示例均会编译,但第三个示例除外,后者明确要求该方法具有两个类型变量。
您的版本不起作用的原因是因为Java的方法引用没有特定的类型。相反,它们具有给定上下文中所需的类型。在您的情况下,由于R
推断Long
为4L
,但是getter不能具有类型Function<MyInterface,Long>
,因为在Java中,泛型类型的参数是不变的。