Hamcrest Matchers contains with List of matchers - java

I am trying to use org.hamcrest.Matchers.contains(java.util.List<Matcher<? super E>>), but the compiler tells me that it cannot resolve the method.
I even tried the example given by Hamcrest here, but I get the same compilation error:
assertThat(Arrays.asList("foo", "bar"), contains(Arrays.asList(equalTo("foo"), equalTo("bar"))));
Error:(13, 9) java: no suitable method found for assertThat(java.util.List<java.lang.String>,org.hamcrest.Matcher<java.lang.Iterable<? extends java.util.List<org.hamcrest.Matcher<java.lang.String>>>>)
method org.hamcrest.MatcherAssert.<T>assertThat(T,org.hamcrest.Matcher<? super T>) is not applicable
(actual argument org.hamcrest.Matcher<java.lang.Iterable<? extends java.util.List<org.hamcrest.Matcher<java.lang.String>>>> cannot be converted to org.hamcrest.Matcher<? super java.util.List<java.lang.String>> by method invocation conversion)
method org.hamcrest.MatcherAssert.<T>assertThat(java.lang.String,T,org.hamcrest.Matcher<? super T>) is not applicable
(cannot instantiate from arguments because actual and formal argument lists differ in length)
method org.hamcrest.MatcherAssert.assertThat(java.lang.String,boolean) is not applicable
(actual argument java.util.List<java.lang.String> cannot be converted to java.lang.String by method invocation conversion)
I tried to cast the second argument to Matcher<? super List<String>>
assertThat(Arrays.asList("foo", "bar"), (Matcher<? super List<String>>)contains(Arrays.asList(equalTo("foo"), equalTo("bar"))));
but then I get another compilation error:
Error:(16, 88) java: inconvertible types
required: org.hamcrest.Matcher<? super java.util.List<java.lang.String>>
found: org.hamcrest.Matcher<java.lang.Iterable<? extends java.util.List<org.hamcrest.Matcher<java.lang.String>>>>
Is there a way to properly use this method?

The problem is that Arrays.asList(equalTo("foo"), equalTo("bar")); will give you the type List<Matcher<String>>, but you really want List<Matcher<? super String>>. You have to explicitly specify the type:
assertThat(str,
contains(Arrays.<Matcher<? super String>>asList(
equalTo("foo"),
equalTo("bar"))));

Related

reflexion on param implementing interface not accepted as Class<T> param

