1. 程式人生 > >如何優雅地列印一個Java物件?

如何優雅地列印一個Java物件?

你好呀,我是沉默王二,一個和黃家駒一樣身高,和劉德華一樣顏值的程式設計師。雖然已經寫了十多年的 Java 程式碼,但仍然覺得自己是個菜鳥(請允許我慚愧一下)。

在一個月黑風高的夜晚,我思前想後,覺得再也不能這麼蹉跎下去了。於是痛下決心,準備通過輸出的方式倒逼輸入,以此來修煉自己的內功,從而進階成為一名真正意義上的大神。與此同時,希望這些文章能夠幫助到更多的讀者,讓大家在學習的路上不再寂寞、空虛和冷。

為了更好的輸入,我選擇 Stack Overflow 作為戰鬥的第一線,畢竟很多前輩都在強烈推薦。本篇文章,我們來探討一下如何優雅地列印一個Java物件。

真沒想到,這個問題的訪問量像阿爾泰山一樣高,訪問量足足有 29+ 萬次,這不得了啊!說明有很多很多的程式設計師被這個問題困擾過。

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

提問者定義了這樣一個類:

public class Cmower {
    private String name;

    public Cmower(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

然後建立了一個該類的物件,並嘗試列印它:

Cmower cmower = new Cmower("沉默王二");
System.out.println(cmower);

但是輸出的結果並不是他想要的:

com.cmower.java_demo.stackoverflow.printObject.Cmower@355da254

除此之外,他在列印陣列的時候也出現了相似的問題:

Cmower [] cmowers = {new Cmower("沉默王二"), new Cmower("沉默王三")};
System.out.println(cmowers);

輸出結果為:

[Lcom.cmower.java_demo.stackoverflow.printObject.Cmower;@4dc63996

Cmower@355da254[LCmower;@4dc63996 這樣的輸出結果代表著什麼意思呢?怎麼樣才能把 Cmower 類的 name 打印出來呢?以及如何列印一個物件的列表(陣列或者集合)呢?

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

01、究竟發生了什麼?

所有的 Java 物件都預設附帶了一個 toString() 的方法,當我們嘗試列印這個物件的時候,該方法就會被呼叫。

System.out.println(object);  // 呼叫 object.toString()

toString() 方法由 Object 類(所有 Java 物件的超類)定義,該方法會返回一個看起來晦澀難懂的字串:

1)Class 名,由包名和類名組成,比如 com.Cmower
2)@ 連線符;
3)十六進位制的雜湊碼。

來看一下該方法的原始碼:

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

陣列和普通的 Java 物件類似,只有一點點不同——追蹤 Class 類的 getName() 方法就可以印證這一點。

If this class object represents a class of arrays, then the internal form of the name consists of the name of the element type preceded by one or more '[' characters representing the depth of the array nesting.

大致的意思就是,如果是一個數組的話,Class 名的前面會有一個或者多個英文中括號“[”,表示陣列的維度(一維陣列為一個“[”,二維陣列為兩個“[”),然後再緊跟一個元素的型別首字母。

這就是為什麼物件陣列的字首是“[L”的原因。是不是有一種恍然大悟的感覺?

02、自定義輸出

如果想在列印的時候輸出自己預期的結果,就必須在自定義類中重寫 toString() 方法,來看例子。

public class Cmower {
    private String name;
    // 省略構造方法和 getter/setter

    @Override
    public String toString() {
        return name;
    }
}

當我們再次列印 Cmower 物件時,輸出結果就不再是 com.Cmower@355da254 了。

沉默王二

但是這樣的結果並不會令我們滿意,它有些突兀,沒法表示物件的型別。更優雅的做法是這樣的:

public class Cmower {
    private String name;
    // 省略構造方法和 getter/setter

    @Override
    public String toString() {
        return getClass().getSimpleName() + "[name=" + name + "]";
    }
}

再次列印 Cmower 物件,輸出結果為:

Cmower[name=沉默王二]

這樣的形式不僅看起來美觀,還能夠在除錯的時候給出有用的資訊。但是,有時候我們不想重寫 toString() 方法(想保留原有的列印格式 ClassType@123121),又想列印該物件的資訊,那麼最好定義一個新的方法,比如說 toMyString() 方法。

03、自動化輸出

IDE(Eclipse 或者 Intellj IDEA) 通常會提供一種針對類的欄位的輸出格式,用來覆蓋 toString() 方法。

@Override
public String toString() {
    return "Cmower{" +
            "name='" + name + '\'' +
            '}';
}

另外,一些開源的第三方類庫也會提供這樣的功能,比如說:

1)Apache Commons Lang 的 ToStringBuilder。

使用方法:

@Override
public String toString() {
    return ToStringBuilder.reflectionToString(this);
}

輸出結果:

com.cmower.printObject.Cmower@355da254[name=沉默王二]

2)Google Guava 的 MoreObjects

使用方法:

@Override
public String toString() {
    return MoreObjects.toStringHelper(this)
            .add("name", getName())
            .toString();
}

輸出結果:

Cmower{name=沉默王二}

3)Lombok 的 @toString 註解(IDE 需要先安裝 Lombok 的外掛)

使用方法:

@ToString
public class Cmower {

    private String name;

    // 省略構造方法和 getter/setter
}

只需要一個 @toString 註解,不需要覆蓋 toString() 方法。

輸出結果:

Cmower(name=沉默王二)

04、列印物件列表(陣列或者集合)

上述內容已經把列印單個物件的事情嘮明白了,are you ok?接下來,我們來說道說道列印物件列表的事兒。

1)陣列

Arrays.toString() 可以將任意型別的陣列轉成字串,包括基本型別陣列和引用型別陣列。程式碼示例如下。

Cmower[] cmowers = {new Cmower("沉默王二"), new Cmower("沉默王三")};
System.out.println(Arrays.toString(cmowers));

輸出結果:

[Cmower{name='沉默王二'}, Cmower{name='沉默王三'}]

2)集合

對於集合來說,可以直接列印就能輸出我們預期的結果。程式碼示例如下。

List<Cmower> list = new ArrayList<>();
list.add(new Cmower("沉默王二"));
list.add(new Cmower("沉默王三"));
System.out.println(list);

輸出結果:

[Cmower{name='沉默王二'}, Cmower{name='沉默王三'}]

05、鳴謝

好了,我親愛的讀者朋友,以上就是本文的全部內容了。能在疫情期間堅持看技術文,二哥必須要伸出大拇指為你點個贊