JavaRush /Java Blog /Random-TW /喝咖啡休息#130。如何正確使用 Java 陣列 - Oracle 的提示

喝咖啡休息#130。如何正確使用 Java 陣列 - Oracle 的提示

在 Random-TW 群組發布
來源:Oracle 使用陣列可以包括反射、泛型和 lambda 表達式。 我最近與一位使用 C 進行開發的同事交談。主題轉向了數組以及它們在 Java 中與 C 相比如何工作。考慮到 Java 被認為是一種類似 C 的語言,我覺得這有點奇怪。他們其實有很多相似之處,但也有不同之處。讓我們從簡單開始。 喝咖啡休息#130。 如何正確使用 Java 陣列 - Oracle 提示 - 1

數組聲明

如果您遵循 Java 教程,您將看到有兩種聲明數組的方法。第一個很簡單:
int[] array; // a Java array declaration
您可以看到它與 C 有何不同,其中語法為:
int array[]; // a C array declaration
讓我們再次回到Java。聲明數組後,您需要分配它:
array = new int[10]; // Java array allocation
是否可以同時宣告和初始化數組?其實沒有:
int[10] array; // NOPE, ERROR!
但是,如果您已經知道這些值,則可以立即聲明並初始化陣列:
int[] array = { 0, 1, 1, 2, 3, 5, 8 };
如果你不知道其意義怎麼辦?以下是您最常看到的用於聲明、分配和使用int數組的代碼:
int[] array;
array = new int[10];
array[0] = 0;
array[1] = 1;
array[2] = 1;
array[3] = 2;
array[4] = 3;
array[5] = 5;
array[6] = 8;
...
請注意,我指定了一個int數組,它是Java 基本資料類型的數組讓我們看看如果您使用 Java 物件數組而不是基元嘗試相同的過程會發生什麼:
class SomeClass {
    int val;
    // …
}
SomeClass[] array = new SomeClass[10];
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;
如果我們運行上面的程式碼,我們會在嘗試使用陣列的第一個元素後立即得到一個異常。為什麼?即使數組已分配,數組的每個段都包含空物件參考。如果您將此程式碼輸入到 IDE 中,它甚至會自動為您填入 .val,因此錯誤可能會令人困惑。若要修復該錯誤,請按照下列步驟操作:
SomeClass[] array = new SomeClass[10];
for ( int i = 0; i < array.length; i++ ) {  //new code
    array[i] = new SomeClass();             //new code
}                                           //new code
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;
但這並不優雅。我想知道為什麼我不能用更少的程式碼輕鬆分配數組和數組中的對象,甚至可能全部在一行上。為了找到答案,我進行了多次實驗。

在 Java 陣列中尋找必殺技

我們的目標是優雅地編寫程式碼。遵循「乾淨程式碼」的規則,我決定建立可重複使用的程式碼來清理陣列分配模式。這是第一次嘗試:
public class MyArray {

    public static Object[] toArray(Class cls, int size)
      throws Exception {
        Constructor ctor = cls.getConstructors()[0];
        Object[] objects = new Object[size];
        for ( int i = 0; i < size; i++ ) {
            objects[i] = ctor.newInstance();
        }

        return objects;
    }

    public static void main(String[] args) throws Exception {
        SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32); // see this
        System.out.println(array1);
    }
}
多虧了toArray實現 ,標記為「see this」的程式碼行看起來正是我想要的方式。此方法使用反射來尋找所提供的類別的預設建構函數,然後呼叫該建構函數來實例化該類別的物件。此過程為每個數組元素呼叫一次建構函數。極好!只是可惜,行不通。程式碼可以正常編譯,但在執行時會拋出ClassCastException錯誤。要使用此程式碼,您需要建立一個Object元素數組,然後將數組的每個元素轉換為SomeClass類,如下所示:
Object[] objects = MyArray.toArray(SomeClass.class, 32);
SomeClass scObj = (SomeClass)objects[0];
...
這不優雅!經過更多實驗,我使用反射、泛型和 lambda 表達式開發了幾種解決方案。

解決方案1:使用反射

