原文連接:http://www.javacodegeeks.com/2015/09/using-methods-common-to-all-objects.htmlhtml
本文是Java進階課程的第二篇。java
本課程的目標是幫你更有效的使用Java。其中討論了一些高級主題,包括對象的建立、併發、序列化、反射以及其餘高級特性。本課程將爲你的精通Java的旅程提供幫助。git
引言程序員
equals和hashCode方法apache
clone方法segmentfault
有用的幫助類數組
源碼下載併發
從前面一篇對象的建立與銷燬中,咱們知道Java是一種面向對象編程語言(儘管不是純粹的面向對象)。Java類層次結構的頂層是Object
類,全部的其餘類都隱式的繼承於它。所以,全部的類也都從Object
中繼承了方法,其中最重要的幾個方法以下表:
方法 | 描述 |
---|---|
protected Object clone() |
建立並返回當前對象的一份拷貝 |
protected void finalize() |
當垃圾回收器判斷出該對象再也不被引用時,就會調用finalize()方法。在對象的建立與銷燬中有對finalizers的介紹。 |
boolean equals(Object obj) |
判斷另一個對象是否與當前對象相等 |
int hasCode() |
返回當前對象的哈希值 |
String toString() |
返回一個表示當前對象的字符串 |
void notify() |
喚醒一個等待當前對象的鎖監視器的線程。咱們將會在第9篇文章併發最佳實踐中詳細介紹此方法 |
void notifyAll() |
喚醒全部等待當前對象的鎖監視器的線程。咱們將會在第9篇文章併發最佳實踐中詳細介紹此方法 |
void wait() void wait(long timeout) void wait(long timeout, int nanos) |
使當前線程進入等待狀態直到其餘線程調用了當前對象的notify() 或notifyAll() 方法。咱們將會在第9篇文章併發最佳實踐中詳細介紹此方法 |
表1
在本篇文章中咱們將重點介紹equals
、hashCode
、toString
和clone
方法。經過本章節的學習,須要對這幾個方法的用法及重要的使用限制瞭然於胸。
默認狀況下,Java 中任何兩個對象引用(或類實例引用)只有指向相同的內存地址時才認爲是相等的(引用相等)。可是Java容許經過重載Object
的equals()
方法給類自定義判等規則。聽起來這是個很強大的概念,然而在適當的equals()
方法實現須要知足如下幾個規則限制:
自反性:對象x必須與其自身相等,equals(x)返回true
對稱性:若是equals(y)爲true,則y.equals(x)也要返回true
傳遞性:若是equals(y)爲true,而且y.equals(z)也爲true,則x.equals(z)也要爲true
一致性:屢次調用equals()方法應該返回相同值,除非對用於判等的任何一個屬性進行了修改
與null判等:equals(null)老是要返回false
不幸的是Java編譯器並不會在編譯時對以上規則進行檢查。然而,不遵照上述規則時可能會引入很是怪異並難以解決的問題。通用的建議是:若是須要重寫equals()
方法,請至少思考兩次重寫的必要性。遵循以上規則,咱們爲Person
類重寫一個簡單的equals()
實現。
package com.javacodegeeks.advanced.objects; public class Person { private final String firstName; private final String lastName; private final String email; public Person( final String firstName, final String lastName, final String email ) { this.firstName = firstName; this.lastName = lastName; this.email = email; } public String getEmail() { return email; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } // Step 0: Please add the @Override annotation, it will ensure that your // intention is to change the default implementation. @Override public boolean equals( Object obj ) { // Step 1: Check if the 'obj' is null if ( obj == null ) { return false; } // Step 2: Check if the 'obj' is pointing to the this instance if ( this == obj ) { return true; } // Step 3: Check classes equality. Note of caution here: please do not use the // 'instanceof' operator unless class is declared as final. It may cause // an issues within class hierarchies. if ( getClass() != obj.getClass() ) { return false; } // Step 4: Check individual fields equality final Person other = (Person) obj; if ( email == null ) { if ( other.email != null ) { return false; } } else if( !email.equals( other.email ) ) { return false; } if ( firstName == null ) { if ( other.firstName != null ) { return false; } } else if ( !firstName.equals( other.firstName ) ) { return false; } if ( lastName == null ) { if ( other.lastName != null ) { return false; } } else if ( !lastName.equals( other.lastName ) ) { return false; } return true; } }
在此部分介紹hashCode()
方法並非偶然的,至少要記住下面這條規則:任什麼時候候重載equals()
方法時,須要一併重載hashCode()
方法。若是兩個對象經過equals()
方法判等時返回true,則每一個對象的hashCode()
方法須要返回相同的整數值(反過來並無限制:若是兩個對象經過equals()
方法返回false,則hashCode()
方法能夠返回相同或不一樣的整數值)。下面看一下Person
類的hashCode()
方法:
// Please add the @Override annotation, it will ensure that your // intention is to change the default implementation. @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ( ( email == null ) ? 0 : email.hashCode() ); result = prime * result + ( ( firstName == null ) ? 0 : firstName.hashCode() ); result = prime * result + ( ( lastName == null ) ? 0 : lastName.hashCode() ); return result; }
爲了不獲得不可預期的結果,儘量在實現equals()
和hashCode()
方法時使用final
字段,從而保證方法的結果不會受到字段變化的影響(儘管真實場景中未必發生)。
最後,要確保在實現equals()
和hashCode()
方法是使用相同的字段,以確保在不可預期的字段調整時保證這兩個方法行爲的一致性。
toString()
是最讓人感興趣的方法,而且被重載的頻率也更高。此方法的目的是提供對象(類實例)的字符串表現。若是對toString()
方法重載恰當,能極大的簡化debug難度和分析解決問題的過程。
默認狀況下,toString()
的結果僅僅返回以@
符分隔的全類名與對象哈希值串,然而這個結果在大多場景下並沒什麼用途。以下:
com.javacodegeeks.advanced.objects.Person@6104e2ee
咱們來經過重寫Person和toString()
方法以使其輸出更有用,下面是其中一種實例:
// Please add the @Override annotation, it will ensure that your // intention is to change the default implementation. @Override public String toString() { return String.format( "%s[email=%s, first name=%s, last name=%s]", getClass().getSimpleName(), email, firstName, lastName ); }
如今咱們在toString()
方法中包含了Person的全部字段,而後執行下面的代碼片斷:
final Person person = new Person( "John", "Smith", "john.smith@domain.com" ); System.out.println( person.toString() );
控制檯中將輸出如下結果:
Person[email=john.smith@domain.com, first name=John, last name=Smith]
遺憾的是在Java標準庫中對toString()
方法實現的支持有限,不過仍是有幾個有用的方法:Objects.toString()
, Arrays.toString() / Arrays.deepToString()
。下面看一下Office
類以及其toString()
的實現。
package com.javacodegeeks.advanced.objects; import java.util.Arrays; public class Office { private Person[] persons; public Office( Person ... persons ) { this.persons = Arrays.copyOf( persons, persons.length ); } @Override public String toString() { return String.format( "%s{persons=%s}", getClass().getSimpleName(), Arrays.toString( persons ) ); } public Person[] getPersons() { return persons; } }
相應的控制檯輸出以下(同時也有Person
實例的字符串值):
Office{persons=[Person[email=john.smith@domain.com, first name=John, last name=Smith]]}
Java社區實例了大量有用的類庫以簡化toString()
的實現。其中普遍使用的有Google Guava
的Objects.toStringHelper和Apache Commons Lang
的ToStringBuilder
若是舉出Java中最聲名狼藉的方法,當屬clone()
無疑。clone()
方法的目的很簡單——返回對象實例的拷貝,然而有一堆理由可證實其使用並不像聽起來那麼垂手可得。
首先,實現自定義的clone()
方法時須要遵照Java文檔)中列出的一系列約定。其次,在Object
類中clone()
方法被聲明爲protected
,因此爲了提升方法的可見性,在重載時須要聲明爲public
並把返回值類型調整爲重載類自身類型。再次,重載類須要實現Cloneable
接口(儘管該接口做爲一種聲明,並未提供任何方法定義),不然將會拋出CloneNotSupportedException
異常。最後,在實現clone()
方法時要先調用super.clone()
而後再執行其餘須要的動做。下面看一下Person
類中的實現:
public class Person implements Cloneable { // Please add the @Override annotation, it will ensure that your // intention is to change the default implementation. @Override public Person clone() throws CloneNotSupportedException { return ( Person )super.clone(); } }
上面的實現看起來簡單直接,然而卻隱藏着錯誤。當類實例的clone動做被執行時,未調用任何構造方法,後果將致使預料外的數據泄露。下面再看下Office
類中的定義:
package com.javacodegeeks.advanced.objects; import java.util.Arrays; public class Office implements Cloneable { private Person[] persons; public Office( Person ... persons ) { this.persons = Arrays.copyOf( persons, persons.length ); } @Override public Office clone() throws CloneNotSupportedException { return ( Office )super.clone(); } public Person[] getPersons() { return persons; } }
在這個實現中,Office
實例克隆出來的全部對象都將共享相同的person數組,然而這並非咱們預期的行爲。爲了讓clone()
實現正確的行爲,咱們還要作一些額外的工做:
@Override public Office clone() throws CloneNotSupportedException { final Office clone = ( Office )super.clone(); clone.persons = persons.clone(); return clone; }
看起來是正確了,但若是對persons字段聲明爲final
就將破壞這種正確性,所以final
字段不能被從新賦值,從而致使數據再次被共享。
總之,當須要類實例的拷貝時,儘量避免使用clone()
/ Cloneable
,相反能夠選擇其餘更簡單的替代方案(例如:C++程序員熟悉的複製構造方法,或者工廠方法——在對象的建立與銷燬中討論過的一種有用的構造模式)。
在Java中,==
操做符與equals()方法有種奇怪的關係,卻會引入大量的問題與困惑。大多數狀況下(除比較基本數據類型),==操做符執行的是引用相等:只要兩個引用指向同一個對象時爲true,不然返回false。下面舉例說明兩者的區別:
final String str1 = new String( "bbb" ); System.out.println( "Using == operator: " + ( str1 == "bbb" ) ); System.out.println( "Using equals() method: " + str1.equals( "bbb" ) );
從咱們人類的視角來看,str1 == "bbb" 和 str1.equals("bbb")並沒有區別:str1僅僅是"bbb"的一個引用,因此結果應該是相同的;但對於Java來講卻不盡然:
Using == operator: false Using equals() method: true
儘管兩個字符串看起來徹底同樣,但事實上倒是兩個不一樣的String
實例。做爲建議,在處理對象引用時要使用equals()
或Objects.equals()
進行判等,除非你真的是要判斷兩個引用是否指向同一個實例。
從Java 7發佈以來,一批有用的幫助類加入到了標準Java庫中,Objects
即是其中之一。具體來講,如下三個方法能夠簡化你的equals()
和hashCode()
方法實現。
方法 | 描述 |
---|---|
static boolean equals(Object a, Object b) |
當參數中的兩個對象相等時返回true,不然返回false |
static int hash(Object...values) |
爲參數列表生成哈希值 |
static int hashCode(Object o) |
爲非null參數生成哈希值,若是參數爲null返回0 |
若是使用上面的方法來重寫Person
的equals()
和hashCode()
實現,代碼量將會大大縮減,同時代碼的可讀性也將大大加強。
@Override public boolean equals( Object obj ) { if ( obj == null ) { return false; } if ( this == obj ) { return true; } if ( getClass() != obj.getClass() ) { return false; } final PersonObjects other = (PersonObjects) obj; if( !Objects.equals( email, other.email ) ) { return false; } else if( !Objects.equals( firstName, other.firstName ) ) { return false; } else if( !Objects.equals( lastName, other.lastName ) ) { return false; } return true; } @Override public int hashCode() { return Objects.hash( email, firstName, lastName ); }
能夠從這裏下載本文中的源碼:advanced-java-part-2
在本章中,咱們學習了做爲Java面向對象基礎的Object
類,以及自定義的類如何經過本身的判等規則重載Object
的相關方法。下一章中,咱們將會把視線暫時從代碼實現上收起,轉向去討論如何設計合適的類和接口。