繁体   English   中英

Java泛型编译错误“不兼容的类型”(在gradle中,但不在IDE中)

[英]Java generics compilation error “incompatible types” (in gradle but not in IDE)

在以下情况下,我在理解Java泛型的行为时遇到问题。

有一些参数化接口IFace<T> ,并且在某个类上的方法返回了扩展该接口的类, <C extends IFace<?>> Class<C> getClazz() java编译错误由gradle产生,1.8 Oracle JDK,OSX和Linux,但不是由Eclipse IDE中的Eclipse编译器提供的(它也很高兴在Eclipse RCP OSGi运行时下运行),用于以下实现:

public class Whatever {
  public interface IFace<T> {}

  @SuppressWarnings("unchecked")
  protected <C extends IFace<?>> Class<C> getClazz() {
    return (Class<C>) IFace.class;
  }
}

➜./gradlew构建

:compileJava
/Users/user/src/test/src/main/java/Whatever.java:6: error: incompatible types: Class<IFace> cannot be converted to Class<C>
    return (Class<C>) IFace.class;
                           ^
  where C is a type-variable:
    C extends IFace<?> declared in method <C>getClazz()
1 error
:compileJava FAILED

这种实现不是很合乎逻辑的实现,它是有人认为很好的默认实现,但是我想理解为什么它不进行编译而不是对代码的逻辑提出质疑。

最简单的解决方法是在方法签名中删除泛型定义的一部分。 以下编译没有问题,但依赖于原始类型:

protected Class<? extends IFace> getClazz() {
  return IFace.class;
}

为什么会编译,而上面的编译不呢? 有没有办法避免使用原始类型?

由于类型不正确,因此未编译。

考虑以下:

class Something implements IFace<String> {}
Class<Something> clazz = new Whatever().getClazz();
Something sth = clazz.newInstance();

这将因InstantiationException而失败,因为clazzIFace.class ,因此无法实例化; 它不是Something.class ,可以实例化。

Ideone演示

但是非不可例性不是这里的重点Class不可非例性很好-这是该代码已尝试实例化它

Class<T>具有方法T newInstance() ,如果成功完成,则必须返回T ,或者引发异常。

如果上面的clazz.newInstance()调用成功(并且编译器不知道不会),则返回的值将是IFace的实例,而不是Something ,因此分配将失败,并发生ClassCastException

您可以通过将IFace更改为可实例化来证明这一点:

class IFace<T> {}
class Something extends IFace<String> {}
Class<Something> clazz = new Whatever().getClazz();
Something sth = clazz.newInstance();  // ClassCastException

Ideone演示

通过像这样引发错误,编译器完全消除了陷入这种情况的可能性。

因此,请不要尝试用原始类型来模糊编译器的错误。 它告诉您有问题,应该正确解决。 该修复程序的确切外观取决于您实际使用Whatever.getClass()的返回值的目的。

有趣的是,Eclipse编译器确实可以编译代码,但是Oracle Java Compiler不会编译它。 您可以在gradle构建期间使用Eclipse编译器来确保gradle的编译方式与IDE相同。 将以下代码段添加到您的build.gradle文件中

configurations {
      ecj
    }

    dependencies {
      ecj 'org.eclipse.jdt.core.compiler:ecj:4.4.2'
    }

    compileJava {
        options.fork = true
        options.forkOptions.with {
        executable = 'java'
        jvmArgs = ['-classpath', project.configurations.ecj.asPath, 'org.eclipse.jdt.internal.compiler.batch.Main', '-nowarn']
      }
    }

它无法编译,因为C可能是任何东西 ,编译器可以确保IFace.class该要求:

class X implements IFace<String> {
}

Class<X> myX = myWhatever.getClass(); // would be bad because IFace.class is not a Class<X>.

安迪(Andy)刚刚演示了为什么这个作业会很糟糕(例如,在尝试实例化该类时),所以我的回答与他的回答并没有太大不同,但是也许更容易理解...

这全部与调用上下文所隐含的方法的类型参数的Java编译器功能有关。 您肯定知道方法

Collections.emptyList();

声明为

public static <T> List<T> emptyList() {
  // ...
}

返回(List<T>)new ArrayList<String>(); 即使使用SuppressWarnings ,显然也将是非法的,因为T可能是调用者将方法的结果分配给(或使用)方法的任何类型类型推断 )。 但这非常类似于您在返回IFace.class时尝试的操作,在该操作中调用者将需要另一个类。

哦,对于那些真正喜欢泛型的人来说,这可能是解决您的问题的最糟糕的方法:

public <C extends IFace<?>> Class<? super C> getClazz() {
  return IFace.class;
}

以下可能会起作用:

public class Whatever {
    public interface IFace<T> {}

    @SuppressWarnings("unchecked")
    protected <C extends IFace> Class<C> getClazz() {
        return (Class<C>) IFace.class;
    }
}

在您以前的代码中,问题在于C必须扩展IFace<?>> ,但是您仅提供了IFace 对于类型为Class<IFace> != Class<IFace<?>> ,因此不能将Class<IFace>Class<C extends IFace<?>>

也许存在一些更好的解决方案,因为我不是泛型专家。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM