簡體   English   中英

Java在稍后使用泛型來設置數組的原始類型

[英]Java use generics to set the primitive type of an array later

我試圖用Java編寫一些簡單的數字代碼,以便以后可以在float和double之間進行選擇。 我的課程的簡化版本類似於以下示例:

public class UniformGrid<T> {

    public T[] data;

    public UniformGrid(int arrayDim) {

        data = new T[arrayDim];

    }
}

這不起作用,嘗試編譯時出現generic array creation錯誤。 谷歌搜索和閱讀一些我了解了java.lang.reflect.Array SO答案,並嘗試使用

    data = (T[]) Array.newInstance(T.class, arrayDim);

由於T是(可能)是原始類型,因此這也不起作用。 我的Java知識非常生銹(尤其是在泛型時),我想知道為什么新運算符不能與泛型數組類型一起使用。 當然,我也很感興趣如何用Java解決這一問題。

由於類型Erase,您無法在Java中創建通用數組。 解決此問題的最簡單方法是使用List<T> 但是,如果必須使用數組,則可以對數組使用Object[]並確保僅將T對象放入其中。 (這是ArrayList采取的策略。)

例如:

private Object[] data = new Object[10];
private int size = 0;

public void add(T obj) {
    data[size++] = obj;
}

public T get(int i){
    return (T) data[i];
}

當然,您會從編譯器中收到未經檢查的警告,但是您可以取消該警告。

創建數組時無法使用泛型,因為您在運行時不知道T是什么類型。 這稱為類型擦除

解決方案很簡單:使用List<T> data

抱歉,您必須采取另一種方法:

  1. 類型參數必須是引用類型,不能是原始類型。
  2. 僅引用類型支持多態,並且僅支持實例方法。 原始類型沒有。 float和double沒有共同的超類型; 您不能編寫像a + b這樣的表達式並在運行時選擇執行浮點加法還是雙加法。 而且由於Java(與C ++或C#不同,它為每個類型參數發出新代碼)對通用類型的所有實例使用相同的字節碼,因此您需要多態性才能使用不同的運算符實現。

如果您真的需要它,我會研究代碼生成,也許是作為自動構建的一部分。 (對源代碼的簡單搜索和替換應該能夠將在double上運行的庫轉換為在float上運行的庫。)

只要使用FloatDouble而不是floatdouble ,這都是可能的,因為Java泛型中不允許使用基本類型。 當然,這可能會很慢。 而且,您將無法(安全地)允許直接公共訪問陣列。 因此,該答案不是很有用,但在理論上可能很有趣。 無論如何,如何構造數組...

data = (T[]) new Object[arrayDim];

這會給您一個警告,但這並不是直接擔心的事情。 它以這種特殊形式工作-在通用構造函數內部, data是對此新構造對象的唯一引用。 請參閱此頁面。

您將無法以自己喜歡的方式公開訪問此數組對象。 您需要在UniformGrid<T>設置方法來獲取和設置對象。 這樣,編譯器將確保類型安全,並且運行時不會給您帶來任何問題。

private T[] data;
public void set(int pos, T t) {
        data[pos] = t;
}
public T get(int pos) {
        return data[pos];
}

在這種情況下,要set的接口將(在編譯時)強制傳遞正確的類型。 底層數組的類型為Object[]但這沒關系,因為它可以采用任何引用類型-並且所有泛型類型實際上都是List<Object>或類似的東西,無論如何在運行時。

有趣的是吸氣劑。 編譯器“知道” data類型為T[] ,因此,吸氣劑將進行干凈編譯,並承諾返回T 因此,只要您將data保密,並且僅通過getset進行訪問,一切都會很好。

一些示例代碼在ideone上

public static void main(String[] args) {
        UniformGrid<A> uf = new UniformGrid<A>(1);
        //uf.insert(0, new Object()); // compile error
        uf.insert(0, new A());
        uf.insert(0, new B());
        Object o1= uf.get(0);
        A      o2= uf.get(0);
        // B      o2= uf.get(0); // compiler error
        System.out.println(o1);
        System.out.println(o2);
        System.out.println("OK so far");
        // A via_array1 = uf.data[0]; // Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [LA;
}

如您所願,存在uf.insert(0, new Object())B o2= uf.get(0);編譯錯誤B o2= uf.get(0);

但是您不應該將data成員公開。 如果這樣做,則可以編寫和編譯A via_array1 = uf.data[0]; 該行看起來應該沒問題,但是您遇到了運行時異常: Ljava.lang.Object; cannot be cast to [LA; Ljava.lang.Object; cannot be cast to [LA;

簡而言之, getset接口提供了一個安全的接口。 但是,如果您在使用數組時遇到了很多麻煩,則應該只使用ArrayList<T> 故事的寓意:任何語言(Java或C ++),具有泛型或不具有泛型, 只需對array拒絕即可 :-)

有效Java,第二版中的項目25討論了此問題:

數組是協變的且是精確化的; 泛型是不變的且已刪除。 因此,對於泛型,數組提供運行時類型安全性,但不提供編譯時類型安全性,反之亦然。 一般來說,數組和泛型不能很好地混合使用。

暫無
暫無

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

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