簡體   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