Reputation: 351
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:
<? extends Class<? extends Number>
.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>
.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
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