In java, I try to pass a class param to a mapper using reflexion on the generic dto I receive as a param.
Despite all my tries, I can't seem to get it to work, even though I think my reasoning is correct and shouldn't raise incompatible types error in java.
Here is a snippet with a minimal reproducible example : https://code.sololearn.com/cTysx65IHL2Q/#java
I have an interface :
interface IDto18Now<TypeDtoParam> {
TypeDtoParam getAttribute();
void setAttribute(TypeDtoParam param);
}
and 2 methods where my param implements this interface :
private <T> T someMapperMethod(String paramToParse, Class<T> valueType){
// some code which definitively works, as it's a mapper
}
private <SpecificDto extends IDto18Now<?>> void callerMethod(SpecificDto paramDto) {
// some logic to take the string, it's actually a param but it's not important here
String unparsedString = "some json or whatever";
paramDto.setAttribute(someMapperMethod(unparsedString, paramDto.getClass().getMethod("getAttribute").getReturnType()));
}
and as you can see on snippet, I have an error where my types aren't compatible on the line where I call the setAttribute, where the truth is, for me, that they are, as the type returned with reflexion is, per the interface contract, the same that the one expected as param in the setAttribute method.
Currently I got this to work by passing an additional class param, but given my models are quite big and tend to chain lots of child dtos, I'd rather avoid passing the class param, especially as it should be guessable from the type of the param passed.
Can anyone 1, explain why this reflexion doesn't work, 2, what would be the best alternative solution for "deducing" like here the class to avoid passing it as an adiitional param.
Thank you for your time.
The issue is that the getMethod method has return type Class<?> which is not compatible with the Class<T> parameter of someMapperMethod.
I think the simplest way to fix this is to introduce a <T> parameter to callerMethod and then cast the Class<?> to Class<T>.
private <T> void callerMethod(IDto18Now<? super T> paramDto) throws NoSuchMethodException {
// some logic to take the string, it's actually a param but it's not important here
String unparsedString = "some json or whatever";
paramDto.setAttribute(someMapperMethod(unparsedString, (Class<T>)paramDto.getClass().getMethod("getAttribute").getReturnType()));
}
Note that I removed the SpecificDto type parameter and replaced it with a generic <T> parameter. I then cast the return of getReturnType to Class<T>. This should be type compatible with any existing callerMethod call sites but there could be corner cases I'm not thinking of.
EDIT: Understanding the error message
The error you were getting is
./Playground/Playground.java:13: error: incompatible types: inference variable T has incompatible bounds
paramDto.setAttribute(someMapperMethod(unparsedString, paramDto.getClass().getMethod("getAttribute").getReturnType()));
^
equality constraints: CAP#1
lower bounds: CAP#2,Object
where T is a type-variable:
T extends Object declared in method <T>someMapperMethod(String,Class<T>)
where CAP#1,CAP#2 are fresh type-variables:
CAP#1 extends Object from capture of ?
CAP#2 extends Object from capture of ?
This is a pretty unhelpful error but it gives you some useful information. First, it tells you that the T parameter on someMapperMethod is getting incompatible type requirements from different places. inference variable T has incompatible bounds
Next, it tells you what the incompatible types (really bounds) are:
equality constraints: CAP#1
lower bounds: CAP#2,Object
So T must be equal to CAP#1 and a super type of CAP#2. But where are these constraints coming from? The T variable is in the return type and the Class<T> parameter, so these are the two directions that may be incompatible.
The return type is the input type to setAttribute so that will impose bounds, and the input to the Class<T> parameter is from getReturnType so that is where the other bound comes from.
Finally, the error says
CAP#1,CAP#2 are fresh type-variables:
CAP#1 extends Object from capture of ?
CAP#2 extends Object from capture of ?
Because we know, from earlier in the error, that T has an equality constraint on CAP#1 this tells us that the problem is that the compiler cannot prove that CAP#1 is a super type of CAP#2. Because both types involved are wildcards this makes sense. Those types can be anything, so it's impossible to prove any relationship between them.

jooq 3.12.0 custom data type binding generated code compile errors

