当前位置: 首页 > 面试题库 >

使用泛型和Lambda重载方法时的方法调用不明确

谈旺
2023-03-14
问题内容

我注意到使用泛型和lambda重载方法的行为很奇怪。这个课程效果很好:

  public <T> void test(T t) { }

  public <T> void test(Supplier<T> t) { }

  public void test() {
    test("test");
    test(() -> "test");
  }

没有模棱两可的方法调用。但是,将其更改为此将使第二个调用不明确:

  public <T> void test(Class<T> c, T t) { }

  public <T> void test(Class<T> c, Supplier<T> t) { }

  public void test() {
    test(String.class, "test");
    test(String.class, () -> "test"); // this line does not compile
  }

怎么会这样?为什么添加另一个参数会导致方法解析不明确?为什么在第一个示例中却能分辨出Supplier和Object之间的区别,而在第二个示例中却不能呢?

编辑:这是使用1.8.0_121。这是完整的错误消息:

error: reference to test is ambiguous
    test(String.class, () -> "test");
    ^
  both method <T#1>test(Class<T#1>,T#1) in TestFoo and method <T#2>test(Class<T#2>,Supplier<T#2>) in TestFoo match
  where T#1,T#2 are type-variables:
    T#1 extends Object declared in method <T#1>test(Class<T#1>,T#1)
    T#2 extends Object declared in method <T#2>test(Class<T#2>,Supplier<T#2>)
/workspace/com/test/TestFoo.java:14: error: incompatible types: cannot infer type-variable(s) T
    test(String.class, () -> "test");
        ^
    (argument mismatch; String is not a functional interface)
  where T is a type-variable:
    T extends Object declared in method <T>test(Class<T>,T)

问题答案:

如果我对JSE for Java SE
8的第15和18章的理解是正确的,那么问题的关键在于第15.12.2段中的以下引文:

某些包含隐式类型的lambda表达式(§15.27.1)或不精确的方法引用(§15.13.1)的参数表达式将被适用性测试忽略,因为在选择目标类型之前,无法确定其含义。

当Java编译器遇到诸如之类的方法调用表达式时test(() -> "test"),它必须搜索可将此方法调用分派到的可访问(可见)和适用(即具有匹配签名)的方法。在第一个示例中,<T> void test(T)<T> void test(Supplier<T>)均可通过test(() -> "test")方法调用访问和适用。在这种情况下,当存在多个匹配方法时,编译器将尝试确定最具体的方法。现在,尽管对通用方法的确定(如 JLS
15.12.2.5
和JLS
18.5.4所述
)相当复杂,但我们可以使用15.12.2.5开头的直觉:

非正式的直觉是,如果第一种方法处理的任何调用都可以传递给另一个方法而没有编译时错误,则一个方法比另一种方法更具体。

因为对于任何有效的调用<T> void test(Supplier<T>),我们可以找到的类型参数的对应的实例T<T> void test(T),前者比后者更具体。

现在,令人惊讶的部分是,在您的第二个示例中,两者<T> void test(Class<T>, Supplier<T>)<T> void test(Class<T>, T)都被认为适用于方法调用test(String.class, () -> "test"),即使我们很清楚,后者也不应该。问题是,如上所述,在存在隐式类型的lambda的情况下,编译器的行为非常保守。特别参见JLS
18.5.1:

一组约束公式C的构造如下。

  • 要通过严格调用来测试适用性:

如果k≠n,或者存在i(1≤i≤n) 使得e_i与适用性有关 (§15.12.2.2)(…) 否则,对于所有i(1≤i≤k
),其中e_i与适用性有关,‹e_i→F_iθ›。

  • 要通过松散调用来测试适用性:

如果k≠n,则该方法不适用,并且无需进行推理。

否则,对于e_i与适用性有关的所有i(1≤i≤k),C包括‹e_i→F_iθ›。

和JLS
15.12.2.2:

除非参数表达式具有以下形式之一,否则认为它 可能适用的方法m的 适用性有关

  • 隐式类型的lambda表达式(第15.27.1节)。

因此,在方法适用性检查的上下文中,作为参数传递的隐式类型的lambda的约束不参与解决类型推断。

现在,如果我们假设这两种方法均适用,那么问题以及本例与前面的示例之间的区别是,这些方法都没有一个更具体。存在对无效<T> void test(Class<T>, Supplier<T>)但对无效的调用,<T> void test(Class<T>, T)反之亦然。

这也解释了为什么test(String.class, (Supplier<String>) () -> "test");编译,就像@Aominè在上面的评论中提到的那样。(Supplier<String>) () -> "test")是一个显式类型的lambda,因此被认为 与适用性有关 ,编译器能够正确推断出这些方法中只有一种适用,并且不会发生冲突。



 类似资料:
  • 这怎么可能呢?为什么添加另一个参数会导致方法解析不明确?为什么在第一个例子中它可以区分供应商和对象,而在第二个例子中却不能? 编辑:这使用的是1.8.0_121。这是完整的错误消息:

  • 我有一个Foo和Bar对象的列表,以及每个相应对象的转换器。 Convert-method需要有所不同,因为Bar1与Bar2和Bar3等有很大不同,但是我想创建一个方法来处理所有可能的列表。 是否可以创建一个泛型方法,根据列表的内容调用相应的非泛型方法? 到目前为止,我已经尝试过了: 但这并不能编译,因为"无法解析方法'Converts(T, S)'" 有什么想法吗?

  • 我一直在尝试泛型,很快我就遇到了一些我无法解释的事情 例如: 我不明白

  • 安吉丽卡·兰格(Angelica Langer)在关于仿制药的常见问题解答中说(参见Technicalities.FAQ822): 如果这些方法具有具有不同边界的类型参数,则它们不会重写,因为这些方法的签名不是重写等价的。请记住,类型参数边界是泛型方法签名的一部分。 示例(泛型子类型方法重载泛型超类型方法;不推荐): 我不明白为什么方法在类中重载。据我所知,这应该是一个编译时错误,因为在和中具有相

  • 我有以下两种方法: ClassA和ClassB都有相同的方法getObjValue,但它们没有关系。它们不从公共类或接口继承。由于这些方法几乎是相同的,我是否可以使用泛型来使用一个签名如下的方法来消除这段代码的重复:

  • 它不编译。它在lambda表达式中显示错误: “目标方法是泛型的” 好的,当我使用编译它时,它显示了以下错误: