【问题标题】:Incompatible types error when using implicit lambda使用隐式 lambda 时出现不兼容类型错误
【发布时间】:2016-03-20 11:35:28
【问题描述】:

我使用的是 java 8,我有以下带有 JUnit 和 AssertJ 的测试代码:

@Test
public void test() {
    List<Number> actual = new ArrayList<>();
    actual.add(Double.valueOf(1));
    actual.add(Integer.valueOf(1));
    actual.add(Long.valueOf(1));

    List<Class<? extends Number>> expected = new ArrayList<>();
    expected.add(Double.class);
    expected.add(Integer.class);
    expected.add(Long.class);

    // Just information how IDEA is generating types for the different statements
    ListAssert<? extends Class<? extends Number>> implicitLambda = assertThat(actual).extracting(t -> t.getClass());
    ListAssert<Class<? extends Number>> explicitLambda = assertThat(actual).extracting((Extractor<Number, Class<? extends Number>>) t -> t.getClass());
    ListAssert<Class<?>> methodReference = assertThat(actual).extracting(Number::getClass);

    // Does not compile
    // Error:(31, 84) java: incompatible types: java.util.List<java.lang.Class<? extends java.lang.Number>> cannot be converted to java.lang.Iterable<? extends java.lang.Class<capture#1 of ? extends java.lang.Number>>
    assertThat(actual).extracting(t -> t.getClass()).containsExactlyElementsOf(expected);

    // Compile because of explicit types
    assertThat(actual).extracting((Extractor<Number, Class<? extends Number>>) t -> t.getClass()).containsExactlyElementsOf(expected);

    // Compile, but don't understand why
    assertThat(actual).extracting(Number::getClass).containsExactlyElementsOf(expected);

}

在第一次断言时出现编译错误:
Error:(31, 84) java: incompatible types: java.util.List&lt;java.lang.Class&lt;? extends java.lang.Number&gt;&gt; cannot be converted to java.lang.Iterable&lt;? extends java.lang.Class&lt;capture#1 of ? extends java.lang.Number&gt;&gt;
我有以下问题:

  1. 第一个断言中 lambda 的返回类型真的是&lt;? extends Class&lt;? extends Number&gt;
    我只是依赖IDEA生成的局部变量。
  2. 如果是,那为什么? getClass 文档说
    The actual result type is Class&lt;? extends |X|&gt; where |X| is the erasure of the static type of the expression on which getClass is called.
    如果我理解正确,应该是Class&lt;? extends Number&gt;
  3. 我应该使用哪个工具来查看隐式 lambda 的类型?我可以使用 JDK 中的一些工具检查它吗?
  4. 为什么最后一个带有方法引用的断言编译?如果它真的返回Class&lt;?&gt;,那么我明白了,但是为什么这个和隐式lambda的返回类型不同?

相关 AssertJ 文档:
AbstractIterableAssert
AbstractIterableAssert.html#extracting
AbstractIterableAssert.html#containsExactlyElementsOf

【问题讨论】:

    标签: java generics lambda java-8 type-inference


    【解决方案1】:

    在 Java-8 中,类型推断比以前的 Java 版本更棘手。特别是,现在表达式的类型可能取决于周围的上下文,而不仅仅是表达式本身。当你写

    ListAssert<? extends Class<? extends Number>> implicitLambda = assertThat(actual).extracting(t -> t.getClass());
    

    您提供了这样的上下文。您还可以将此表达式分配给不同的类型:

    ListAssert<Class<?>> implicitLambda2 = assertThat(actual).extracting(t -> t.getClass());
    

    这也有效。请注意,您不能将implicitLambda 分配给implicitLambda2。您也不能将implicitLambda2 分配给implicitLambda。这两种类型是不相关的。所以事实是:表达式本身可能没有特定的类型,它只是具有一组根据周围上下文解析的约束。通常,当您的表达式被分配、强制转换或传递给另一个方法时,就会发生约束解析。 JLS chapter 18 涵盖了这一点,尽管它很难阅读。

    在链式调用中,没有合适的环境上下文来明确地解决约束,因此可能会以与链中的下一个调用不兼容的方式解析类型(从不考虑链式调用来绑定约束)。您正确地找到了两种消除歧义的方法。一是使用方法参考。这很有帮助,因为映射方法引用到功能接口(由JLS 15.13.2 涵盖)与将 lambda 映射到功能接口(由JLS 15.27.3 涵盖)完全不同;在这里它有助于设置更具体的约束。另一个是明确指定 lambda 参数。 JLS 15.27.3 中的以下语句涵盖了它:

    如果 lambda 表达式是显式类型的,其形参类型与函数类型的形参类型相同。

    所以在这里执行更简单的类型解析过程。还有另一种方法:指定显式泛型参数:

    // compiles fine
    assertThat(actual).<Class<? extends Number>>extracting(t -> t.getClass()).containsExactlyElementsOf(expected);
    // also compiles
    assertThat(actual).<Class<?>>extracting(t -> t.getClass()).containsExactlyElementsOf(expected);
    

    有时它是最短的方式。

    【讨论】:

    • 感谢您的回答。
    猜你喜欢
    • 2018-03-05
    • 2022-01-26
    • 1970-01-01
    • 2015-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-18
    • 1970-01-01
    相关资源
    最近更新 更多