認識Object中的幾個經常需要覆蓋的方法——toString方法
學習Java少不了對Object的認知,所有類都會繼承它的屬性,真正的超類。這一個系列,我會對Object中的幾個方法,也就是我們自定義類的時候需要重寫的幾個方法做一個介紹。下面是這一個系列的主要內容:
- toString方法
- clone方法
- 自定義類時考慮實現Comparable介面
本系列內容源於對《Effective Java》中文第二版第8條到第12條的學習記錄。所有內容的準確性均以原書為準。
1,引言
toString方法在平時是我們用的相對來說最多的,所以它的意義也更加重要,雖然重寫它的時候沒有約束,但是我們應該利用它達到我們想要的目的,下面就來看一個官方API對其的介紹和建議:
public String toString()
返回該物件的字串表示。通常,toString
方法會返回一個“以文字方式表示”此物件的字串。結果應是一個簡明但易於讀懂的資訊表示式。建議所有子類都重寫此方法。
Object
類的 toString
方法返回一個字串,該字串由類名(物件是該類的一個例項)、at 標記符“@
”和此物件雜湊碼的無符號十六進位制表示組成。換句話說,該方法返回一個字串,它的值等於:
getClass().getName() + '@' + Integer.toHexString(hashCode())
返回:
該物件的字串表示形式。
從官方的介紹我們可以總結出兩點:
- 建議所有子類重寫該方法
- 預設的格式是類名+“@”+雜湊碼值的無符號16進製表示
2,分析
我們下面就對上述的兩條進行驗證,看看對於Java平臺類庫中的類是不是都重寫toString方法,如果沒有,是不是符合第二條的組成規則。
- 驗證沒有重寫的類
我在API中隨便找了一個類:
public final class Parameter
org/omg/Dynamic/Parameter.java。由 "3.2" 版的 IDL-to-Java 編譯器(可移植)從 ../../../../src/share/classes/org/omg/PortableInterceptor/Interceptors.idl 生成,生成時間為 2006 年 6 月 30 日,星期五上午 12:40:09 (GMT-08:00)。
它就沒有重寫toString方法,測試如下:
可見並不是所有java平臺類庫中的所有類都重寫了toSTring,但是不可否認我們經常使用的比如String,檔案相關API,集合相關API等等都是重寫了的
- 驗證Object中的toString方法預設輸出組成
這裡為了驗證,我建立了兩個Object物件,測試結果如下:
於是我們還會有下面兩個猜測:
- 如果我們自己重寫了hashCode方法還會保持上面的規律嗎?
- 如果想保持一致該腫麼辦?
我們使用如下的Person類測試第一個猜測:
package hfut.edu;
/** * Date:2018年10月1日 上午11:05:45 Author:why */
public class Person {
private int hash=1; int age; String name; String sex;
public Person(int age, String name, String sex) { super(); this.age = age; this.name = name; this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getSex() { return sex; }
public void setSex(String sex) { this.sex = sex; }
@Override public boolean equals(Object obj) { // TODO Auto-generated method stub
if (!(obj instanceof Person)) return false;
Person p = (Person) obj; return this.age == p.age && this.name.equals(p.name) && this.sex.equals(p.sex);
}
@Override public int hashCode() { // TODO Auto-generated method stub hash=hash*3+this.age; hash=hash*3+this.name.hashCode(); hash=hash*3+this.sex.hashCode(); return hash; } }
測試結果如下:
到這裡我們就驗證了第一條是否定的。但是我覺得還是有點問題,因為我們觀察Object類中的toString方法:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
我們想了一下,它呼叫的就是我們的hashCode方法轉為16進位制字串顯示,為什麼我們轉回10進位制就不對了,這裡有一個問題就是我們呼叫了兩次hashCode方法,第一次呼叫的時候就修改了物件的hash引數的值,第二次是在此基礎上進行運算的,所以出現了上述的錯誤,我在上一篇部落格中也出現了這樣的錯誤(現在已修改),所以,Person類的hashCode方法應該這樣定義:
@Override public int hashCode() { // TODO Auto-generated method stub int hash=1; hash = hash * 3 + this.age; hash = hash * 3 + this.name.hashCode(); hash = hash * 3 + this.sex.hashCode(); return hash; }
同時,刪除Person類中的hash成員變數即可,這個時候我們再次測試:
現在就沒有任何問題了。
上面也說了,重寫toString沒有什麼約束條約,所以可以隨便定義,但是我們必須要明確其用途和意義,這裡,我們就可以效仿Java平臺類庫了,比如File:
/** * Returns the pathname string of this abstract pathname. This is just the * string returned by the <code>{@link #getPath}</code> method. * * @return The string form of this abstract pathname */ public String toString() { return getPath(); }
比如Calender:
/** * Return a string representation of this calendar. This method * is intended to be used only for debugging purposes, and the * format of the returned string may vary between implementations. * The returned string may be empty but may not be <code>null</code>. * * @return a string representation of this calendar. */ @Override public String toString() { // NOTE: BuddhistCalendar.toString() interprets the string // produced by this method so that the Gregorian year number // is substituted by its B.E. year value. It relies on // "...,YEAR=<year>,..." or "...,YEAR=?,...". StringBuilder buffer = new StringBuilder(800); buffer.append(getClass().getName()).append('['); appendValue(buffer, "time", isTimeSet, time); buffer.append(",areFieldsSet=").append(areFieldsSet); buffer.append(",areAllFieldsSet=").append(areAllFieldsSet); buffer.append(",lenient=").append(lenient); buffer.append(",zone=").append(zone); appendValue(buffer, ",firstDayOfWeek", true, (long) firstDayOfWeek); appendValue(buffer, ",minimalDaysInFirstWeek", true, (long) minimalDaysInFirstWeek); for (int i = 0; i < FIELD_COUNT; ++i) { buffer.append(','); appendValue(buffer, FIELD_NAME[i], isSet(i), (long) fields[i]); } buffer.append(']'); return buffer.toString(); }
可見只要新增好toString方法備註,無論是繼承還是擴充套件都明白你輸出的內容即可。到這裡,關於toString的介紹就結束了。