這裡我們使用java.lang.reflect.Array類別來實例化您指定的類別的數組,而不是使用基java.lang.Object類別。這本質上是一行程式碼更改:
public static Object[] toArray(Class cls, int size) throws Exception {
    Constructor ctor = cls.getConstructors()[0];
    Object array = Array.newInstance(cls, size);  // new code
    for ( int i = 0; i < size; i++ ) {
        Array.set(array, i, ctor.newInstance());  // new code
    }
    return (Object[])array;
}
您可以使用這種方法來取得所需類別的數組,然後像這樣使用它:
SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32);
儘管這不是必需的更改,但第二行已更改為使用 Array 反射類別來設定每個陣列元素的內容。這真太了不起了!但還有一個細節似乎不太正確:對SomeClass[] 的轉換看起來不太好。幸運的是,有一個泛型解決方案。

解 2:使用泛型

集合框架使用泛型進行類型綁定,並在許多操作中消除了對泛型的強制轉換。泛型也可以用在這裡。我們以java.util.List為例。
List list = new ArrayList();
list.add( new SomeClass() );
SomeClass sc = list.get(0); // Error, needs a cast unless...
除非您像這樣更新第一行,否則上面程式碼片段中的第三行將引發錯誤:
List<SomeClass> = new ArrayList();
您可以透過在MyArray類別 中使用泛型來實現相同的結果。這是新版本:
public class MyArray<E> {
    public <E> E[] toArray(Class cls, int size) throws Exception {
        E[] array = (E[])Array.newInstance(cls, size);
        Constructor ctor = cls.getConstructors()[0];
        for ( int element = 0; element < array.length; element++ ) {
            Array.set(array, element, ctor.newInstance());
        }
        return arrayOfGenericType;
    }
}
// ...
MyArray<SomeClass> a1 = new MyArray(SomeClass.class, 32);
SomeClass[] array1 = a1.toArray();
看起來不錯。透過使用泛型並在聲明中包含目標類型,可以在其他操作中推斷出該類型。此外,透過執行以下操作,可以將該程式碼減少為一行:
SomeClass[] array = new MyArray<SomeClass>(SomeClass.class, 32).toArray();
任務完成了,對嗎?嗯,不完全是。如果您不關心呼叫哪個類別建構函數,這很好,但如果您想呼叫特定的建構函數,那麼此解決方案不起作用。可以繼續使用反射來解決這個問題,但是這樣程式碼就會變得複雜。幸運的是,有 lambda 表達式提供了另一個解。

解 3:使用 lambda 表達式

我承認,我以前對 lambda 表達式並不是特別感興趣,但我已經學會欣賞它們。我特別喜歡java.util.stream.Stream接口,它處理物件集合。Stream幫助我達到了 Java 陣列的必殺技。這是我第一次嘗試使用 lambda:
SomeClass[] array =
    Stream.generate(() -> new SomeClass())
    .toArray(SomeClass[]::new);
為了方便閱讀,我將這段程式碼分成三行。您可以看到它滿足了所有要求:它簡單而優雅,創建了物件實例的填充數組,並允許您呼叫特定的建構函數。注意toArray方法參數:SomeClass[]::new。這是一個生成器函數,用於指派指定類型的陣列。然而,就目前情況而言,這段程式碼有一個小問題:它創建了一個無限大小的陣列。這不是很理想。但是這個問題可以透過呼叫limit方法來解決:
SomeClass[] array =
    Stream.generate(() -> new SomeClass())
    .limit(32)   // calling the limit method
    .toArray(SomeClass[]::new);
該數組現在限制為 32 個元素。你甚至可以為數組的每個元素設定特定的物件值,如下所示:
SomeClass[] array = Stream.generate(() -> {
    SomeClass result = new SomeClass();
    result.val = 16;
    return result;
    })
    .limit(32)
    .toArray(SomeClass[]::new);
這段程式碼展示了 lambda 表達式的強大功能,但程式碼並不整潔或緊湊。在我看來,呼叫另一個建構函數來設定該值會好得多。
SomeClass[] array6 = Stream.generate( () -> new SomeClass(16) )
    .limit(32)
    .toArray(SomeClass[]::new);
我喜歡基於 lambda 表達式的解決方案。當您需要呼叫特定的建構函式或處理陣列的每個元素時,它是理想的選擇。當我需要更簡單的東西時,我通常會使用基於泛型的解決方案,因為它更簡單。然而,您可以親眼看到 lambda 表達式提供了一種優雅且靈活的解決方案。

結論

今天我們學習如何在 Java 中 宣告和分配基元數組、分配Object元素數組、使用反射、泛型和 lambda 表達式。
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION