Javac查看行并反复进行解析,填充空白处,就像做拼图一样.具体地说,assertThat
行需要一些类型变量绑定;方法调用涉及一个类型变量<T>
.我将把它称为<A>
,以将它与is
方法所具有的完全独立的类型变量区分开来.
需要计算出<A>
个.没有明确的 Select (您拨打了MatcherAssert.assertThat
;您没有拨打MatcherAssert.<String>assertThat
).因此,是时候进行推断了.
让我们来看看有什么可用的提示.第一个参数是T类型,传递的参数是JsonNode
.
你会想--哦,太好了,那么,T就是JsonNode.还没那么快.如果我有一个签名为void foo(Number n)
的方法,我可以向它传递一个整数.说表达式的类型绑定了typevar,这意味着T从一开始就不可能是接口类型,任何子类型都可能 destruct 东西,甚至是无关紧要的子类型,如new ArrayList<String>() {}
(请注意那里的花括号)或任何lambda.
不,它将T绑定到范围[Object ... JsonNode]
.就像? super JsonNode
.如果T最后是Object
,那很好.当参数需要Object
时,类型JsonNode
的表达式是有效参数.
如果类型推断在这里结束,则javac将采用它找到的范围中最具体的类型,并将would绑定到JsonNode.我们可以在这个不同的例子中看到这一点:
public static <T> T ident(T in) { return in; }
var x = ident(new JsonNode());
x.someMethodOnlyJsonNodeHas();
上面的代码编译得很好,运行正常.var x
将把var‘绑定’为表达式的类型,而类型为JsonNode
.由于上面概述的过程:虽然推断T,但通过new JsonNode()
,T被约束为[Object .. JsonNode]
,并且没有进一步的约束,因此,javac Select 它能 Select 的最具体的东西.
但是,assertThat
有更多涉及<A>
的参数.我们必须继续寻找.我们还有第二个参数,它的类型需要是Matcher<? super A>
.我们发现Matchers.is(m)
这个表达式有点古怪--我们试图弄清楚<A>
是什么,在做这件事的过程中,我们现在必须弄清楚<B>
是什么(<B>
是我们将称为public static <T> Matcher<T> is (T value);
中的<T>
的东西,因为这些类型变量完全独立,这两个使用T的事实纯粹是巧合,与推理没有任何关系.其中之一的重命名应该无关紧要,实际上也不重要).为了弄清楚<B>
是什么,javac将..从上下文中推断.
因此,当我们在推断<A>
的过程中由外而内时,我们需要由内而外推断出<B>
.希望这有助于强调为什么推理有时会产生一些奇怪的结果,以及为什么您可能只想用显式的Matcher<String> matcher = Matchers.is(m)
重写这段代码,以避免这种非常棘手的分析.
关键是,Java不能做到这一点,它不能同时走两条路,也不能违反因果关系的基本原理.
因此,相反,Matchers.is(m)
是独立解决的,而不是它周围的背景.m
是一个字符串,is()
的签名很简单.<B>
被约束为[Object .. String]
.
现在我们可以回到解决<A>
的问题上了.
我们有一个参数将<A>
约束为[Object .. JsonNode]
,另一个参数将<A>
约束为[Object .. String]
.
然后...我们完事了!没有进一步的限制,也不需要计算重载,所以,我们现在可以合并我们的限制.
因此,javac一如既往地执行它的操作, Select 符合所有限制的最具体类型.也就是Object
.对象在这里工作得很好:
Matcher<Object> matcher = Matchers.is("Hello");
编译得很好.试试吧,为什么不好呢所以,这对T = Object
人有效.j
是JsonNode
的类型,也可以用:
Object o = new JsonNode():
编译和工作正常.试试看.那怎么会不好呢?
因此,一切都很好.编译器编译并运行它,而不会给你任何错误或警告.
从语义上讲,这也是有意义的.出于同样的原因,java.util.List
的contains
方法接受任何Object
而不是E
--"这份橙子列表中包含这个苹果吗?"是一个有意义的问题,并有一个合理的、尽管相当明显的答案:不,不是.
与此形成对比的是:"这里有一个苹果.把它加到这个橙子 list 上",这是没有意义的.同样的事情也在这里发生:请判断这个JsonNode是否传递了这个字符串匹配器.如果未通过测试,则无法通过测试.
这也是一个语义上合理的问题,有一个显而易见的答案:它永远不会.答案是‘不,它们不相等’,我们甚至在运行代码之前就知道这一点.但是编译器没有意识到这就是assertThat
方法的用处,而且任何一行代码如果断言guaranteed失败,最好是一个编译器警告.除了将javac变成某种无休止的可插拔的编辑工具包(这将是非常棒的!但事实并非如此,而且我所知道的编程语言也不是),所以对于javac来说,没有办法知道assertThat
带有这样的包袱:"如果通过它的所有路径都必然导致失败的结果,那么这个调用是无用的",甚至也不知道Matcher<String>
个对象附带的包袱是,当被要求匹配类型JsonNode
的对象时,它们是guaranteed失败的.如果不运行这段代码,Javac就无法知道这一点,而且您真的不希望编译器在编译代码的过程中运行代码.对于初学者来说,需要考虑的是停顿问题.