Java 是一門典型的面向?qū)ο笳Z(yǔ)言,提供 extends
關(guān)鍵字使子類繼承父類咒劲。
public class Student extends Person {
...
}
但是創(chuàng)建 Person
類時(shí)不用使用 extends
繼承 Object
類继薛。
public class Person extends Object {
...
}
因?yàn)椋瑒?chuàng)建的類沒(méi)有明確指明繼承關(guān)系時(shí)爽待,會(huì)在編譯時(shí)自動(dòng)繼承 Object
類损同。可以使用 Object
類型的變量引用任何類型的實(shí)例:
Object sample = new Student(1001, "小咖", 20, "男");
也可以將任意的 Object
實(shí)例轉(zhuǎn)換為需要的類型:
Person p = (Person) sample;
上面兩個(gè)例子在編譯器中是不會(huì)報(bào)錯(cuò)的鸟款。因此膏燃,Object
類是 Person
類的父類,每個(gè)類都是由 Object
擴(kuò)展而來(lái)的何什。所以组哩,熟悉 Object
類中提供的服務(wù)是十分重要的专普。
Java 中的基本類型不是對(duì)象锚沸,但也提供了相應(yīng)的包裝類型,如 int
基本類型對(duì)應(yīng) Integer
包裝類型。但使用基本類型創(chuàng)建的數(shù)組是擴(kuò)展自 Object
類散庶,實(shí)際上是引用。
Object sample = new int[10];
其實(shí)仪际,所有的數(shù)組類型都是擴(kuò)展了 Object
類咸作。
下表為 Object
類的通用方法。
方法 | 描述 | 異常 |
---|---|---|
final native Class<?> getClass() |
返回對(duì)象的運(yùn)行時(shí)類 | 無(wú) |
native int hashCode() |
返回對(duì)象的散列碼 | 無(wú) |
boolean equals(Object obj) |
與其它對(duì)象是否相等 | 無(wú) |
native Object clone() |
克隆并返回對(duì)象的副本 | CloneNotSupportedException |
String toString() |
返回對(duì)象的字符串表示 | 無(wú) |
final native void notify() |
喚醒正在等待對(duì)象監(jiān)聽(tīng)器上的一個(gè)線程 | 無(wú) |
final native void notifyAll() |
喚醒正在等待對(duì)象監(jiān)聽(tīng)器上的所有線程 | 無(wú) |
final native void wait() |
導(dǎo)致當(dāng)前線程等待们豌,直到另一個(gè)線程調(diào)用此對(duì)象的notify() 或notifyAll()
|
InterruptedException |
final native void wait(long timeout) |
導(dǎo)致當(dāng)前線程等待涯捻,直到另一個(gè)線程調(diào)用此對(duì)象的notify() 或notifyAll() ,或者指定時(shí)間已到 |
InterruptedException |
final void wait(long timeout, int nanos) |
導(dǎo)致當(dāng)前線程等待望迎,直到另一個(gè)線程調(diào)用此對(duì)象的notify() 或notifyAll() 障癌,或者指定時(shí)間已到 |
InterruptedException |
void finalize() |
當(dāng)GC確定不再有對(duì)該對(duì)象的引用時(shí),由對(duì)象的 GC 調(diào)用此方法 | Throwable |
equals()
Object
類提供了 equals()
方法用于檢測(cè)對(duì)象是否相等辩尊。其實(shí)現(xiàn)相等性要滿足五個(gè)條件:
- 自反性涛浙。 對(duì)于任何的非空引用都需滿足
x.equals(x) == true
。 - 對(duì)稱性摄欲。 對(duì)于任何引用都滿足
x.equals(y) == y.equals(x)
轿亮。 - 傳遞性。 對(duì)于任何引用都滿足
(x.equals(y) && y.equals(z)) == x.equals(z)
胸墙。 - 一致性我注。 對(duì)于不發(fā)生任何變化的引用都滿足
x.equals(y) == x.equals(y)
,多次調(diào)用equals()
方法結(jié)果不變迟隅。 - 與
null
的比較但骨。x.equals(null)
返回false
。對(duì)任何不是null
的對(duì)象調(diào)用equals()
方法與null
比較的結(jié)果都為false
智袭。
引用類型最好使用 equals()
方法比較奔缠;而基本類型使用 ==
比較。
其中补履,子類中定義 equals()
方法時(shí)添坊,需先比較超類的 equals()
。如果檢測(cè)失敗箫锤,對(duì)象就不可能相等贬蛙。如果超類中的域都相等,就需要比較子類中的實(shí)例域谚攒。
下面可以從兩個(gè)截然不同的情況看一下這個(gè)問(wèn)題:
- 如果子類能夠擁有自己的相等概念阳准,則對(duì)稱性需求將強(qiáng)制采用 getClass 進(jìn)行檢測(cè)。
- 如果由超類決定相等的概念馏臭,那么就可以使用 instanceof 進(jìn)行檢測(cè)野蝇,這樣可以在不同子類的對(duì)象之間進(jìn)行相同的比較讼稚。
對(duì)象中使用 equals()
方法比較需要實(shí)現(xiàn)的步驟如下:
- 檢查是否為同一個(gè)引用,如果是直接返回
true
绕沈。 - 檢測(cè)傳入的值是否為
null
锐想,如果是直接返回false
。 - 檢測(cè)是否屬于同一個(gè)類型乍狐。如果不是直接返回
false
赠摇。 - 將
Object
對(duì)象類型轉(zhuǎn)為要比較的類型。 - 比較每個(gè)關(guān)鍵域是否相等浅蚪。
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name)
&& Objects.equals(sex, person.sex)
&& (age != person.age);
}
如果子類重新定義 equals
藕帜,就要在其中包含 super.equals(other)
。
hasCode()
hashCode()
返回的是整數(shù)值惜傲,是無(wú)規(guī)律的散列碼洽故。hashCode()
定義在了 Object
類中,每個(gè)類都可以使用 hashCode()
方法調(diào)用自身散列碼盗誊,其值為對(duì)象的存儲(chǔ)地址时甚。
Object sample = new Student(1001, "小咖", 20, "男");
System.out.println(sample); // [I@5b464ce8
如果你在創(chuàng)建的類中覆蓋了 equals()
方法,就必須覆蓋 hashCode()
方法 哈踱。這是 hashCode()
的通用約定撞秋。下面是覆蓋 hashCode()
方法的約定:
- 程序執(zhí)行期間,對(duì)象的
equals()
方法中比較的信息不變嚣鄙,同一個(gè)對(duì)象的hashCode()
方法的返回值也不變。兩個(gè)程序的執(zhí)行期間串结,hashCode()
方法返回的值可以不一致哑子。 - 如果兩個(gè)對(duì)象根據(jù)
equals(Object)
方法比較相等,那hashCode()
方法的返回值也必須相等肌割。 - 如果兩個(gè)對(duì)象根據(jù)
equals(Object)
方法比較不相等卧蜓,那hashCode()
方法的返回值最好不相等。如果相等把敞,會(huì)在使用Map
時(shí)造成散列碼沖突弥奸。
總結(jié)就是兩個(gè)對(duì)象相等,其散列碼一定相同奋早;但是散列碼相同的兩個(gè)對(duì)象并不一定相等盛霎。因?yàn)橛?jì)算散列碼具有隨機(jī)性,兩個(gè)值不同的對(duì)象可能計(jì)算出相同的散列碼耽装。
理想的散列函數(shù)是把集合中不相等的實(shí)例均勻地分布到所有可能的 int
之上愤炸。但非常困難,只能實(shí)現(xiàn)相對(duì)接近這種理想的情形掉奄。
當(dāng)計(jì)算散列碼時(shí)规个,要將每個(gè)域都考慮進(jìn)去。可以將每個(gè)域都當(dāng)成 R 進(jìn)制的某一位诞仓,然后組成一個(gè) R 進(jìn)制的整數(shù)缤苫。
R 一般取奇數(shù) 31,偶數(shù)會(huì)出現(xiàn)乘法溢出墅拭,信息會(huì)丟失活玲。因?yàn)榕c 2 相乘相當(dāng)于向左移一位,最左邊的位丟失帜矾。并且一個(gè)數(shù)與 31 相乘可以轉(zhuǎn)換成移位和減法:31*x == (x<<5)-x
翼虫,編譯器會(huì)自動(dòng)進(jìn)行這個(gè)優(yōu)化。
如下是一個(gè)如何簡(jiǎn)單的計(jì)算散列碼的參考:
- 基本類型調(diào)用
Type.hashCode(value)
來(lái)生成屡萤。Type
類型為基本類型各自的包裝類珍剑。 - 如果是引用類型,并且需要覆蓋
equals()
方法死陆,equals()
方法使用哪些域比較招拙,hashCode()
方法也會(huì)遞歸地調(diào)用這些域的散列碼并計(jì)算。 - 如果是數(shù)組類型措译,那數(shù)組中的每個(gè)值都當(dāng)做單獨(dú)的域來(lái)處理别凤。也可以使用
Arrays.hashCode()
方法計(jì)算。
不要試圖從散列碼計(jì)算中排除掉一個(gè)對(duì)象的關(guān)鍵域來(lái)提高性能领虹。
下面重寫 Student
類的 hashCode()
方法:
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + sid;
return result;
}
hashCode()
方法返回的散列碼也可以是負(fù)數(shù)规哪,合理地組合實(shí)例域的散列碼,以便能夠讓各個(gè)不同的對(duì)象產(chǎn)生的散列碼更加均勻塌衰。
toString()
默認(rèn)的 toString()
方法是返回的是 java.lang.Object@511baa65
這種類型是的字符串诉稍。
Student sample = new Student(1001, "小咖", 20, "男");
System.out.println(sample); // java.lang.Object@511baa65
上面 System.out.println(sample)
會(huì)自動(dòng)調(diào)用 sample.toString()
方法將值輸出到控制臺(tái)。但是最疆,提供好的 toString()
實(shí)現(xiàn)可以獲取對(duì)象狀態(tài)的必要信息杯巨,也易于調(diào)試。因此建議為每個(gè)自定義的類覆蓋 toString()
方法努酸。
下面重寫 Student
類的 toString()
方法:
@Override
public String toString() {
return getClass().getName()
+ "{ sid=" + sid
+ ", name=" + super.getName()
+ ", age=" + super.getAge()
+ ", sex=" + super.getSex()
+ " }";
}
如果父類 Person
也重寫類 toString()
方法:
@Override
public String toString() {
return getClass().getName() +
"{" + '\'' +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
并且子類的 toString()
也可以調(diào)用:
@Override
public String toString() {
return super.toString() + "{sid = " + sid + "}";
}
// xxx.Student{'name='小咖', age=20, sex='男'}{sid = 1001}
clone()
Object
類提供了 clone()
方法用于克隆實(shí)例服爷,但因?yàn)槭?protected
修飾符所修飾的方法,因此不會(huì)顯示地覆蓋 clone()
获诈。實(shí)現(xiàn) Cloneable
接口的類可以覆蓋 clone()
方法提供克隆仍源。如果不實(shí)現(xiàn),會(huì)拋出 CloneNotSupportedException
異常舔涎。正確的寫法如下所示:
public class Person implements Cloneable {
private String name;
private int age;
private String sex;
private String[] address;
public Person(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
...省略getter與setter...
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
}
Java 支持協(xié)變返回類型镜会,也就是覆蓋方法的返回類型可以是被覆蓋方法的返回類型的子類。并且在 clone()
方法中調(diào)用 super.clone()
方法得到功能完整的克隆對(duì)象终抽。
當(dāng)使用 Person
創(chuàng)建對(duì)象并調(diào)用 clone()
方法克隆戳表。
Person s1 = new Person("小卡", 22, "男");
s1.setAddress(new String[] {"浙江省", "江蘇省", "湖南省"});
Person s2 = sample.clone();
System.out.println(s1.hashCode()); // 873415566
System.out.println(s2.hashCode()); // 818403870
System.out.println(s1 == s2); // false
System.out.println(s1.getAddress() == s2.getAddress()); // true
從上面的代碼中得出桶至,調(diào)用 clone()
方法獲得的對(duì)象是個(gè)新的對(duì)象,但是對(duì)象中的引用還是原來(lái)的引用匾旭,而不是新引用镣屹。這次的克隆被稱為 淺拷貝。
使用 clone()
方法與通過(guò)構(gòu)造器創(chuàng)建對(duì)象實(shí)際上是一樣的价涝,要確保不會(huì)傷害到原始的對(duì)象女蜈,并確保正確地創(chuàng)建被克隆的對(duì)象中的約束條件。這次的克隆被稱為 深拷貝色瘩。
因此伪窖,在 Person
類的內(nèi)部,address
數(shù)組也要遞歸地調(diào)用 clone()
方法:
@Override
protected Person clone() throws CloneNotSupportedException {
Person result = (Person) super.clone();
result.address = address.clone();
return result;
}
記住居兆,Cloneable
與引用可變對(duì)象的 final
域的正常用法是不兼容的覆山。因此,實(shí)現(xiàn) clone()
方法禁止給 final
賦新值泥栖。
上述的拷貝方式比較復(fù)雜簇宽。可以在類中提供一個(gè)拷貝構(gòu)造器或拷貝工廠來(lái)實(shí)現(xiàn)克隆的替代功能吧享。
public Person(Person value) {...}
public static Person newInstance(Person value) {...}
finalize()
當(dāng) GC 確定不再有對(duì)該對(duì)象的引用時(shí)魏割,GC 會(huì)調(diào)用對(duì)象的 finalize()
方法來(lái)清除回收。
protected void finalize() throws Throwable { }
因此钢颂,子類可以通過(guò)覆蓋此方法處理一些額外的清理工作钞它。 但是,finalize()
方法何時(shí)被調(diào)用取決于 Java VM殊鞭,而且不保證 finalize()
方法會(huì)被及時(shí)地執(zhí)行 须揣。因此,不要依賴 finalize()
方法來(lái)更新重要的持久狀態(tài)钱豁。
Java VM 會(huì)確保一個(gè)對(duì)象的 finalize()
方法只被調(diào)用一次,而且程序中不能直接調(diào)用 finalize()
方法疯汁。
finalize()
方法通常也不可預(yù)測(cè)牲尺,而且很危險(xiǎn),一般情況下幌蚊,不必要覆蓋 finalize()
方法谤碳。
wait 與 notify
Object
對(duì)象提供了 wait()
和 notify()
方法,這兩個(gè)方法的使用是相對(duì)的:
-
wait()
:線程進(jìn)入等待狀態(tài)溢豆。 -
notify()
:?jiǎn)拘训却搶?duì)象的線程蜒简。
使用 wait()
方法必須在同步區(qū)域內(nèi)部調(diào)用,這個(gè)同步區(qū)域?qū)?duì)象鎖定在調(diào)用 wait()
方法的對(duì)象上漩仙。下面是使用 wait()
方法的標(biāo)準(zhǔn)模式:
synchronized (obj) {
while (<condition does not hold>) {
obj.wait();
}
}
這時(shí)的 obj
對(duì)象所在線程會(huì)處于等待狀態(tài)搓茬,需要使用 notify()
方法喚醒 obj
所在線程犹赖。
synchronized (obj) {
obj.notify();
}
最好使用 notifyAll()
方法喚醒線程。因?yàn)榭倳?huì)產(chǎn)生正確的結(jié)果卷仑,保證會(huì)喚醒所有需要被喚醒的線程峻村。雖然也會(huì)喚醒其他線程,但不影響程序的正確性锡凝,而且 notifyAll()
方法代替 notify()
方法可以避免來(lái)自不相關(guān)線程的意外或惡意的等待粘昨。
<small>注意:必須使用 synchronized
,否則會(huì)報(bào) IllegalMonitorStateException
異常窜锯。</small>
總結(jié)
這里對(duì) Object
類做了一個(gè)簡(jiǎn)單的了解张肾,知道 Object
類是一切類的基類,可以引用一切的引用類型锚扎,包括數(shù)組類型吞瞪。且 Object
中提供的方法需要在適合的場(chǎng)景下覆蓋得到最佳的結(jié)果,最好始終都覆蓋 toString()
方法工秩。
歡迎關(guān)注公眾號(hào)「海人為記」尸饺,期待與你共同進(jìn)步!