为什么是
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) {...}
这是对Why is lambda return type not checked at compile time的跟进。我发现使用方法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
这是一个非常有趣的问题。恐怕答案很复杂。
找出差异需要对Java的type inference specification进行相当深入的阅读,但基本上可以归结为:
with
,有一个(公认的含糊的)替代词,它满足[C0]的所有要求:R
Serializable
,引入附加类型参数withX
会强制编译器首先解析F
,而不考虑约束R
。 F extends Function<T,R>
解析为(更具体的)R
,这意味着String
的推理失败。最后一个要点是最重要的,也是最手工的。我想不出一种更好的简洁措辞方式,因此,如果您需要更多详细信息,建议您阅读下面的完整说明。
我要在这里四肢走动,说no
。我并不是说规范中存在错误,更多的是(在F
的情况下)语言设计者举起手来说“在某些情况下,类型推断变得太难了,所以我们只会失败“
withX
的行为似乎是您想要的,我仍认为这是当前规范的附带副作用,而不是积极的设计决策。这很重要,因为它告知了问题我应该在我的应用程序设计中依赖此行为吗?
我认为您不应该这样做,因为您不能保证该语言的未来版本将继续起作用这样。虽然确实是语言设计人员在更新其规范/设计/编译器时尽最大努力不破坏现有应用程序,但问题是您要依赖的行为是编译器当前的行为[[失败(即不是现有应用程序)。语言更新始终将未编译的代码变成编译的代码。例如,以下代码可能不是[[保证在Java 7中编译,而是将
在Java 8中编译:withX
您的用例没有什么不同。[我对使用static Runnable x = () -> System.out.println();
方法要谨慎的另一个原因是withX
参数本身。通常,存在method
上的通用类型参数(不会出现在返回类型中)以将签名的多个部分的类型绑定在一起。意思是:
我不在乎F
是什么,但要确保无论我在哪里使用T
都是同一类型。
逻辑上,我们希望每个类型参数在方法签名中至少出现两次,否则“什么也没做”。 T
中的F
在签名中仅出现一次,这向我建议使用类型参数,该参数不与该语言功能的intent
替代实现
一种以稍微更多的“预期行为”方式实现此目的的方法是将withX
方法分成2个链:with
然后可以按如下方式使用:
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) { // 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
)约束of
的类型要与您先前设置的兼容。使整件事无关紧要的最后注解:
我认为value
(尤其是其存根功能)可能已经基本完成了您要使用“类型安全的通用构建器”实现的目标。也许您可以改用它?
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
}
}
和applicability inference类型的过程:
type inference我们有:
with
初始绑定集
B是:0
with(TypeInference::getLong, "Not a long");
R <: Object
。C
是:applicability inference 与...兼容
TypeInference::getLong
Supplier<R>
与...兼容"Not a long"
R
绑定到的集合B 2
的其中:reduces(来自R <: Object
(来自第一个约束)Long <: R
(来自第二个约束)String <: R
的resolution成功(给出R
),所以该调用是适用的。所以,我们继续到 CSerializable
。新的约束集
input和output
变量是:invocation type inferenceTypeInference::getLong
Supplier<R>
output变量之间的相互依赖关系,因此可以在单个步骤中将其设为R
,而最后的边界集B 4是与B 2
withX
初始绑定集
B是:0
withX(TypeInference::getLong, "Also not a long");
R <: Object
F <: Supplier<R>
。第一个(pertinent to applicability)不是,因为它满足以下条件:如果TypeInference::getLong
是通用方法,并且方法调用不提供显式类型参数,显式类型的lambda表达式或精确方法引用表达式,则对应的目标类型(从m
的签名派生)是类型参数m
。
因此,为m
设置的初始约束C
是:applicability inference
的其中:reduces(来自与...兼容
"Also not a long"
此 R
绑定到的集合B2
B
)- 0
R <: Object
(来自B0 )F <: Supplier<R>
(来自约束)同样,由于它不包含绑定的'false',并且 String <: R
的resolution成功(给出R
),因此该调用是适用的。
String
再一次...这次,新的约束集C
与相关的[[input和output
变量是:Invocation type inference 与...兼容TypeInference::getLong
- 输入变量:
F
输出变量:无
同样,我们在 input和output变量之间没有相互依赖性。但是这一次,有一个 开始。我们如下确定子集输入变量
(F
),因此我们在尝试F
之前必须先将此resolve设置为reduction。因此,我们从绑定集B 2V
:给出一组要解析的推理变量,使
V
为该集合与该集合中至少一个变量的分辨率所依赖的所有变量的并集。
B
2的第二个边界,F
的分辨率取决于R
,因此V := {F, R}
。
[我们根据规则选择V
的子集:
{ α1, ..., αn }
是V
中未实例化变量的非空子集,这样i)对于所有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
,我们可以使用新的约束条件继续进行reduction:TypeInference::getLong
与...兼容
Supplier<String>
...简化为Long
String