[英]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.