如何優(yōu)雅地打印一個(gè)Java對(duì)象?
你好呀,我是沉默王二,一個(gè)和黃家駒一樣身高,和劉德華一樣顏值的程序員。雖然已經(jīng)寫了十多年的 Java 代碼,但仍然覺(jué)得自己是個(gè)菜鳥(niǎo)(請(qǐng)?jiān)试S我慚愧一下)。
在一個(gè)月黑風(fēng)高的夜晚,我思前想后,覺(jué)得再也不能這么蹉跎下去了。于是痛下決心,準(zhǔn)備通過(guò)輸出的方式倒逼輸入,以此來(lái)修煉自己的內(nèi)功,從而進(jìn)階成為一名真正意義上的大神。與此同時(shí),希望這些文章能夠幫助到更多的讀者,讓你在學(xué)習(xí)的路上不再寂寞、空虛和冷。
為了更好的輸入,我選擇 Stack Overflow 作為戰(zhàn)斗的第一線,畢竟很多前輩都在強(qiáng)烈推薦。本篇文章,我們來(lái)探討一下如何優(yōu)雅地打印一個(gè) Java 對(duì)象。
真沒(méi)想到,這個(gè)問(wèn)題的訪問(wèn)量像阿爾泰山一樣高,訪問(wèn)量足足有 29+ 萬(wàn)次,這不得了啊!說(shuō)明有很多很多的程序員被這個(gè)問(wèn)題困擾過(guò)。
來(lái)回顧一下提問(wèn)者的問(wèn)題吧。
提問(wèn)者定義了這樣一個(gè)類:
- 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;
- }
- }
然后創(chuàng)建了一個(gè)該類的對(duì)象,并嘗試打印它:
- Cmower cmower = new Cmower("沉默王二");
- System.out.println(cmower);
但是輸出的結(jié)果并不是他想要的:
- com.cmower.java_demo.stackoverflow.printObject.Cmower@355da254
除此之外,他在打印數(shù)組的時(shí)候也出現(xiàn)了相似的問(wèn)題:
- Cmower [] cmowers = {new Cmower("沉默王二"), new Cmower("沉默王三")};
- System.out.println(cmowers);
輸出結(jié)果為:
- [Lcom.cmower.java_demo.stackoverflow.printObject.Cmower;@4dc63996
Cmower@355da254 和 [LCmower;@4dc63996 這樣的輸出結(jié)果代表著什么意思呢?怎么樣才能把 Cmower 類的 name 打印出來(lái)呢?以及如何打印一個(gè)對(duì)象的列表(數(shù)組或者集合)呢?
如果大家也被這樣的問(wèn)題困擾過(guò),或者正在被困擾,就請(qǐng)隨我來(lái),咱們肩并肩手拉手一起梳理一下這個(gè)問(wèn)題,并找出優(yōu)秀答案。Duang、Duang、Duang,打怪進(jìn)階嘍!
01、究竟發(fā)生了什么?
所有的 Java 對(duì)象都默認(rèn)附帶了一個(gè) toString() 的方法,當(dāng)我們嘗試打印這個(gè)對(duì)象的時(shí)候,該方法就會(huì)被調(diào)用。
- System.out.println(object); // 調(diào)用 object.toString()
toString() 方法由 Object 類(所有 Java 對(duì)象的超類)定義,該方法會(huì)返回一個(gè)看起來(lái)晦澀難懂的字符串:
1)Class 名,由包名和類名組成,比如 com.Cmower;
2)@ 連接符;
3)十六進(jìn)制的哈希碼。
來(lái)看一下該方法的源碼:
- public String toString() {
- return getClass().getName() + "@" + Integer.toHexString(hashCode());
- }
數(shù)組和普通的 Java 對(duì)象類似,只有一點(diǎn)點(diǎn)不同——追蹤 Class 類的 getName()方法就可以印證這一點(diǎn)。
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.
大致的意思就是,如果是一個(gè)數(shù)組的話,Class 名的前面會(huì)有一個(gè)或者多個(gè)英文中括號(hào)“[”,表示數(shù)組的維度(一維數(shù)組為一個(gè)“[”,二維數(shù)組為兩個(gè)“[”),然后再緊跟一個(gè)元素的類型首字母。
這就是為什么對(duì)象數(shù)組的前綴是“[L”的原因。是不是有一種恍然大悟的感覺(jué)?
02、自定義輸出
如果想在打印的時(shí)候輸出自己預(yù)期的結(jié)果,就必須在自定義類中重寫 toString() 方法,來(lái)看例子。
- public class Cmower {
- private String name;
- // 省略構(gòu)造方法和 getter/setter
- @Override
- public String toString() {
- return name;
- }
- }
當(dāng)我們?cè)俅未蛴?Cmower 對(duì)象時(shí),輸出結(jié)果就不再是 com.Cmower@355da254 了。
- 沉默王二
但是這樣的結(jié)果并不會(huì)令我們滿意,它有些突兀,沒(méi)法表示對(duì)象的類型。更優(yōu)雅的做法是這樣的:
- public class Cmower {
- private String name;
- // 省略構(gòu)造方法和 getter/setter
- @Override
- public String toString() {
- return getClass().getSimpleName() + "[name=" + name + "]";
- }
- }
再次打印 Cmower 對(duì)象,輸出結(jié)果為:
- Cmower[name=沉默王二]
這樣的形式不僅看起來(lái)美觀,還能夠在調(diào)試的時(shí)候給出有用的信息。但是,有時(shí)候我們不想重寫 toString() 方法(想保留原有的打印格式 ClassType@123121),又想打印該對(duì)象的信息,那么最好定義一個(gè)新的方法,比如說(shuō) toMyString() 方法。
03、自動(dòng)化輸出
IDE(Eclipse 或者 Intellj IDEA) 通常會(huì)提供一種針對(duì)類的字段的輸出格式,用來(lái)覆蓋 toString() 方法。
- @Override
- public String toString() {
- return "Cmower{" +
- "name='" + name + '\'' +
- '}';
- }
另外,一些開(kāi)源的第三方類庫(kù)也會(huì)提供這樣的功能,比如說(shuō):
1)Apache Commons Lang 的 ToStringBuilder。
使用方法:
- @Override
- public String toString() {
- return ToStringBuilder.reflectionToString(this);
- }
輸出結(jié)果:
- com.cmower.printObject.Cmower@355da254[name=沉默王二]
2)Google Guava 的 MoreObjects
使用方法:
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(this)
- .add("name", getName())
- .toString();
- }
輸出結(jié)果:
- Cmower{name=沉默王二}
3)Lombok 的 @toString 注解(IDE 需要先安裝 Lombok 的插件)
使用方法:
- @ToString
- public class Cmower {
- private String name;
- // 省略構(gòu)造方法和 getter/setter
- }
只需要一個(gè) @toString 注解,不需要覆蓋 toString() 方法。
輸出結(jié)果:
- Cmower(name=沉默王二)
04、打印對(duì)象列表(數(shù)組或者集合)
上述內(nèi)容已經(jīng)把打印單個(gè)對(duì)象的事情嘮明白了,are you ok?接下來(lái),我們來(lái)說(shuō)道說(shuō)道打印對(duì)象列表的事兒。
1)數(shù)組
Arrays.toString() 可以將任意類型的數(shù)組轉(zhuǎn)成字符串,包括基本類型數(shù)組和引用類型數(shù)組。代碼示例如下。
- Cmower[] cmowers = {new Cmower("沉默王二"), new Cmower("沉默王三")};
- System.out.println(Arrays.toString(cmowers));
輸出結(jié)果:
- [Cmower{name='沉默王二'}, Cmower{name='沉默王三'}]
2)集合
對(duì)于集合來(lái)說(shuō),可以直接打印就能輸出我們預(yù)期的結(jié)果。代碼示例如下。
- List<Cmower> list = new ArrayList<>();
- list.add(new Cmower("沉默王二"));
- list.add(new Cmower("沉默王三"));
- System.out.println(list);
輸出結(jié)果:
- [Cmower{name='沉默王二'}, Cmower{name='沉默王三'}]
05、鳴謝
好了,我親愛(ài)的讀者朋友,以上就是本文的全部?jī)?nèi)容了。能在疫情期間堅(jiān)持看技術(shù)文,二哥必須要伸出大拇指為你點(diǎn)個(gè)贊。































