簡體   English   中英

創建通用 class 成員的實例:Class<t> .newInstance() 拋出異常</t>

[英]Create instance of generic class member: Class<T>.newInstance() throws exception

我想制作一個通用的 class 存儲臨時對象池(例如 2D 矢量 Vec2d ),這樣我就不必在模擬過程中動態分配它們。 基於這些答案[1] [2]我得出了這個解決方案:

import java.lang.reflect.Array;

class TempPool<T>{
  T[] objs;

  public TempPool(Class<T> c, int n) {
    T[] a = (T[])Array.newInstance(c, n);                // initialize the array
    try{ // Compiler forces me to use try fo handling                     
      for(int i=0; i<n; i++) a[i]=(T)c.newInstance();   // initialize fields in the array
    }catch(Exception e){
      System.out.println("Exception thrown  :" + e +" in TempPool()" );
    }
    objs = a;
  }

  T    borrow(  ){ return objs[icur++];    };
  void repay(T o){ 
    if( o != objs[icur] ) objs[-1]=null; // Throws exeption
    }else{ icur--; }
  }

但是當我嘗試使用

class Vec2d{ double x,y; }

tmpVec2d   = new TempPool       (Vec2d.class,10);   // tried both
//tmpVec2d = new TempPool<Vec2d>(Vec2d.class,10);   // tried both

它拋出異常Exception thrown:java.lang.InstantiationException: Boulders_rigid$Vec2d in TempPool()

內部課程並不像您認為的那樣運作。

內部 class 如下所示:

class Outer /* must be a class; not an interface */ {
    class Inner /* A class (not an interface), AND, not static {
    }
}

內部 class 有一個秘密的、不可見的最終字段,如下所示:

private final Outer magicThis;

不能是null Inner的每個構造函數都有第一個參數Outer magicThis ,但這個參數也是隱藏的。 要傳遞它,您可以使用這種奇怪的語法:

outerInstance.new Inner();

並且,至關重要的是,如果Outer.this是一個合法表達式(例如,在Outer內的任何非靜態方法中,它就是這樣),那么從某種意義上說,這使它變得“神奇”,你不知道它是這樣工作的, Outer.this被默認為 outerInstance: 在那些上下文中,並且只有在那些上下文中, new Inner()才有效。

既然你知道了這一切,那么你就會明白在非靜態內部 class 上調用newInstance()是行不通的:這需要一個無參數的構造函數,但沒有一個:所有構造函數都有那個秘密的、不可見的Outer magicThis參數.

您可以使用javap工具觀察這一切。

解決方案很簡單:將您的內部 class 標記為static 實際上,前面是 go 並將所有內部類標記為 static。 如果你真的知道你在做什么並且你絕對確定你真的想要那個,那么只制作非靜態內部 class - 神奇的東西往往會讓人們失望,並且很容易對代碼做出錯誤的假設。 例如,外部的秘密參考? 這真的會讓你陷入垃圾收集(它會阻止收集外部實例。)。

請注意,您的代碼質量很差。 需要研究的幾件事:

  1. 正確的“我不知道該怎么做”異常處理程序永遠不會只記錄它並忘記它,並且 System.out 不是適當的記錄位置。 只需e丟棄幾乎所有相關信息 - 特別是您不想丟棄的原因。 我不知道異常塊的正確處理程序總是這樣: throw new RuntimeException("Unhandled", e); . 這很簡短,不會導致代碼繼續在未知的 state 中運行,並保留所有信息。 解決這個問題。

  2. 不要在 class 上調用newInstance() 它已被棄用。 它有瘋狂的異常行為。 你想要c.getConstructor().newInstance()代替。

  3. objs[-1] = null當然會拋出; 沒有-1索引。 大概你想要objs[icur] = null 或者也許objs[objs.length - 1] = null; ? 我不確定repay試圖完成什么。

您不需要以這種方式使用反射。

查看java.util.ArrayList的源代碼:它沒有聲明E[]類型的數組來保存元素:它使用Object[]並應用強制轉換。

所以,你可以這樣做:

class TempPool<T>{
  Object[] objs;

  public TempPool(Class<T> c, int n) {
    objs = new Object[n];  // Simple!
    // ...
  }

  // Unchecked cast, but it's safe to suppress because you're ensuring only
  // instances of T are in here if you use it correct.
  @SuppressWarnings("unchecked");
  T    borrow(  ){ return (T) objs[icur++];    };
  
  // No changes needed to repay.
}

此外,不要通過Class<T>依賴反射,而是傳遞Supplier<T> (或者,更好的是Supplier<? extends T> ):反射實例化非常容易出錯,因為您需要確保您是傳遞帶有零參數構造函數的Class不會引發檢查異常等( rzwitserloot 的回答描述了內部類特有的另一個問題)。 在編譯時檢查Supplier<T>以確保存在這樣的構造函數; 並且它不會拋出已檢查的異常,因此它大大地整理了代碼。

  public TempPool(Supplier<? extends T> c, int n) {
    objs = new Object[n];  // Simple!
    for(int i=0; i<n; i++) objs[i]=c.get();
  }

或者,如果你想變得流暢:

objs = IntStream.range(0, n).mapToObj(i -> c.get()).toArray();

您現在可以像這樣調用:

tmpVec2d   = new TempPool<>(Vec2d::new,10);

當然,沒有顯式轉換的更簡潔的解決方案是簡單地使用List<T>

class TempPool<T>{
  List<T> objs;

  public TempPool(Class<T> c, int n) {
    objs = IntStream.range(0, n).mapToObj(i -> c.get()).collect(toList());
    // ...
  }

  // No cast required in borrow:
  T    borrow(  ){ return objs.get(icur++);    };

暫無
暫無

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

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