1. 程式人生 > >五分鐘學Java:列印Java陣列最優雅的方式是什麼?

五分鐘學Java:列印Java陣列最優雅的方式是什麼?

在逛 Stack Overflow 的時候,發現了一些訪問量像‎安第斯山一樣高的問題,比如說這個:列印 Java 陣列最優雅的方式是什麼?訪問量足足有 220W+,想不到啊,這麼簡單的問題竟然有這麼多程式設計師被困擾過。

來回顧一下提問者的問題吧:

在 Java 中,陣列雖然是一個物件,但並未明確的定義這樣一個類,因此也就沒有覆蓋 toString() 方法的機會。如果嘗試直接列印陣列的話,輸出的結果並不是我們預期的結果。那有沒有一些簡單可行的方式呢?

如果大家也被這個問題困擾過,或者正在被困擾,就請隨我來,咱們肩並肩手拉手一起梳理一下這個問題,並找出最佳答案。Duang、Duang、Duang,打怪進階嘍!

01、為什麼不能直接列印

很好奇,是不是,為什麼不能直接使用 System.out.println() 等系列方法來列印陣列?來看這樣一個例子。

String [] cmowers = {"沉默","王二","一枚有趣的程式設計師"};
System.out.println(cmowers);

程式列印的結果是:

[Ljava.lang.String;@3d075dc0

[Ljava.lang.String; 表示字串陣列的 Class 名,@ 後面的是十六進位制的 hashCode——這樣的列印結果太“人性化”了,一般人表示看不懂!為什麼會這樣顯示呢?檢視一下 java.lang.Object

類的 toString() 方法就明白了。

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

PS:陣列雖然沒有顯式定義成一個類,但它的確是一個物件,繼承了祖先類 Object 的所有方法。

那為什麼陣列不單獨定義一個類來表示呢?就像字串 String 類那樣呢?

一個合理的解釋是 Java 將其隱藏了。假如真的存在一個 Array.java,我們也可以假想它真實的樣子,它必須要定義一個容器來存放陣列的元素,就像 String 類那樣。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}

但這樣做真的有必要嗎?為陣列單獨定義一個類,是不是有點畫蛇添足的意味。

02、使用 Stream

如果使用的是 JDK8 以上的版本,我們可以使用 Stream 這種時髦、fashion 的方式來遍歷陣列,順帶將其打印出來。

第一種:

Arrays.asList(cmowers).stream().forEach(s -> System.out.println(s));

第二種:

Stream.of(cmowers).forEach(System.out::println);

第三種:

Arrays.stream(cmowers).forEach(System.out::println);

列印的結果如下所示。

沉默
王二
一枚有趣的程式設計師

沒錯,這三種方式都可以輕鬆勝任本職工作,並且顯得有點高大上,畢竟用到了 Stream,以及 lambda 表示式。但在我心目中,它們並不是最優雅的方式。

03、使用 for 迴圈

當然了,如果不喜歡 Stream 的方式,也可以使用 for 迴圈對陣列進行變數順便列印的方式,甚至 for-each 也行。

for(int i = 0; i < cmowers.length; i++){
    System.out.println(cmowers[i]);
}

for (String s : cmowers) {
    System.out.println(s);
}

但如果你是一名有追求的程式設計師的話,不免覺得這樣的方式有點 low。那到底最優雅的方式是什麼呢?

04、使用 Arrays.toString()

Arrays.toString() 可以將任意型別的陣列轉成字串,包括基本型別陣列和引用型別陣列,截個圖大家感受一下。

Arrays 類就不用我多做介紹了吧?雖然我的意思大家懂,但我還是忍不住要廢話兩句:該類包含了各種運算元組的便捷方法,與其命名為 Arrays,不如命名為 ArrayUtil。

使用 Arrays.toString() 方法來列印陣列再優雅不過了,就像,就像,就像蒙娜麗莎的微笑。

被逗笑了吧?來,懷揣著愉快的心情看一下程式碼示例。

String [] cmowers = {"沉默","王二","一枚有趣的程式設計師"};
System.out.println(Arrays.toString(cmowers));

程式列印結果:

[沉默, 王二, 一枚有趣的程式設計師]

哇,列印格式不要太完美,不多不少!完全是我們預期的結果:[] 表明是一個數組,, 點和空格用來分割元素。

順便再來看一下 toString() 方法的原始碼。

public static String toString(Object[] a) {
    if (a == null)
        return "null";

    int iMax = a.length - 1;
    if (iMax == -1)
        return "[]";

    StringBuilder b = new StringBuilder();
    b.append('[');
    for (int i = 0; ; i++) {
        b.append(String.valueOf(a[i]));
        if (i == iMax)
            return b.append(']').toString();
        b.append(", ");
    }
}

1)如果陣列為 null,那就返回“null”字串,考慮很周全,省去了 NullPointerException 的麻煩。

2)如果陣列長度為 0,那就返回“[]”字串。注意,此處沒有使用 a.length == 0 進行判空,而是用了 a.length - 1 == -1,又為之後的 for 迴圈中的 i == iMax 埋下了伏筆,資源一點也沒有浪費。

3)for 迴圈中字串的拼接更是巧妙,for 迴圈的條件中沒有判斷 i < a.length,而在迴圈體內使用了 i == iMax,這樣有什麼好處呢?

通常來說,一般的程式設計師拼接字串的時候是這樣做的。

StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0; i < cmowers.length; i++) {
    b.append(cmowers[i]);
    b.append(", ");
}
b.delete(b.length()-2, b.length());
b.append(']');

沒錯吧,非常的循規蹈矩,但比起 toString() 方法原始碼中的寫法,就要相形見絀了。情不自禁地感慨一下啊:要想成為一名卓越的程式設計師,而不只是一名普通的程式設計師,最快的捷徑就是學習 Java 的原始碼。

05、使用 Arrays.deepToString()

如果需要列印多維碼陣列的話,Arrays.toString() 就無能為力了。

String[][] deepArray = new String[][] {{"沉默", "王二"}, {"一枚有趣的程式設計師"}};
System.out.println(Arrays.toString(deepArray));

列印結果如下所示。

[[Ljava.lang.String;@7ba4f24f, [Ljava.lang.String;@3b9a45b3]

不不不,這不是我們期望的結果,怎麼辦呢?使用 Arrays.deepToString(),專為多維陣列而生。

String[][] deepArray = new String[][] {{"沉默", "王二"}, {"一枚有趣的程式設計師"}};
System.out.println(Arrays.deepToString(deepArray));

列印結果如下所示。

[[沉默, 王二], [一枚有趣的程式設計師]]

優秀吧!至於 deepToString() 的原始碼,本文就不再分析了,大家感興趣的話自己看一看。(如果你想卓越的話,必須要看啊)

06、鳴謝

好了各位讀者朋友們,以上就是本文的全部內容了。能看到這裡的都是最優秀的程式設計師,升職加薪就是你了