使用隐式lambda表达式时出现不兼容类型错误

3

我正在使用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);

}

在第一个assert中,我得到了一个编译错误:
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>>
我的问题如下:

  1. 第一个assert中的lambda表达式的返回类型真的是<? extends Class<? extends Number>吗?
    我只是依赖于IDEA生成的局部变量。
  2. 如果是这样,那么为什么呢?getClass文档说:
    The actual result type is Class<? extends |X|> where |X| is the erasure of the static type of the expression on which getClass is called.
    如果我理解正确,它应该是Class<? extends Number>
  3. 我应该使用哪个工具来查看隐式lambda的类型?我能用JDK中的某个工具来检查它吗?
  4. 为什么最后一个使用方法引用的assert可以编译通过?如果它真的返回Class<?>,那么我理解了,但为什么返回类型对于这个和隐式lambda不同呢?

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

1个回答

2

在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第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);

有时候,这是最短的方式。

谢谢你的回答。 - ncsibra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接