摘要:判斷另外一個對象是否與當前對象相等返回當前對象的哈希值返回一個表示當前對象的字符串喚醒一個等待當前對象的鎖監視器的線程。
原文鏈接:http://www.javacodegeeks.com/2015/09/using-methods-common-to-all-objects.html
本文是Java進階課程的第二篇。
本課程的目標是幫你更有效的使用Java。其中討論了一些高級主題,包括對象的創建、并發、序列化、反射以及其他高級特性。本課程將為你的精通Java的旅程提供幫助。
內容提綱引言
equals和hashCode方法
toString方法
clone方法
equals方法與"=="操作符
有用的幫助類
源碼下載
下章概要
1. 引言從前面一篇對象的創建與銷毀中,我們知道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方法。通過本章節的學習,需要對這幾個方法的用法及重要的使用限制了然于胸。
2. equlas和hashCode方法默認情況下,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()方法是使用相同的字段,以確保在不可預期的字段調整時保證這兩個方法行為的一致性。
3. toString方法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
4. clone方法如果舉出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++程序員熟悉的復制構造方法,或者工廠方法——在對象的創建與銷毀中討論過的一種有用的構造模式)。
5. equals方法與"=="操作符在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()進行判等,除非你真的是要判斷兩個引用是否指向同一個實例。
6. 有用的幫助類從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 ); }7. 源碼下載
可以從這里下載本文中的源碼:advanced-java-part-2
8. 下章概要在本章中,我們學習了作為Java面向對象基礎的Object類,以及自定義的類如何通過自己的判等規則重載Object的相關方法。下一章中,我們將會把視線暫時從代碼實現上收起,轉向去討論如何設計合適的類和接口。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/65466.html
摘要:以實現自己熟悉的東西為導向比如我們做后端開發,首先是常用的循環迭代條件判斷增刪改成。它是由實現的,不保證元素的順序,也就是說所說元素插入的順序與輸出的順序不一致。 下面是我直播的文字版,直播地址:https://segmentfault.com/l/15...代碼:https://github.com/zhoumengka...整個項目我們我又細分了6個版本來演進,希望更加便于大家對比...
摘要:以實現自己熟悉的東西為導向比如我們做后端開發,首先是常用的循環迭代條件判斷增刪改成。它是由實現的,不保證元素的順序,也就是說所說元素插入的順序與輸出的順序不一致。 下面是我直播的文字版,直播地址:https://segmentfault.com/l/15...代碼:https://github.com/zhoumengka...整個項目我們我又細分了6個版本來演進,希望更加便于大家對比...
摘要:構造方法是在對象實例初始化過程中具有舉足輕重的地位,并且提供了多種方式來定義構造方法。在中創建對象的開銷是相當低的,并且速度很快。對象終結器前面我們講述的都是構造方法和對象初始化相關的主題,但還未提及他們的反面對象銷毀。 原文鏈接:http://www.javacodegeeks.com/2015/09/how-to-create-and-destroy-objects.html 本文...
摘要:首當其沖的便是接口中的每個聲明必須是即便不指定也是,并且不能設置為非,詳細規則可參考可見性部分介紹。函數式接口有著不同的場景,并被認為是對編程語言的一種強大的擴展。抽象類與中的接口有些類似,與中支持默認方法的接口更為相像。 原文鏈接:http://www.javacodegeeks.com/2015/09/how-to-design-classes-and-interfaces.htm...
閱讀 2864·2021-11-16 11:55
閱讀 2608·2021-09-29 09:34
閱讀 3405·2021-09-01 14:21
閱讀 3753·2019-08-29 12:36
閱讀 697·2019-08-26 10:55
閱讀 3959·2019-08-26 10:20
閱讀 1026·2019-08-23 18:19
閱讀 1194·2019-08-23 17:56