测试 Java 6 和 Java 8 中的类型匹配差异

Testing type matching differences in Java 6 and Java 8

我将应用程序 JDK 从 1.6 更新到 1.8,但我在 IntelliJ 中保持语言级别 1.6。

更新后,我在检查对象类型的 assertThat 语句中的某些测试中遇到编译错误。代码是这样的:

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

myList 看起来像这样:

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

MyClassDefinition 和 getMyClassType() 的定义是这样的:

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

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

而 MySubclass 是 Myclass 的子class:

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

MyClass的定义是

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

Assert is and equals 库的导入是这样的:

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

并且 hamcrest 版本是 hamcrest-all-1-3

将项目 SDK 更改为 jdk 1.8 后,我收到此错误消息:

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>))

应用程序是用 Ant 构建的,我们已将 hamcrest jar 文件添加到 class 文件,因此当我们在项目中更改 JDK 时没有变化。 所以我的问题是,为什么当我使用相同级别的语言 (1.6) 进行编译时,这段代码可以使用 JDK 1.6 而不能使用 1.8?它是否取决于不同的库?

显然,您通过将类型转换插入 原始类型 Class 解决了泛型类型签名问题,这导致编译器将整个语句视为仅使用 原始类型未检查 操作。

正如您从编译器消息中看到的那样,org.hamcrest.Matcher<java.lang.Class<MySubClass>> cannot be converted to org.hamcrest.Matcher<? super java.lang.Class>,它现在进行了泛型类型检查(正确地)失败了,但是恕我直言,它不应该在 Java 下进行类型检查6 语言规则。问题源于 JDK8 不包含原始 Java 6 编译器,而是让新编译器尝试使用旧规则进行编译,这可能不那么精确。

但无论行为是否正确,我都不希望对其进行修复,因为模仿旧的 Java 6 语言规则不是高优先级。

请注意,使用源代码级别 1.8,即使没有 原始类型 转换,代码编译也没有问题。最初的问题源于 CoreMatchers.equalTo(…) 的限制性签名。传递给 assertThat 的独立表达式 equalTo(InstanceOfFoo) returns a Matcher<Foo> 只允许测试 Foo 的实例(使用 [=18= 时更糟] 也只能测试 Foo 的实例,这使得测试毫无意义)。

对于 Java 8,有目标类型推断,这允许,例如
Matcher<Object> m = equalTo(InstanceOfFoo);,这将为 <T> 推断出 Object 并通过,因为 InstanceOfFoo 也可以分配给 Object。这也可以与您问题的复合 assertThat 语句结合使用,从而推断出合适的类型。

这引导我们找到适用于所有语言级别的通用解决方案。只需使用

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

Class<MySubclass> 当然可以分配给 Object 这使得转换成为空操作。但是,得到一个 Matcher<Object>,你可以检查每个你喜欢的对象,包括 myList.get(0).getMyClassType() 的结果,当然,它也可以分配给 Object。与您最初的解决方法不同,这不支持任何 原始类型 用法,也不支持 未检查 操作。

您也可以使用显式类型而不是强制转换,CoreMatchers.<Object>equalTo(MySubclass.class),但这会消除 import static 的好处。

顺便说一句,在匹配Class个对象时,您可以使用sameInstance代替equalTo