[英]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 - 神奇的東西往往會讓人們失望,並且很容易對代碼做出錯誤的假設。 例如,外部的秘密參考? 這真的會讓你陷入垃圾收集(它會阻止收集外部實例。)。
請注意,您的代碼質量很差。 需要研究的幾件事:
正確的“我不知道該怎么做”異常處理程序永遠不會只記錄它並忘記它,並且 System.out 不是適當的記錄位置。 只需e
丟棄幾乎所有相關信息 - 特別是您不想丟棄的原因。 我不知道異常塊的正確處理程序總是這樣: throw new RuntimeException("Unhandled", e);
. 這很簡短,不會導致代碼繼續在未知的 state 中運行,並保留所有信息。 解決這個問題。
不要在 class 上調用newInstance()
; 它已被棄用。 它有瘋狂的異常行為。 你想要c.getConstructor().newInstance()
代替。
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.