I used jooq in 3.7.3 and now I am upgrading to jooq 3.12.0. I am seeing compile errors in my generated code.
With jooq version 3.7.3, using https://www.jooq.org/doc/3.7/manual/code-generation/custom-data-type-bindings/. I can see my custom data binding class being used ("MySqlJsonBinding") in generated code and I do not get compile errors with the generated code.
Part of my jooq.xml:
<customTypes>
<customType>
<name>JsonElement</name>
<type>com.google.gson.JsonElement</type>
<binding>jooq.MySqlJsonBinding</binding>
</customType>
</customTypes>
<forcedTypes>
<forcedType>
<name>JsonElement</name>
<expression>board_data</expression>
<types>JSON</types>
</forcedType>
</forcedTypes>
Generated code: no compile errors
public final TableField<UserBoardRecord, JsonElement> BOARD_DATA = createField("board_data", org.jooq.impl.DefaultDataType.getDefaultDataType("json"), this, "", new MySqlJsonBinding());
With jooq version 3.12.0, using https://www.jooq.org/doc/latest/manual/code-generation/custom-data-type-bindings/ as my example. I am not seeing "MySqlJsonBinding" in generated code, so I am not sure it is being included.
Part of my jooq.xml:
<forcedTypes>
<forcedType>
<userType>com.google.gson.JsonElement</userType>
<binding>com.samplecompany.jooq.MySqlJsonBinding</binding>
<includeExpression>.*JSON.*</includeExpression>
<includeTypes>.*</includeTypes>
</forcedType>
</forcedTypes>
Generated code: no compile errors, but no MySqlJsonBinding:
public final TableField<UserBoardRecord, JSON> BOARD_DATA = createField(DSL.name("board_data"), org.jooq.impl.SQLDataType.JSON, this, "");
Lastly, I am using 3.12.0, trying to apply the approach I used for 3.7.3. I do get generated code, and it does contain MySqlJsonBinding, but it has compile errors.
Part of my jooq.xml:
<forcedTypes>
<forcedType>
<userType>com.google.gson.JsonElement</userType>
<binding>com.samplecompany.jooq.MySqlJsonBinding</binding>
<includeExpression>board_data</includeExpression>
<includeTypes>JSON</includeTypes>
</forcedType>
</forcedTypes>
Generated code:
public final TableField<UserBoardRecord, JsonElement> BOARD_DATA = createField(DSL.name("board_data"), org.jooq.impl.SQLDataType.JSON, this, "", new MySqlJsonBinding());
Compile errors below:
ERROR:
Error:(81, 72) java: no suitable method found for createField(org.jooq.Name,org.jooq.DataType<org.jooq.JSON>,com.samplecompany.domain.data.tables.UserBoard,java.lang.String,com.samplecompany.jooq.MySqlJsonBinding)
method org.jooq.impl.AbstractTable.<R,T>createField(java.lang.String,org.jooq.DataType<T>,org.jooq.Table<R>) is not applicable
(cannot infer type-variable(s) R,T
(actual and formal argument lists differ in length))
method org.jooq.impl.AbstractTable.<R,T>createField(java.lang.String,org.jooq.DataType<T>,org.jooq.Table<R>,java.lang.String) is not applicable
(cannot infer type-variable(s) R,T
(actual and formal argument lists differ in length))
method org.jooq.impl.AbstractTable.<R,T,U>createField(java.lang.String,org.jooq.DataType<T>,org.jooq.Table<R>,java.lang.String,org.jooq.Converter<T,U>) is not applicable
(cannot infer type-variable(s) R,T,U
(argument mismatch; org.jooq.Name cannot be converted to java.lang.String))
method org.jooq.impl.AbstractTable.<R,T,U>createField(java.lang.String,org.jooq.DataType<T>,org.jooq.Table<R>,java.lang.String,org.jooq.Binding<T,U>) is not applicable
(cannot infer type-variable(s) R,T,U
(argument mismatch; org.jooq.Name cannot be converted to java.lang.String))
method org.jooq.impl.AbstractTable.<R,T,X,U>createField(java.lang.String,org.jooq.DataType<T>,org.jooq.Table<R>,java.lang.String,org.jooq.Converter<X,U>,org.jooq.Binding<T,X>) is not applicable
(cannot infer type-variable(s) R,T,X,U
(actual and formal argument lists differ in length))
method org.jooq.impl.AbstractTable.<T>createField(java.lang.String,org.jooq.DataType<T>) is not applicable
(cannot infer type-variable(s) T
(actual and formal argument lists differ in length))
method org.jooq.impl.AbstractTable.<T>createField(java.lang.String,org.jooq.DataType<T>,java.lang.String) is not applicable
(cannot infer type-variable(s) T
(actual and formal argument lists differ in length))
method org.jooq.impl.AbstractTable.<T,U>createField(java.lang.String,org.jooq.DataType<T>,java.lang.String,org.jooq.Converter<T,U>) is not applicable
(cannot infer type-variable(s) T,U
(actual and formal argument lists differ in length))
method org.jooq.impl.AbstractTable.<T,U>createField(java.lang.String,org.jooq.DataType<T>,java.lang.String,org.jooq.Binding<T,U>) is not applicable
(cannot infer type-variable(s) T,U
(actual and formal argument lists differ in length))
method org.jooq.impl.AbstractTable.<T,X,U>createField(java.lang.String,org.jooq.DataType<T>,java.lang.String,org.jooq.Converter<X,U>,org.jooq.Binding<T,X>) is not applicable
(cannot infer type-variable(s) T,X,U
(argument mismatch; org.jooq.Name cannot be converted to java.lang.String))
method org.jooq.impl.AbstractTable.<R,T>createField(org.jooq.Name,org.jooq.DataType<T>,org.jooq.Table<R>) is not applicable
(cannot infer type-variable(s) R,T
(actual and formal argument lists differ in length))
method org.jooq.impl.AbstractTable.<R,T>createField(org.jooq.Name,org.jooq.DataType<T>,org.jooq.Table<R>,java.lang.String) is not applicable
(cannot infer type-variable(s) R,T
(actual and formal argument lists differ in length))
method org.jooq.impl.AbstractTable.<R,T,U>createField(org.jooq.Name,org.jooq.DataType<T>,org.jooq.Table<R>,java.lang.String,org.jooq.Converter<T,U>) is not applicable
(cannot infer type-variable(s) R,T,U
(argument mismatch; com.samplecompany.jooq.MySqlJsonBinding cannot be converted to org.jooq.Converter<T,U>))
method org.jooq.impl.AbstractTable.<R,T,U>createField(org.jooq.Name,org.jooq.DataType<T>,org.jooq.Table<R>,java.lang.String,org.jooq.Binding<T,U>) is not applicable
(inferred type does not conform to equality constraint(s)
inferred: java.lang.Object
equality constraints(s): java.lang.Object,org.jooq.JSON)
method org.jooq.impl.AbstractTable.<R,T,X,U>createField(org.jooq.Name,org.jooq.DataType<T>,org.jooq.Table<R>,java.lang.String,org.jooq.Converter<X,U>,org.jooq.Binding<T,X>) is not applicable
(cannot infer type-variable(s) R,T,X,U
(actual and formal argument lists differ in length))
method org.jooq.impl.AbstractTable.<T>createField(org.jooq.Name,org.jooq.DataType<T>) is not applicable
(cannot infer type-variable(s) T
(actual and formal argument lists differ in length))
method org.jooq.impl.AbstractTable.<T>createField(org.jooq.Name,org.jooq.DataType<T>,java.lang.String) is not applicable
(cannot infer type-variable(s) T
(actual and formal argument lists differ in length))
method org.jooq.impl.AbstractTable.<T,U>createField(org.jooq.Name,org.jooq.DataType<T>,java.lang.String,org.jooq.Converter<T,U>) is not applicable
(cannot infer type-variable(s) T,U
(actual and formal argument lists differ in length))
method org.jooq.impl.AbstractTable.<T,U>createField(org.jooq.Name,org.jooq.DataType<T>,java.lang.String,org.jooq.Binding<T,U>) is not applicable
(cannot infer type-variable(s) T,U
(actual and formal argument lists differ in length))
method org.jooq.impl.AbstractTable.<T,X,U>createField(org.jooq.Name,org.jooq.DataType<T>,java.lang.String,org.jooq.Converter<X,U>,org.jooq.Binding<T,X>) is not applicable
(cannot infer type-variable(s) T,X,U
(argument mismatch; com.samplecompany.domain.data.tables.UserBoard cannot be converted to java.lang.String))
Any ideas what I could be doing wrong? Thanks.
jOOQ 3.12 introduced the new org.jooq.JSON type, which applies automatically to your generated code. Before, the type could not be mapped, and was thus generated as SQLDataType.OTHER, which corresponds to java.lang.Object
Your binding was working before, because it was probably a Binding<Object, YourType>. You will have to change this binding to Binding<JSON, YourType> to fix this.

