簡體   English   中英

創建新實例時,使用變量作為泛型類型

[英]Use variable for generic type when creating new instance

我正在嘗試做某事,這可能不是最好的方法,或者我可能只是缺少語言設計中的一些東西。 基本上,我有一些看起來像這樣的服務:

public class MyService<T extends MyAbstractClass>{}

作為我的服務器的配置,我想指出我用作環境變量的MyAbstractClass哪個擴展。 在我的具體用例中,它基本上是為我正在做的事情設置一個特定的數據源,但這在這里並不重要。

我以為我可以做這樣的事情,但是它給了我語法錯誤,所以顯然不允許這樣做。 我不確定我是否可以做我想要完成的事情。

@Bean(name="myService")
public MyService<? extends MyAbstractClass> getMyService(){
    String packageName = "com.my.project." + ConfigUtils.get(env, "mypackage") + "";
    Class<? extends MyAbstractClass> clazz = (Class<? extends MyAbstractClass>) Class.forName(packageName);
    return new MyService<clazz>();
}

我一直在尋找答案,但我甚至不確定如何在搜索引擎中正確地說出這一點來找到答案。 我應該如何在java中嘗試完成此操作?

這永遠不會奏效:

new MyService<clazz>() 

clazz是一個變量。

new調用中的類型參數只能是封閉方法或類的另一個類型參數(如果封閉方法不是靜態的)或具體的類名。

類型參數是僅在編譯時有影響的實體,為了限制參數類型和在泛型類和方法中返回值而引入,如果代碼可能在運行時傳遞錯誤類型的值,則在編譯時失敗時間。

clazz的值只能在運行時確定,因此編譯器無法確定編譯時實際類的內容。 這就是為什么使用包含變量的類引用作為類型參數參數是不合法的也沒有任何意義。

事實是,很難限制返回的MyService實例的底層類型參數是什么,並返回MyService<? extends MyAbstractClass> MyService<? extends MyAbstractClass>盡可能好。

至少你可以仔細檢查一下clazz實際上擴展了MyAbstractService而不是簡單地相信盲目的情況:

class ServiceClass {}
class MyService<T extends ServiceClass> {}

class Main {

   public MyService<? extends ServiceClass> getMyService() throws Exception {
      final String className = "com.my.project." + ConfigUtils.get(env, "mypackage");
      @SuppressWarnings("unchecked")
      final Class<? extends ServiceClass> clazz = (Class<? extends ServiceClass>) Class.forName(className);
      if (!ServiceClass.class.isAssignableFrom(clazz))
         throw new RuntimeException("bad configuration blah blah");
      return new MyService<ServiceClass>();
   }
}

請注意, new MyService<ServiceClass>()看起來不是很整潔,因為實際上clazz通常是其他東西; ServiceClass的后代,而不是ServiceClass本身...這個編譯沒有錯誤的原因是,到目前為止,給定代碼,與返回的類型參數無關,因為在運行時它們都被轉換為相同的代碼。

但是,在更復雜的上下文中,當您想要對某些對象執行某些操作時,如何引用相同的類型參數,您將開始遇到編譯時問題。 因此總是建議使用來自現場的更整潔的基於類型參數的解決方案:

class ServiceClass {}
class MyService<T extends ServiceClass> {}

class Main {

   public <S extends ServiceClass> MyService<S> getMyService() throws Exception {
      final String className = "com.my.project." + ConfigUtils.get(env, "mypackage");
      @SuppressWarnings("unchecked")
      final Class<S> clazz = (Class<S>) Class.forName(className);
      if (!ServiceClass.class.isAssignableFrom(clazz))
         throw new RuntimeException("bad configuration blah blah");
      return new MyService<S>();
   }
}

現在我認為你不需要在MyService中的某個地方保留對服務類的引用是很奇怪的...也許你最終需要編寫如下代碼:

class ServiceClass {}

class MyService<S extends ServiceClass> {
  private final Class<S> clazz;
  MyService(Class<S> clazz) {
     this.clazz = clazz;
  }

  Class<S> serviceClass() {
    return clazz;
  }
}

class Main {
  public <S extends ServiceClass> MyService<S> getMyService() throws Exception {
    final String className = "com.my.project." + ConfigUtils.get(env, "mypackage");
    @SuppressWarnings("unchecked")
    final Class<S> clazz = (Class<S>) Class.forName(className);
    if (!ServiceClass.class.isAssignableFrom(clazz))
       throw new RuntimeException("bad configuration blah blah");
    return new MyService<S>(clazz);
  }
}

此時, getMyService的類型參數S開始變得必要。

順便說一下,自Java 7(我認為)你不需要在新語句中指定S ,這將由編譯器推導:

return new MyService<>(clazz); 

您不能在方法聲明中使用通配符。 而是使用另一個類型變量並聲明它。

public <S extends MyAbstractClass> S getMyService() {
    // implementation omitted
}

請記住,在這種情況下, S與類聲明中的T 這就是為什么我通常給他們不同的名字。

在方法體中,您可以使用以下內容:

Class<S> clazz = (Class<S>)Class.forName(packageName);
return clazz.newInstance();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM