使用隐式lambda

时间:2016-03-20 11:35:28

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

我正在使用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<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. 第一个断言中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. 为什么最后一个带有方法引用的断言编译?如果它确实返回Class<?>,那么我理解,但为什么返回类型与此不同,隐含的lambda?
  5. 相关AssertJ文件:
    AbstractIterableAssert
    AbstractIterableAssert.html#extracting
    AbstractIterableAssert.html#containsExactlyElementsOf

1 个答案:

答案 0 :(得分: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 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);

有时它是最短路的。