摘要:判斷另外一個(gè)對(duì)象是否與當(dāng)前對(duì)象相等返回當(dāng)前對(duì)象的哈希值返回一個(gè)表示當(dāng)前對(duì)象的字符串喚醒一個(gè)等待當(dāng)前對(duì)象的鎖監(jiān)視器的線(xiàn)程。
原文鏈接:http://www.javacodegeeks.com/2015/09/using-methods-common-to-all-objects.html
本文是Java進(jìn)階課程的第二篇。
本課程的目標(biāo)是幫你更有效的使用Java。其中討論了一些高級(jí)主題,包括對(duì)象的創(chuàng)建、并發(fā)、序列化、反射以及其他高級(jí)特性。本課程將為你的精通Java的旅程提供幫助。
內(nèi)容提綱引言
equals和hashCode方法
toString方法
clone方法
equals方法與"=="操作符
有用的幫助類(lèi)
源碼下載
下章概要
1. 引言從前面一篇對(duì)象的創(chuàng)建與銷(xiāo)毀中,我們知道Java是一種面向?qū)ο缶幊陶Z(yǔ)言(盡管不是純粹的面向?qū)ο?。Java類(lèi)層次結(jié)構(gòu)的頂層是Object類(lèi),所有的其他類(lèi)都隱式的繼承于它。因此,所有的類(lèi)也都從Object中繼承了方法,其中最重要的幾個(gè)方法如下表:
方法 | 描述 |
---|---|
protected Object clone() | 創(chuàng)建并返回當(dāng)前對(duì)象的一份拷貝 |
protected void finalize() | 當(dāng)垃圾回收器判斷出該對(duì)象不再被引用時(shí),就會(huì)調(diào)用finalize()方法。在對(duì)象的創(chuàng)建與銷(xiāo)毀中有對(duì)finalizers的介紹。 |
boolean equals(Object obj) | 判斷另外一個(gè)對(duì)象是否與當(dāng)前對(duì)象相等 |
int hasCode() | 返回當(dāng)前對(duì)象的哈希值 |
String toString() | 返回一個(gè)表示當(dāng)前對(duì)象的字符串 |
void notify() | 喚醒一個(gè)等待當(dāng)前對(duì)象的鎖監(jiān)視器的線(xiàn)程。我們將會(huì)在第9篇文章并發(fā)最佳實(shí)踐中詳細(xì)介紹此方法 |
void notifyAll() | 喚醒所有等待當(dāng)前對(duì)象的鎖監(jiān)視器的線(xiàn)程。我們將會(huì)在第9篇文章并發(fā)最佳實(shí)踐中詳細(xì)介紹此方法 |
void wait() void wait(long timeout) void wait(long timeout, int nanos) |
使當(dāng)前線(xiàn)程進(jìn)入等待狀態(tài)直到其他線(xiàn)程調(diào)用了當(dāng)前對(duì)象的notify()或notifyAll()方法。我們將會(huì)在第9篇文章并發(fā)最佳實(shí)踐中詳細(xì)介紹此方法 |
表1
在本篇文章中我們將重點(diǎn)介紹equals、hashCode、toString和clone方法。通過(guò)本章節(jié)的學(xué)習(xí),需要對(duì)這幾個(gè)方法的用法及重要的使用限制了然于胸。
2. equlas和hashCode方法默認(rèn)情況下,Java 中任何兩個(gè)對(duì)象引用(或類(lèi)實(shí)例引用)只有指向相同的內(nèi)存地址時(shí)才認(rèn)為是相等的(引用相等)。但是Java允許通過(guò)重載Object的equals()方法給類(lèi)自定義判等規(guī)則。聽(tīng)起來(lái)這是個(gè)很強(qiáng)大的概念,然而在適當(dāng)?shù)?b>equals()方法實(shí)現(xiàn)需要滿(mǎn)足以下幾個(gè)規(guī)則限制:
自反性:對(duì)象x必須與其自身相等,equals(x)返回true
對(duì)稱(chēng)性:如果equals(y)為true,則y.equals(x)也要返回true
傳遞性:如果equals(y)為true,并且y.equals(z)也為true,則x.equals(z)也要為true
一致性:多次調(diào)用equals()方法應(yīng)該返回相同值,除非對(duì)用于判等的任何一個(gè)屬性進(jìn)行了修改
與null判等:equals(null)總是要返回false
不幸的是Java編譯器并不會(huì)在編譯時(shí)對(duì)以上規(guī)則進(jìn)行檢查。然而,不遵守上述規(guī)則時(shí)可能會(huì)引入非常怪異并難以解決的問(wèn)題。通用的建議是:如果需要重寫(xiě)equals()方法,請(qǐng)至少思考兩次重寫(xiě)的必要性。遵循以上規(guī)則,我們?yōu)?b>Person類(lèi)重寫(xiě)一個(gè)簡(jiǎn)單的equals()實(shí)現(xiàn)。
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()方法并不是偶然的,至少要記住下面這條規(guī)則:任何時(shí)候重載equals()方法時(shí),需要一并重載hashCode()方法。如果兩個(gè)對(duì)象通過(guò)equals()方法判等時(shí)返回true,則每個(gè)對(duì)象的hashCode()方法需要返回相同的整數(shù)值(反過(guò)來(lái)并沒(méi)有限制:如果兩個(gè)對(duì)象通過(guò)equals()方法返回false,則hashCode()方法可以返回相同或不同的整數(shù)值)。下面看一下Person類(lèi)的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; }
為了避免得到不可預(yù)期的結(jié)果,盡可能在實(shí)現(xiàn)equals()和hashCode()方法時(shí)使用final字段,從而保證方法的結(jié)果不會(huì)受到字段變化的影響(盡管真實(shí)場(chǎng)景中未必發(fā)生)。
最后,要確保在實(shí)現(xiàn)equals()和hashCode()方法是使用相同的字段,以確保在不可預(yù)期的字段調(diào)整時(shí)保證這兩個(gè)方法行為的一致性。
3. toString方法toString()是最讓人感興趣的方法,并且被重載的頻率也更高。此方法的目的是提供對(duì)象(類(lèi)實(shí)例)的字符串表現(xiàn)。如果對(duì)toString()方法重載恰當(dāng),能極大的簡(jiǎn)化debug難度和分析解決問(wèn)題的過(guò)程。
默認(rèn)情況下,toString()的結(jié)果僅僅返回以@符分隔的全類(lèi)名與對(duì)象哈希值串,然而這個(gè)結(jié)果在大多場(chǎng)景下并沒(méi)什么用途。如下:
com.javacodegeeks.advanced.objects.Person@6104e2ee
我們來(lái)通過(guò)重寫(xiě)Person和toString()方法以使其輸出更有用,下面是其中一種實(shí)例:
// 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 ); }
現(xiàn)在我們?cè)?b>toString()方法中包含了Person的所有字段,然后執(zhí)行下面的代碼片段:
final Person person = new Person( "John", "Smith", "john.smith@domain.com" ); System.out.println( person.toString() );
控制臺(tái)中將輸出以下結(jié)果:
Person[email=john.smith@domain.com, first name=John, last name=Smith]
遺憾的是在Java標(biāo)準(zhǔn)庫(kù)中對(duì)toString()方法實(shí)現(xiàn)的支持有限,不過(guò)還是有幾個(gè)有用的方法:Objects.toString(), Arrays.toString() / Arrays.deepToString()。下面看一下Office類(lèi)以及其toString()的實(shí)現(xiàn)。
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; } }
相應(yīng)的控制臺(tái)輸出如下(同時(shí)也有Person實(shí)例的字符串值):
Office{persons=[Person[email=john.smith@domain.com, first name=John, last name=Smith]]}
Java社區(qū)實(shí)例了大量有用的類(lèi)庫(kù)以簡(jiǎn)化toString()的實(shí)現(xiàn)。其中廣泛使用的有Google Guava的Objects.toStringHelper和Apache Commons Lang的ToStringBuilder
4. clone方法如果舉出Java中最聲名狼藉的方法,當(dāng)屬clone()無(wú)疑。clone()方法的目的很簡(jiǎn)單——返回對(duì)象實(shí)例的拷貝,然而有一堆理由可證明其使用并不像聽(tīng)起來(lái)那么輕而易舉。
首先,實(shí)現(xiàn)自定義的clone()方法時(shí)需要遵守Java文檔)中列出的一系列約定。其次,在Object類(lèi)中clone()方法被聲明為protected,所以為了提高方法的可見(jiàn)性,在重載時(shí)需要聲明為public并把返回值類(lèi)型調(diào)整為重載類(lèi)自身類(lèi)型。再次,重載類(lèi)需要實(shí)現(xiàn)Cloneable接口(盡管該接口作為一種聲明,并未提供任何方法定義),否則將會(huì)拋出CloneNotSupportedException異常。最后,在實(shí)現(xiàn)clone()方法時(shí)要先調(diào)用super.clone()然后再執(zhí)行其他需要的動(dòng)作。下面看一下Person類(lèi)中的實(shí)現(xiàn):
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(); } }
上面的實(shí)現(xiàn)看起來(lái)簡(jiǎn)單直接,然而卻隱藏著錯(cuò)誤。當(dāng)類(lèi)實(shí)例的clone動(dòng)作被執(zhí)行時(shí),未調(diào)用任何構(gòu)造方法,后果將導(dǎo)致預(yù)料外的數(shù)據(jù)泄露。下面再看下Office類(lèi)中的定義:
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; } }
在這個(gè)實(shí)現(xiàn)中,Office實(shí)例克隆出來(lái)的所有對(duì)象都將共享相同的person數(shù)組,然而這并不是我們預(yù)期的行為。為了讓clone()實(shí)現(xiàn)正確的行為,我們還要做一些額外的工作:
@Override public Office clone() throws CloneNotSupportedException { final Office clone = ( Office )super.clone(); clone.persons = persons.clone(); return clone; }
看起來(lái)是正確了,但如果對(duì)persons字段聲明為final就將破壞這種正確性,因此final字段不能被重新賦值,從而導(dǎo)致數(shù)據(jù)再次被共享。
總之,當(dāng)需要類(lèi)實(shí)例的拷貝時(shí),盡可能避免使用clone() / Cloneable,相反可以選擇其他更簡(jiǎn)單的替代方案(例如:C++程序員熟悉的復(fù)制構(gòu)造方法,或者工廠(chǎng)方法——在對(duì)象的創(chuàng)建與銷(xiāo)毀中討論過(guò)的一種有用的構(gòu)造模式)。
5. equals方法與"=="操作符在Java中,==操作符與equals()方法有種奇怪的關(guān)系,卻會(huì)引入大量的問(wèn)題與困惑。大多數(shù)情況下(除比較基本數(shù)據(jù)類(lèi)型),==操作符執(zhí)行的是引用相等:只要兩個(gè)引用指向同一個(gè)對(duì)象時(shí)為true,否則返回false。下面舉例說(shuō)明二者的區(qū)別:
final String str1 = new String( "bbb" ); System.out.println( "Using == operator: " + ( str1 == "bbb" ) ); System.out.println( "Using equals() method: " + str1.equals( "bbb" ) );
從我們?nèi)祟?lèi)的視角來(lái)看,str1 == "bbb" 和 str1.equals("bbb")并無(wú)區(qū)別:str1僅僅是"bbb"的一個(gè)引用,所以結(jié)果應(yīng)該是相同的;但對(duì)于Java來(lái)說(shuō)卻不盡然:
Using == operator: false Using equals() method: true
盡管兩個(gè)字符串看起來(lái)完全一樣,但事實(shí)上卻是兩個(gè)不同的String實(shí)例。作為建議,在處理對(duì)象引用時(shí)要使用equals()或Objects.equals()進(jìn)行判等,除非你真的是要判斷兩個(gè)引用是否指向同一個(gè)實(shí)例。
6. 有用的幫助類(lèi)從Java 7發(fā)布以來(lái),一批有用的幫助類(lèi)加入到了標(biāo)準(zhǔn)Java庫(kù)中,Objects便是其中之一。具體來(lái)說(shuō),以下三個(gè)方法可以簡(jiǎn)化你的equals()和hashCode()方法實(shí)現(xiàn)。
方法 | 描述 |
---|---|
static boolean equals(Object a, Object b) | 當(dāng)參數(shù)中的兩個(gè)對(duì)象相等時(shí)返回true,否則返回false |
static int hash(Object...values) | 為參數(shù)列表生成哈希值 |
static int hashCode(Object o) | 為非null參數(shù)生成哈希值,如果參數(shù)為null返回0 |
如果使用上面的方法來(lái)重寫(xiě)Person的equals()和hashCode()實(shí)現(xiàn),代碼量將會(huì)大大縮減,同時(shí)代碼的可讀性也將大大增強(qiáng)。
@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. 下章概要在本章中,我們學(xué)習(xí)了作為Java面向?qū)ο蠡A(chǔ)的Object類(lèi),以及自定義的類(lèi)如何通過(guò)自己的判等規(guī)則重載Object的相關(guān)方法。下一章中,我們將會(huì)把視線(xiàn)暫時(shí)從代碼實(shí)現(xiàn)上收起,轉(zhuǎn)向去討論如何設(shè)計(jì)合適的類(lèi)和接口。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/65466.html
摘要:以實(shí)現(xiàn)自己熟悉的東西為導(dǎo)向比如我們做后端開(kāi)發(fā),首先是常用的循環(huán)迭代條件判斷增刪改成。它是由實(shí)現(xiàn)的,不保證元素的順序,也就是說(shuō)所說(shuō)元素插入的順序與輸出的順序不一致。 下面是我直播的文字版,直播地址:https://segmentfault.com/l/15...代碼:https://github.com/zhoumengka...整個(gè)項(xiàng)目我們我又細(xì)分了6個(gè)版本來(lái)演進(jìn),希望更加便于大家對(duì)比...
摘要:以實(shí)現(xiàn)自己熟悉的東西為導(dǎo)向比如我們做后端開(kāi)發(fā),首先是常用的循環(huán)迭代條件判斷增刪改成。它是由實(shí)現(xiàn)的,不保證元素的順序,也就是說(shuō)所說(shuō)元素插入的順序與輸出的順序不一致。 下面是我直播的文字版,直播地址:https://segmentfault.com/l/15...代碼:https://github.com/zhoumengka...整個(gè)項(xiàng)目我們我又細(xì)分了6個(gè)版本來(lái)演進(jìn),希望更加便于大家對(duì)比...
摘要:構(gòu)造方法是在對(duì)象實(shí)例初始化過(guò)程中具有舉足輕重的地位,并且提供了多種方式來(lái)定義構(gòu)造方法。在中創(chuàng)建對(duì)象的開(kāi)銷(xiāo)是相當(dāng)?shù)偷模⑶宜俣群芸臁?duì)象終結(jié)器前面我們講述的都是構(gòu)造方法和對(duì)象初始化相關(guān)的主題,但還未提及他們的反面對(duì)象銷(xiāo)毀。 原文鏈接:http://www.javacodegeeks.com/2015/09/how-to-create-and-destroy-objects.html 本文...
摘要:首當(dāng)其沖的便是接口中的每個(gè)聲明必須是即便不指定也是,并且不能設(shè)置為非,詳細(xì)規(guī)則可參考可見(jiàn)性部分介紹。函數(shù)式接口有著不同的場(chǎng)景,并被認(rèn)為是對(duì)編程語(yǔ)言的一種強(qiáng)大的擴(kuò)展。抽象類(lèi)與中的接口有些類(lèi)似,與中支持默認(rèn)方法的接口更為相像。 原文鏈接:http://www.javacodegeeks.com/2015/09/how-to-design-classes-and-interfaces.htm...
閱讀 2936·2021-11-16 11:55
閱讀 2702·2021-09-29 09:34
閱讀 3620·2021-09-01 14:21
閱讀 3874·2019-08-29 12:36
閱讀 748·2019-08-26 10:55
閱讀 4123·2019-08-26 10:20
閱讀 1092·2019-08-23 18:19
閱讀 1254·2019-08-23 17:56