Call generic method with class determined at runtime

I'm trying to automatically bind factory classes with a certain annotation using jersey 2/HK2. Therefore, I get the provided type at runtime from a generic interface and then try to bind the factory to this type. The method that binds the factory to a class looks like this:
protected void bindResourceFactory(Class<? extends Factory<?>> factory) {
Class<?> providedClass = getProvidedClass(factory);
bindFactory(factory).to(providedClass).in(Singleton.class);
}
The bindFactoy method provided by HK2 is defined as following:
public <T> ServiceBindingBuilder<T> bindFactory(Class<? extends Factory<T>> factoryType) {
return resetBuilder(AbstractBindingBuilder.<T>createFactoryBinder(factoryType, null));
}
This seems to work well when I build everything with eclipse. However when I build the project with maven, I get the following build error:
[ERROR] /Users/jan/Documents/Workspace/jersey-test/bind/ResourceFactoryBinder.java:[32,5] no suitable method found for bindFactory(java.lang.Class<capture#1 of ? extends org.glassfish.hk2.api.Factory<?>>)
[ERROR] method org.glassfish.hk2.utilities.binding.AbstractBinder.<T>bindFactory(java.lang.Class<? extends org.glassfish.hk2.api.Factory<T>>,java.lang.Class<? extends java.lang.annotation.Annotation>) is not applicable
[ERROR] (cannot infer type-variable(s) T
[ERROR] (actual and formal argument lists differ in length))
[ERROR] method org.glassfish.hk2.utilities.binding.AbstractBinder.<T>bindFactory(java.lang.Class<? extends org.glassfish.hk2.api.Factory<T>>) is not applicable
[ERROR] (cannot infer type-variable(s) T
[ERROR] (argument mismatch; java.lang.Class<capture#1 of ? extends org.glassfish.hk2.api.Factory<?>> cannot be converted to java.lang.Class<? extends org.glassfish.hk2.api.Factory<T>>))
[ERROR] method org.glassfish.hk2.utilities.binding.AbstractBinder.<T>bindFactory(org.glassfish.hk2.api.Factory<T>) is not applicable
[ERROR] (cannot infer type-variable(s) T
[ERROR] (argument mismatch; java.lang.Class<capture#1 of ? extends org.glassfish.hk2.api.Factory<?>> cannot be converted to org.glassfish.hk2.api.Factory<T>))
The java version in both cases is 1.8.0_152.
The reason probably is that my argument used is of type Class<? extends Factory<?>> whereas bindFactory expects Class<? extends Factory<T>>. Does someone know, why this might build with eclipse but not with maven? And is there any way to make this work apart from calling bindFactory via reflection?
This happens because eclipse uses it's own compiler named ECJ, and maven uses the javac compiler. Sometimes code that compiles in ECJ does not compile in javac and vice versa.
In this particular case the eclipse compiler is able to infer the generic type T but javac isn't. So you need to explicity tell the type T, which is unknown because the received type is Class<? extends Factory<?>>, this means you should use Object like the following.
this.<Object>bindFactory((Class<? extends Factory<Object>>) factory);
In this case factory needs to be casted, and this.<Object> can be ommited because the compiler already infers Object.
Finally you could suppress the cast warning, and it's better to 'uncheck' as little code as possible.
#SuppressWarnings({ "unchecked" })
Class<? extends Factory<Object>> objFactory = (Class<? extends Factory<Object>>) factory;
bindFactory(objFactory).to(providedClass).in(Singleton.class);
One important thing to consider is that the method getProvidedClass(...) should return the class correctly
Also the use of generics in the method like this <T> void bindResourceFactory(Class<? extends Factory<T>>) would take you to the same place again, because you wouldn't be able to call the method with a class extending Factory<?> with a wildcard (Class<? extends Factory<?>>).
The reason this error happens is because the compiler doesn't capture convert the "inner" wildcard in Class<? extends Factory<?>> (the one in Factory<?>). (In terms of the specification, "capture conversion is not applied recursively".)
It's easier to explain why this should happen with a different (but analogous with respect to the kind of types involved) example. Suppose we have a List of any type of List:
List<List<?>> lists = ...;
Now suppose we have some method that processes lists of lists, but assumes that the lists all have the same type:
<T> void process(List<List<T>> lists) {
// and at this point we should note that List<T>
// allows us to add elements to the lists, so we
// could do something like this:
if (!lists.isEmpty()) {
List<T> list0 = lists.get(0);
for (int i = 1; i < lists.size(); ++i)
list0.addAll(lists.get(i));
}
}
So the question is: should we be able to pass our List<List<?>> to the process method? Well, it could be that we've built our list of lists in something like the following way:
List<Double> doubles = new ArrayList<>();
Collections.addAll(doubles, 0.0, 1.0, 2.0);
List<String> strings = new ArrayList<>();
Collections.addAll(strings, "X", "Y", "Z");
List<List<?>> lists = new ArrayList<>();
Collections.addAll(lists, strings, doubles);
In that case it's more obvious that we shouldn't be able to pass the List<List<?>> to the process method taking a List<List<T>>. The way this is actually accomplished by the compiler is that it won't capture the "inner" wildcard to some type variable T.
The code in the question doesn't compile for a pretty similar reason. Since the type parameter on Class is mainly relevant to methods related to constructors (and in particular the newInstance method), we could show an example that's more similar using Supplier:
static void example(Supplier<? extends Factory<?>> s) {
capture(s);
}
static <T> void capture(Supplier<? extends Factory<T>> s) {
Factory<T> a = s.get();
Factory<T> b = s.get();
// remember, a and b are supposed to have the same type
T obj = a.provide();
b.dispose(obj);
}
The problem is that since our supplier could originally be a Supplier<Factory<?>>, there's no reason it couldn't, say, return a Factory<String> from one invocation and a Factory<Double> from another. We therefore shouldn't be able to capture Supplier<Factory<?>> to Supplier<Factory<T>>. Class.newInstance will always return objects of the exact same type, but the compiler doesn't know that.
I think that Eclipse's compiler is probably just wrong in this case to compile the code in the question.
If you want to force this to compile, you could use unchecked casts as that user in the comments is suggesting, but I don't know enough about the classes involved to say whether the result of that is actually provably correct. The above two code examples show how doing something like that could actually go horribly wrong (in principle), but Class is sometimes a special case.
A way to fix it that's more proper would be to declare a type variable on bindResourceFactory so it takes a Class<? extends Factory<T>> too, but I don't know if that actually works for the way you're calling the method.

Eclipse's "extract to method" on Function results in compilation error

Starting with a class A with 2 fields, name and id, a constructor and getters, I wrote this test, which runs green:
#Test
public void test() {
List<A> list = Arrays.asList(new A(null, "a"), new A(null, "b"));
Collections.sort(list,
Comparator
.comparing(A::getName, Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(A::getId, Comparator.nullsLast(Comparator.reverseOrder())));
assertThat(list.get(0).id, is("b"));
}
However, if I select Eclipse's quick fix "extract to method" on A::getName:
I suddenly get 2 compilation errors on the next line (thenComparing(...)):
#Test
public void test() {
List<A> list = Arrays.asList(new A(null, "a"), new A(null, "b"));
Collections.sort(list,
Comparator
.comparing(extracted(), Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(A::getId, Comparator.nullsLast(Comparator.reverseOrder())));
assertThat(list.get(0).id, is("b"));
}
private Function<? super A, ? extends String> extracted() {
return A::getName;
}
Saying:
The type Test.A does not define getId(capture#1-of ? super Test.A) that is applicable here
and
The method thenComparing(Function<? super capture#1-of ? super Test.A,? extends U>, Comparator<? super U>) in the type Comparator is not applicable for the arguments (A::getId, Comparator<Comparable<? super Comparable<? super T>>>)
Why does this result in an error? What am I doing wrong?
It's the refactoring that's to blame. It creates two new wildcards that need to be captured before / during type inference. After this, types can no longer be unified and inference cannot find a solution.
Another bug is that javac 8 accepted this code, but this has been fixed in javac 9 (I tried build 9-ea+118).
In that version javac's error message reads:
error: no suitable method found for thenComparing(A::getId,Comparator<T#1>)
.thenComparing(A::getId, Comparator.nullsLast(Comparator.reverseOrder())));
^
method Comparator.<U#1>thenComparing(Function<? super CAP#1,? extends U#1>,Comparator<? super U#1>) is not applicable
(cannot infer type-variable(s) U#1
(argument mismatch; invalid method reference
method getId in class A cannot be applied to given types
required: no arguments
found: CAP#1
reason: actual and formal argument lists differ in length))
method Comparator.<U#2>thenComparing(Function<? super CAP#1,? extends U#2>) is not applicable
(cannot infer type-variable(s) U#2
(actual and formal argument lists differ in length))
where T#1,T#2,U#1,T#3,U#2 are type-variables:
T#1 extends T#2
T#2 extends Comparable<? super T#2>
U#1 extends Object declared in method <U#1>thenComparing(Function<? super T#3,? extends U#1>,Comparator<? super U#1>)
T#3 extends Object declared in interface Comparator
U#2 extends Comparable<? super U#2> declared in method <U#2>thenComparing(Function<? super T#3,? extends U#2>)
where CAP#1 is a fresh type-variable:
CAP#1 extends Object super: A from capture of ? super A
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
EDIT:
The bug in javac was most likely https://bugs.openjdk.java.net/browse/JDK-8039214 which was indeed resolved for version 9. This is said to be the "master bug" for a cluster of related bugs dealing with javac's failure to correctly handle wildcard captures. In that bug the following text is proposed for the release notes:
The javac compiler's behavior when handling wildcards and "capture" type variables has been improved for conformance to the language specification. This improves type checking behavior in certain unusual circumstances. It is also a source-incompatible change: certain uses of wildcards that have compiled in the past may fail to compile because of a program's reliance on the javac bug.
I believe in both cases (javac 9 and ecj) the complaint regarding getId is secondary, result of inference having failed already.
At a closer look, the error against getId is the primary cause for failure to type-check:
The resolved type of comparing(..) is Comparator<capture#1-of ? super A> (capture induced by capturing the resolved type of extracted()).
The target type for the method reference A::getName is Function<? super capture#1-of ? super A,? extends U> (from the first parameter of <U> Comparator<T> thenComparing(Function<? super T, ? extends U>, Comparator<? super U>)
The non-wildcard parameterization (JLS 9.9) of this target type is <capture#1-of ? super A, U>.
Hence the method reference must implement the function type U apply(capture#1-of ? super A)
To use the argument of apply as the receiver for getId we need a value of type A, but we only have guarantees that the provided value is a (unknown) super type of A.
javac reverts to expecting an argument of the given type, while getId takes no argument -> hence the 1st message regarding argument list lengths.
Ergo getId does not implement the expected functional type.
This vaguely matches the description in JDK-8039214 that javac 8 erroneously uses a bound of the capture, instead of the capture itself. I say vaguely, because the bug speaks of upper bounds, while here javac 8 seems to use a lower bound even.

How can I get contains(List<Matcher> itemMatchers) to compile in Java 7?

I am learning Hamcrest 1.3 and I want to come up with an example for each Hamcrest static method in Matchers. The Javadoc helpfully already has examples for some methods. I tested the following contains code snippet with Java 8 and it passed:
assertThat(Arrays.asList("foo", "bar"),
contains(Arrays.asList(equalTo("foo"), equalTo("bar"))));
However, my team is currently using Java 7 so I wanted to make sure all the examples work in that version. The above code snippet produces the following error in Java 7:
no suitable method found for assertThat(java.util.List,org.hamcrest.Matcher>>>)
method org.junit.Assert.assertThat(T,org.hamcrest.Matcher) is not applicable
(actual argument org.hamcrest.Matcher>>> cannot be converted to org.hamcrest.Matcher> by method invocation conversion)
method org.junit.Assert.assertThat(java.lang.String,T,org.hamcrest.Matcher) is not applicable
(cannot instantiate from arguments because actual and formal argument lists differ in length)
I know that Java 8 added new implicit typing features for static methods and I think this is likely related. I have attempted to refactor out parameters and casting them to the expected arguments, but that results in the same error:
List<String> actual = Arrays.asList("foo", "bar");
List<Matcher<String>> expected = Arrays.asList(equalTo("foo"),
equalTo("bar"));
assertThat(actual, contains(expected));
What is the proper way to call static <E> Matcher<java.lang.Iterable<? extends E>> contains(java.util.List<Matcher<? super E>> itemMatchers) in Java 7?
In the Hamcrest Javadoc, the method signature for the contains() that you're targeting is:
public static <E> Matcher<Iterable<? extends E>> contains(List<Matcher<? super E>> itemMatchers);
The important bit to note in the above signature is the List<Matcher<? super E>>. Java 7 cannot infer List<Matcher<? super E>> from List<Matcher<String>>. The contains() is additionally overloaded, so the signature of the method that Java 7 targets is:
public static <E> Matcher<Iterable<? extends E>> contains(E... items);
And this is the reason you're getting the cryptic compilation error message!
Fortunately, the fix is pretty straightforward:
List<String> actual = Arrays.asList("foo", "bar");
List<Matcher<? super String>> expected = Arrays.<Matcher<? super String>>asList(equalTo("foo"),
equalTo("bar"));
assertThat(actual, contains(expected));
I don't have the libraries installed to readily test this, but I think this is a subtyping problem.
expected has type List<Matcher<String>>
contains(expected) has type Matcher<Iterable<String>>
actual has type List<String>
assertThat needs a first argument of type T and a second of type Matcher<T>
Your T is List<String>, which means it expects the second argument of type Matcher<List<String>>, not Matcher<Iterable<String>>.
I'm guessing this should fix it:
Iterable<String> actual = Arrays.asList("foo", "bar");
List<Matcher<String>> expected = Arrays.asList(equalTo("foo"),
equalTo("bar"));
assertThat(actual, contains(expected));
Again, I haven't tested this fix—but that seems like it would clear up the type issues.

Categories

Resources