ncsibra
ncsibra

Reputation: 351

Incompatible types error when using implicit lambda

I'm using java 8 and I have the following test code with JUnit and 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);

}

At the first assert I got a compile error:
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>>
I have the following questions:

  1. Is the return type of the lambda in the first assert really <? extends Class<? extends Number>.
    I just rely on the local variables generated by IDEA.
  2. If it is, then why is that? The getClass documentation says that
    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.
    If I understand correctly it should be Class<? extends Number>.
  3. Which tool should I use to see the types of the implicit lambda? Can I check it with some tool from the JDK?
  4. Why is the last assert with method reference compiles? If it really return Class<?>, then I understand, but why the return type different for this and the implicit lambda?

Relevant AssertJ documentations:
AbstractIterableAssert
AbstractIterableAssert.html#extracting
AbstractIterableAssert.html#containsExactlyElementsOf

Upvotes: 3

Views: 484

Answers (1)

Tagir Valeev
Tagir Valeev

Reputation: 100189

In Java-8 type inference is more tricky than in previous Java versions. In particular, now the type of expression may depend on the surrounding context, not just on the expression itself. When you write

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

You provide such context. You can also assign this expression to the different type:

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

This also works. Note that you cannot assign implicitLambda to implicitLambda2. Neither can you assign implicitLambda2 to implicitLambda. These two types are unrelated. So the fact is: expression by itself may have no specific type, it just has set of constraints which are resolved according to the surrounding context. Usually constraints resolution occurs when your expression is assigned, cast or passed to another method. This is covered by JLS chapter 18, though it's pretty hard to read.

In chaining call there's no suitable surrounding context to resolve the constraints unambiguously, so the may resolve type in a way which is incompatible with the next call in the chain (chaining calls are never taken into account to bind the constraints). You're correctly found two ways to disambiguate this. One is using method reference. This helps because mapping method reference to the functional interface (covered by JLS 15.13.2) is completely different procedure from mapping lambda to the functional interface (covered by JLS 15.27.3); here it helps to set more specific constraint. Another is specifying explicitly lambda arguments. It's covered by the following statement in JLS 15.27.3:

If the lambda expression is explicitly typed, its formal parameter types are the same as the parameter types of the function type.

So simpler type resolution process is performed here. There's one more approach: to specify explicit generic parameter:

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

Sometimes it's shortest way.

Upvotes: 2

Related Questions