[英]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
而失败,因为clazz
是IFace.class
,因此无法实例化; 它不是Something.class
,可以实例化。
但是非不可例性不是这里的重点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
通过像这样引发错误,编译器完全消除了陷入这种情况的可能性。
因此,请不要尝试用原始类型来模糊编译器的错误。 它告诉您有问题,应该正确解决。 该修复程序的确切外观取决于您实际使用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.