Java中的Object類是所有類的父類枯芬,它提供了以下11個(gè)方法:
- public final native Class<?> getClass()
- public native int hashCode()
- public boolean equals(Object obj)
- protected native Object clone() throws CloneNotSupportedException
- public String toString()
- public final native void notify()
- public final native void notifyAll()
- public final native void wait(long timeout) throws InterruptedException
- public final void wait(long timeout, int nanos) throws InterruptedException
- public final void wait() throws InterruptedException
- protected void finalize() throws Throwable { }
下面我們一個(gè)個(gè)方法進(jìn)行分析,看這些方法到底有什么作用:
getClass方法
getClass方法是一個(gè)final方法铐望,不允許子類重寫彰檬,并且也是一個(gè)native方法。
返回當(dāng)前運(yùn)行時(shí)對(duì)象的Class對(duì)象
,注意這里是運(yùn)行時(shí)妄辩,比如以下代碼中n是一個(gè)Number類型的實(shí)例,但是java中數(shù)值默認(rèn)是Integer類型山上,所以getClass方法返回的是java.lang.Integer:
"str".getClass() // class java.lang.String
"str".getClass == String.class // true
Number n = 0;
Class<? extends Number> c = n.getClass(); // class java.lang.Integer
hashCode方法
hashCode方法也是一個(gè)native方法眼耀。 該方法返回對(duì)象的哈希碼,主要使用在哈希表中佩憾,比如JDK中的HashMap哮伟。
哈希碼的通用約定如下:
- 在java程序執(zhí)行過(guò)程中,在一個(gè)對(duì)象沒(méi)有被改變的前提下妄帘,無(wú)論這個(gè)對(duì)象被調(diào)用多少次澈吨,hashCode方法都會(huì)返回相同的整數(shù)值。對(duì)象的哈希碼沒(méi)有必要在不同的程序中保持相同的值寄摆。
- 如果2個(gè)對(duì)象使用equals方法進(jìn)行比較并且相同的話谅辣,那么這2個(gè)對(duì)象的hashCode方法的值也必須相等。
- 如果根據(jù)equals方法婶恼,得到兩個(gè)對(duì)象不相等桑阶,那么這2個(gè)對(duì)象的hashCode值不需要必須不相同。但是勾邦,不相等的對(duì)象的hashCode值不同的話可以提高哈希表的性能蚣录。
通常情況下,不同的對(duì)象產(chǎn)生的哈希碼是不同的眷篇。默認(rèn)情況下萎河,對(duì)象的哈希碼是通過(guò)將該對(duì)象的內(nèi)部地址轉(zhuǎn)換成一個(gè)整數(shù)來(lái)實(shí)現(xiàn)的。
String的hashCode方法實(shí)現(xiàn)如下蕉饼, 計(jì)算方法是 s[0]31^(n-1) + s[1]31^(n-2) + … + s[n-1]虐杯,其中s[0]表示字符串的第一個(gè)字符,n表示字符串長(zhǎng)度:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
比如”fo”的hashCode = 102 *31^1 + 111 = 3273昧港, “foo”的hashCode = 102 *31^2 + 111 * 31^1 + 111 = 101574 (‘f’的ascii碼為102, ‘o’的ascii碼為111)
hashCode在哈希表HashMap中的應(yīng)用:
// Student類擎椰,只重寫了hashCode方法
public static class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
return name.hashCode();
}
}
Map<Student, String> map = new HashMap<Student, String>();
Student stu1 = new Student("fo", 11);
Student stu2 = new Student("fo", 22);
map.put(stu1, "fo");
map.put(stu2, "fo");
上面這段代碼中,map中有2個(gè)元素stu1和stu2创肥。但是這2個(gè)元素是在哈希表中的同一個(gè)數(shù)組項(xiàng)中的位置达舒,也就是在同一串鏈表中值朋。 但是為什么stu1和stu2的hashCode相同,但是兩條元素都插到map里了巩搏,這是因?yàn)閙ap判斷重復(fù)數(shù)據(jù)的條件是 兩個(gè)對(duì)象的哈希碼相同并且(兩個(gè)對(duì)象是同一個(gè)對(duì)象或者兩個(gè)對(duì)象相等[equals為true])昨登。 所以再給Student重寫equals方法,并且只比較name的話贯底,這樣map就只有1個(gè)元素了篙骡。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return this.name.equals(student.name);
}
這個(gè)例子直接說(shuō)明了hashCode中通用約定的第三點(diǎn):
第三點(diǎn):如果根據(jù)equals方法,得到兩個(gè)對(duì)象不相等丈甸,那么這2個(gè)對(duì)象的hashCode值不需要必須不相同糯俗。但是,不相等的對(duì)象的hashCode值不同的話可以提高哈希表的性能睦擂。 –> 上面例子一開始沒(méi)有重寫equals方法得湘,導(dǎo)致兩個(gè)對(duì)象不相等,但是這兩個(gè)對(duì)象的hashCode值一樣顿仇,所以導(dǎo)致這兩個(gè)對(duì)象在同一串鏈表中淘正,影響性能。
當(dāng)然臼闻,還有第三種情況鸿吆,那就是equals方法相等,但是hashCode的值不相等述呐。
這種情況也就是違反了通用約定的第二點(diǎn):
第二點(diǎn):如果2個(gè)對(duì)象使用equals方法進(jìn)行比較并且相同的話惩淳,那么這2個(gè)對(duì)象的hashCode方法的值也必須相等。 違反這一點(diǎn)產(chǎn)生的后果就是如果一個(gè)stu1實(shí)例是Student(“fo”, 11)乓搬,stu2實(shí)例是Student(“fo”, 11)思犁,那么這2個(gè)實(shí)例是相等的,但是他們的hashCode不一樣进肯,這樣是導(dǎo)致哈希表中都會(huì)存入stu1實(shí)例和stu2實(shí)例激蹲,但是實(shí)際情況下,stu1和stu2是重復(fù)數(shù)據(jù)江掩,只允許存在一條數(shù)據(jù)在哈希表中学辱。所以這一點(diǎn)是非常重點(diǎn)的,再?gòu)?qiáng)調(diào)一下:如果2個(gè)對(duì)象的equals方法相等环形,那么他們的hashCode值也必須相等策泣,反之,如果2個(gè)對(duì)象hashCode值相等斟赚,但是equals不相等着降,這樣會(huì)影響性能差油,所以還是建議2個(gè)方法都一起重寫拗军。
equals方法
比較兩個(gè)對(duì)象是否相等
任洞。Object類的默認(rèn)實(shí)現(xiàn),即比較2個(gè)對(duì)象的內(nèi)存地址是否相等:
public boolean equals(Object obj) {
return (this == obj);
}
equals方法在非空對(duì)象引用上的特性:
- reflexive发侵,自反性交掏。任何非空引用值x,對(duì)于x.equals(x)必須返回true
- symmetric刃鳄,對(duì)稱性盅弛。任何非空引用值x和y,如果x.equals(y)為true叔锐,那么y.equals(x)也必須為true
- transitive挪鹏,傳遞性。任何非空引用值x愉烙、y和z讨盒,如果x.equals(y)為true并且y.equals(z)為true,那么x.equals(z)也必定為true
- consistent步责,一致性返顺。任何非空引用值x和y,多次調(diào)用 x.equals(y) 始終返回 true 或始終返回 false蔓肯,前提是對(duì)象上 equals 比較中所用的信息沒(méi)有被修改
- 對(duì)于任何非空引用值 x遂鹊,x.equals(null) 都應(yīng)返回 false
Object類的equals方法對(duì)于任何非空引用值x和y,當(dāng)x和y引用同一個(gè)對(duì)象時(shí)蔗包,此方法才返回true秉扑。這個(gè)也就是我們常說(shuō)的地址相等。
注意點(diǎn):如果重寫了equals方法调限,通常有必要重寫hashCode方法邻储,這點(diǎn)已經(jīng)在hashCode方法中說(shuō)明了。
clone方法
創(chuàng)建并返回當(dāng)前對(duì)象的一份拷貝
旧噪。一般情況下吨娜,對(duì)于任何對(duì)象 x,表達(dá)式 x.clone() != x 為true淘钟,x.clone().getClass() == x.getClass() 也為true宦赠。
Object類的clone方法是一個(gè)protected的native方法。
由于Object本身沒(méi)有實(shí)現(xiàn)Cloneable接口米母,所以不重寫clone方法并且進(jìn)行調(diào)用的話會(huì)發(fā)生CloneNotSupportedException異常勾扭。
toString方法
Object對(duì)象的默認(rèn)實(shí)現(xiàn),即輸出類的名字@實(shí)例的哈希碼的16進(jìn)制:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString方法的結(jié)果應(yīng)該是一個(gè)簡(jiǎn)明但易于讀懂的字符串铁瞒。建議Object所有的子類都重寫這個(gè)方法妙色。
notify方法
notify方法是一個(gè)native方法,并且也是final的慧耍,不允許子類重寫
身辨。
喚醒一個(gè)在此對(duì)象監(jiān)視器上等待的線程(監(jiān)視器相當(dāng)于就是鎖的概念)丐谋。如果所有的線程都在此對(duì)象上等待,那么只會(huì)選擇一個(gè)線程煌珊。選擇是任意性的号俐,并在對(duì)實(shí)現(xiàn)做出決定時(shí)發(fā)生。一個(gè)線程在對(duì)象監(jiān)視器上等待可以調(diào)用wait方法定庵。
直到當(dāng)前線程放棄對(duì)象上的鎖之后吏饿,被喚醒的線程才可以繼續(xù)處理。被喚醒的線程將以常規(guī)方式與在該對(duì)象上主動(dòng)同步的其他所有線程進(jìn)行競(jìng)爭(zhēng)蔬浙。例如猪落,喚醒的線程在作為鎖定此對(duì)象的下一個(gè)線程方面沒(méi)有可靠的特權(quán)或劣勢(shì)。
notify方法只能被作為此對(duì)象監(jiān)視器的所有者的線程來(lái)調(diào)用畴博。一個(gè)線程要想成為對(duì)象監(jiān)視器的所有者许布,可以使用以下3種方法:
- 執(zhí)行對(duì)象的同步實(shí)例方法
- 使用synchronized內(nèi)置鎖
- 對(duì)于Class類型的對(duì)象,執(zhí)行同步靜態(tài)方法
一次只能有一個(gè)線程擁有對(duì)象的監(jiān)視器绎晃。
如果當(dāng)前線程不是此對(duì)象監(jiān)視器的所有者的話會(huì)拋出IllegalMonitorStateException異常
注意點(diǎn):
因?yàn)閚otify只能在擁有對(duì)象監(jiān)視器的所有者線程中調(diào)用蜜唾,否則會(huì)拋出IllegalMonitorStateException異常
notifyAll方法
跟notify一樣,唯一的區(qū)別就是會(huì)喚醒在此對(duì)象監(jiān)視器上等待的所有線程庶艾,而不是一個(gè)線程袁余。
同樣,如果當(dāng)前線程不是對(duì)象監(jiān)視器的所有者咱揍,那么調(diào)用notifyAll同樣會(huì)發(fā)生IllegalMonitorStateException異常颖榜。
以下這段代碼直接調(diào)用notify或者notifyAll方法會(huì)發(fā)生IllegalMonitorStateException異常,這是因?yàn)檎{(diào)用這兩個(gè)方法需要當(dāng)前線程是對(duì)象監(jiān)視器的所有者:
Factory factory = new Factory();
factory.notify();
factory.notifyAll();
wait(long timeout) throws InterruptedException方法
wait(long timeout)方法同樣是一個(gè)native方法煤裙,并且也是final的掩完,不允許子類重寫。
wait方法會(huì)讓當(dāng)前線程等待直到另外一個(gè)線程調(diào)用對(duì)象的notify或notifyAll方法硼砰,或者超過(guò)參數(shù)設(shè)置的timeout超時(shí)時(shí)間且蓬。
跟notify和notifyAll方法一樣,當(dāng)前線程必須是此對(duì)象的監(jiān)視器所有者题翰,否則還是會(huì)發(fā)生IllegalMonitorStateException異常恶阴。
wait方法會(huì)讓當(dāng)前線程(我們先叫做線程T)將其自身放置在對(duì)象的等待集中,并且放棄該對(duì)象上的所有同步要求豹障。出于線程調(diào)度目的冯事,線程T是不可用并處于休眠狀態(tài),直到發(fā)生以下四件事中的任意一件:
- 其他某個(gè)線程調(diào)用此對(duì)象的notify方法血公,并且線程T碰巧被任選為被喚醒的線程
- 其他某個(gè)線程調(diào)用此對(duì)象的notifyAll方法
- 其他某個(gè)線程調(diào)用Thread.interrupt方法中斷線程T
- 時(shí)間到了參數(shù)設(shè)置的超時(shí)時(shí)間昵仅。如果timeout參數(shù)為0,則不會(huì)超時(shí)累魔,會(huì)一直進(jìn)行等待
所以可以理解wait方法相當(dāng)于放棄了當(dāng)前線程對(duì)對(duì)象監(jiān)視器的所有者(也就是說(shuō)釋放了對(duì)象的鎖)
之后摔笤,線程T會(huì)被等待集中被移除够滑,并且重新進(jìn)行線程調(diào)度。然后籍茧,該線程以常規(guī)方式與其他線程競(jìng)爭(zhēng)版述,以獲得在該對(duì)象上同步的權(quán)利梯澜;一旦獲得對(duì)該對(duì)象的控制權(quán)寞冯,該對(duì)象上的所有其同步聲明都將被恢復(fù)到以前的狀態(tài),這就是調(diào)用wait方法時(shí)的情況晚伙。然后吮龄,線程T從wait方法的調(diào)用中返回。所以咆疗,從wait方法返回時(shí)漓帚,該對(duì)象和線程T的同步狀態(tài)與調(diào)用wait方法時(shí)的情況完全相同。
在沒(méi)有被通知午磁、中斷或超時(shí)的情況下尝抖,線程還可以喚醒一個(gè)所謂的虛假喚醒 (spurious wakeup)。雖然這種情況在實(shí)踐中很少發(fā)生迅皇,但是應(yīng)用程序必須通過(guò)以下方式防止其發(fā)生昧辽,即對(duì)應(yīng)該導(dǎo)致該線程被提醒的條件進(jìn)行測(cè)試,如果不滿足該條件登颓,則繼續(xù)等待搅荞。換句話說(shuō),等待應(yīng)總是發(fā)生在循環(huán)中框咙,如下面的示例:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
如果當(dāng)前線程在等待之前或在等待時(shí)被任何線程中斷咕痛,則會(huì)拋出InterruptedException異常。在按上述形式恢復(fù)此對(duì)象的鎖定狀態(tài)時(shí)才會(huì)拋出此異常喇嘱。
wait(long timeout, int nanos) throws InterruptedException方法
跟wait(long timeout)方法類似茉贡,多了一個(gè)nanos參數(shù),這個(gè)參數(shù)表示額外時(shí)間(以毫微秒為單位者铜,范圍是 0-999999)块仆。 所以超時(shí)的時(shí)間還需要加上nanos毫秒。
需要注意的是 wait(0, 0)和wait(0)效果是一樣的王暗,即一直等待悔据。
wait() throws InterruptedException方法
跟之前的2個(gè)wait方法一樣,只不過(guò)該方法一直等待俗壹,沒(méi)有超時(shí)時(shí)間這個(gè)概念科汗。
以下這段代碼直接調(diào)用wait方法會(huì)發(fā)生IllegalMonitorStateException異常,這是因?yàn)檎{(diào)用wait方法需要當(dāng)前線程是對(duì)象監(jiān)視器的所有者:
Factory factory = new Factory();
factory.wait();
一般情況下绷雏,wait方法和notify方法會(huì)一起使用的头滔,wait方法阻塞當(dāng)前線程怖亭,notify方法喚醒當(dāng)前線程,一個(gè)使用wait和notify方法的生產(chǎn)者消費(fèi)者例子代碼如下:
public class WaitNotifyTest {
public static void main(String[] args) {
Factory factory = new Factory();
new Thread(new Producer(factory, 5)).start();
new Thread(new Producer(factory, 5)).start();
new Thread(new Producer(factory, 20)).start();
new Thread(new Producer(factory, 30)).start();
new Thread(new Consumer(factory, 10)).start();
new Thread(new Consumer(factory, 20)).start();
new Thread(new Consumer(factory, 5)).start();
new Thread(new Consumer(factory, 5)).start();
new Thread(new Consumer(factory, 20)).start();
}
}
class Factory {
public static final Integer MAX_NUM = 50;
private int currentNum = 0;
public void consume(int num) throws InterruptedException {
synchronized (this) {
while(currentNum - num < 0) {
this.wait();
}
currentNum -= num;
System.out.println("consume " + num + ", left: " + currentNum);
this.notifyAll();
}
}
public void produce(int num) throws InterruptedException {
synchronized (this) {
while(currentNum + num > MAX_NUM) {
this.wait();
}
currentNum += num;
System.out.println("produce " + num + ", left: " + currentNum);
this.notifyAll();
}
}
}
class Producer implements Runnable {
private Factory factory;
private int num;
public Producer(Factory factory, int num) {
this.factory = factory;
this.num = num;
}
@Override
public void run() {
try {
factory.produce(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Consumer implements Runnable {
private Factory factory;
private int num;
public Consumer(Factory factory, int num) {
this.factory = factory;
this.num = num;
}
@Override
public void run() {
try {
factory.consume(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
注意的是Factory類的produce和consume方法都將Factory實(shí)例鎖住了坤检,鎖住之后線程就成為了對(duì)象監(jiān)視器的所有者兴猩,然后才能調(diào)用wait和notify方法。
輸出:
produce 5, left: 5
produce 20, left: 25
produce 5, left: 30
consume 10, left: 20
produce 30, left: 50
consume 20, left: 30
consume 5, left: 25
consume 5, left: 20
consume 20, left: 0
finalize方法
finalize方法是一個(gè)protected方法早歇,Object類的默認(rèn)實(shí)現(xiàn)是不進(jìn)行任何操作倾芝。
該方法的作用是實(shí)例被垃圾回收器回收的時(shí)候觸發(fā)的操作,就好比 “死前的最后一波掙扎”箭跳。
直接寫個(gè)弱引用例子:
Car car = new Car(9999, "black");
WeakReference<Car> carWeakReference = new WeakReference<Car>(car);
int i = 0;
while(true) {
if(carWeakReference.get() != null) {
i++;
System.out.println("Object is alive for "+i+" loops - "+carWeakReference);
} else {
System.out.println("Object has been collected.");
break;
}
}
class Car {
private double price;
private String colour;
public Car(double price, String colour){
this.price = price;
this.colour = colour;
}
// get set method
@Override
protected void finalize() throws Throwable {
System.out.println("i will be destroyed");
}
}
輸出:
....
Object is alive for 26417 loops - java.lang.ref.WeakReference@7c2f1622
Object is alive for 26418 loops - java.lang.ref.WeakReference@7c2f1622
Object is alive for 26419 loops - java.lang.ref.WeakReference@7c2f1622
Object is alive for 26420 loops - java.lang.ref.WeakReference@7c2f1622
Object is alive for 26421 loops - java.lang.ref.WeakReference@7c2f1622
Object is alive for 26422 loops - java.lang.ref.WeakReference@7c2f1622
Object has been collected.
i will be destroyed