3

I updated an application JDK from 1.6 to 1.8 but I kept the language level 1.6 in IntelliJ.

After updating I got compilation error in some tests in assertThat statements with checking type of an object. The code is like this:

assertThat((Class) myList.get(0).getMyClassType(), is(equalTo(MySubclass.class)));

myList is looking like this:

  List<MyClassDefinition> myList = myClassDefinition.getMyClassDefinitions(reader);

Where definition of MyClassDefinition and getMyClassType() are like this:

public class MyClassDefinition {
    private Class<? extends MyClass> classType;

   public Class<? extends MyClass> getMyClassType() {
        return classType;
    }
}

and MySubclass is Myclass's subclass:

  public class MySubclass extends MyClass {
        @Override
        public void initialise() {
            // some action here!
        }
    }

The definition of MyClass is

public abstract class MyClass extends AnotherClass<AnotherType>{
  //Definitions
}

The import for Assert is and equals library is this:

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

and the hamcrest version is hamcrest-all-1-3

After changing project SDK to jdk 1.8 I am getting this error message:

Error:(136, 9) java: no suitable method found for assertThat(java.lang.Class,org.hamcrest.Matcher<java.lang.Class<MySubClass>>)
    method org.hamcrest.MatcherAssert.assertThat(java.lang.String,boolean) is not applicable
      (argument mismatch; java.lang.Class cannot be converted to java.lang.String)
    method org.hamcrest.MatcherAssert.<T>assertThat(java.lang.String,T,org.hamcrest.Matcher<? super T>) is not applicable
      (cannot infer type-variable(s) T
        (actual and formal argument lists differ in length))
    method org.hamcrest.MatcherAssert.<T>assertThat(T,org.hamcrest.Matcher<? super T>) is not applicable
      (cannot infer type-variable(s) T
        (argument mismatch; org.hamcrest.Matcher<java.lang.Class<MySubClass>> cannot be converted to org.hamcrest.Matcher<? super java.lang.Class>))

application is build with Ant and we have added hamcrest jar file to class file, so there is no change when we are altering JDK in project. So my question is why this code is working with JDK 1.6 but not working with 1.8 while I am using same level of language (1.6) for compilation? Is it depend to different libraries some how?

Govan
  • 2,079
  • 4
  • 31
  • 53
  • 1
    You're missing vital details here. What does `MyClass` look like? What is `myList`? What is `taskClass`? What classes are the static methods `assertThat`, `is` and `equalTo` coming from? Write a [MCVE](https://stackoverflow.com/help/mcve) please. – Michael Feb 02 '18 at 11:33
  • @Michael OK I added information – Govan Feb 02 '18 at 11:50
  • I hope `initialise()` is _not_ called in a constructor. – Joop Eggen Feb 02 '18 at 12:00
  • @JoopEggen no it is not but it doesn't matter. As I said I am getting compile error. So JDK 1.8 dosn't think we have right syntax here but jdk 1.6 have no problem – Govan Feb 02 '18 at 12:04

1 Answers1

3

Apparently, you had a problem with the generic type signatures which you worked around by inserting a type cast to the raw type Class, which causes the compiler to treat the entire statement as an unchecked operation using raw types only.

As you can see from the compiler message, org.hamcrest.Matcher<java.lang.Class<MySubClass>> cannot be converted to org.hamcrest.Matcher<? super java.lang.Class>, it now did a generic type check which (correctly) failed, but imho, it shouldn’t do that type check under Java 6 language rules. The problem stems from the fact that JDK8 doesn’t include the original Java 6 compiler but rather let the new compiler attempt to compile using the old rules, which may not be that precise.

But regardless of whether the behavior is correct or not, I wouldn’t expect a fix for it, as emulating the old Java 6 language rules is not of a high priority.

Note that with source level 1.8, the code compiles without problems, even without the raw type cast. The original problem stems from the restrictive signature of CoreMatchers.equalTo(…). The standalone expression equalTo(InstanceOfFoo) returns a Matcher<Foo>, which being passed to assertThat allows to only test instances of Foo (it’s even worse when using isA(Foo.class) which also can only test instances of Foo, which makes the test pointless).

With Java 8, there is target type inference, which allows, e.g.
Matcher<Object> m = equalTo(InstanceOfFoo);, which will infer Object for <T> and passes, as InstanceOfFoo is also assignable to Object. This also works in conjunction with the compound assertThat statement of your question, inferring a suitable type.

This guides us to the general solution that works with all language levels. Just use

assertThat(myList.get(0).getMyClassType(), is(equalTo((Object)MySubclass.class)));

Class<MySubclass> is, of course, assignable to Object which makes the cast a no-op. But then, getting a Matcher<Object>, you can check every object you like, including the result of myList.get(0).getMyClassType(), which is, of course, also assignable to Object. Unlike your original workaround, this doesn’t bear any raw type usage nor unchecked operation.

You could also use an explicit type instead of a cast, CoreMatchers.<Object>equalTo(MySubclass.class), but this eliminates the benefit of import static.

By the way, when matching Class objects, you may use sameInstance instead of equalTo.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thanks for your complete answer. The code is old code I have got and trying to update to java 8. Interesting that if the source level is 1.8 it is compiling without problem. I will update tests. – Govan Feb 02 '18 at 14:09