Java中的Object類是所有類的父類,它提供了以下11個方法:
- 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 { }
下面我們一個個方法進(jìn)行分析,看這些方法到底有什么作用:
getClass方法
getClass方法是一個final方法,不允許子類重寫,并且也是一個native方法。
返回當(dāng)前運(yùn)行時對象的Class對象,注意這里是運(yùn)行時,比如以下代碼中n是一個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方法也是一個native方法。 該方法返回對象的哈希碼,主要使用在哈希表中,比如JDK中的HashMap。
哈希碼的通用約定如下:
- 在java程序執(zhí)行過程中,在一個對象沒有被改變的前提下,無論這個對象被調(diào)用多少次,hashCode方法都會返回相同的整數(shù)值。對象的哈希碼沒有必要在不同的程序中保持相同的值。
- 如果2個對象使用equals方法進(jìn)行比較并且相同的話,那么這2個對象的hashCode方法的值也必須相等。
- 如果根據(jù)equals方法,得到兩個對象不相等,那么這2個對象的hashCode值不需要必須不相同。但是,不相等的對象的hashCode值不同的話可以提高哈希表的性能。
通常情況下,不同的對象產(chǎn)生的哈希碼是不同的。默認(rèn)情況下,對象的哈希碼是通過將該對象的內(nèi)部地址轉(zhuǎn)換成一個整數(shù)來實(shí)現(xiàn)的。
String的hashCode方法實(shí)現(xiàn)如下, 計算方法是 s[0]31^(n-1) + s[1]31^(n-2) + … + s[n-1],其中s[0]表示字符串的第一個字符,n表示字符串長度:
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個元素stu1和stu2。但是這2個元素是在哈希表中的同一個數(shù)組項(xiàng)中的位置,也就是在同一串鏈表中。 但是為什么stu1和stu2的hashCode相同,但是兩條元素都插到map里了,這是因?yàn)閙ap判斷重復(fù)數(shù)據(jù)的條件是 兩個對象的哈希碼相同并且(兩個對象是同一個對象或者兩個對象相等[equals為true])。 所以再給Student重寫equals方法,并且只比較name的話,這樣map就只有1個元素了。
@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);
}
這個例子直接說明了hashCode中通用約定的第三點(diǎn):
第三點(diǎn):如果根據(jù)equals方法,得到兩個對象不相等,那么這2個對象的hashCode值不需要必須不相同。但是,不相等的對象的hashCode值不同的話可以提高哈希表的性能。 –> 上面例子一開始沒有重寫equals方法,導(dǎo)致兩個對象不相等,但是這兩個對象的hashCode值一樣,所以導(dǎo)致這兩個對象在同一串鏈表中,影響性能。
當(dāng)然,還有第三種情況,那就是equals方法相等,但是hashCode的值不相等。
這種情況也就是違反了通用約定的第二點(diǎn):
第二點(diǎn):如果2個對象使用equals方法進(jìn)行比較并且相同的話,那么這2個對象的hashCode方法的值也必須相等。 違反這一點(diǎn)產(chǎn)生的后果就是如果一個stu1實(shí)例是Student(“fo”, 11),stu2實(shí)例是Student(“fo”, 11),那么這2個實(shí)例是相等的,但是他們的hashCode不一樣,這樣是導(dǎo)致哈希表中都會存入stu1實(shí)例和stu2實(shí)例,但是實(shí)際情況下,stu1和stu2是重復(fù)數(shù)據(jù),只允許存在一條數(shù)據(jù)在哈希表中。所以這一點(diǎn)是非常重點(diǎn)的,再強(qiáng)調(diào)一下:如果2個對象的equals方法相等,那么他們的hashCode值也必須相等,反之,如果2個對象hashCode值相等,但是equals不相等,這樣會影響性能,所以還是建議2個方法都一起重寫。
equals方法
比較兩個對象是否相等。Object類的默認(rèn)實(shí)現(xiàn),即比較2個對象的內(nèi)存地址是否相等:
public boolean equals(Object obj) {
return (this == obj);
}
equals方法在非空對象引用上的特性:
- reflexive,自反性。任何非空引用值x,對于x.equals(x)必須返回true
- symmetric,對稱性。任何非空引用值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,前提是對象上 equals 比較中所用的信息沒有被修改
- 對于任何非空引用值 x,x.equals(null) 都應(yīng)返回 false
Object類的equals方法對于任何非空引用值x和y,當(dāng)x和y引用同一個對象時,此方法才返回true。這個也就是我們常說的地址相等。
注意點(diǎn):如果重寫了equals方法,通常有必要重寫hashCode方法,這點(diǎn)已經(jīng)在hashCode方法中說明了。
clone方法
創(chuàng)建并返回當(dāng)前對象的一份拷貝。一般情況下,對于任何對象 x,表達(dá)式 x.clone() != x 為true,x.clone().getClass() == x.getClass() 也為true。
Object類的clone方法是一個protected的native方法。
由于Object本身沒有實(shí)現(xiàn)Cloneable接口,所以不重寫clone方法并且進(jìn)行調(diào)用的話會發(fā)生CloneNotSupportedException異常。
toString方法
Object對象的默認(rèn)實(shí)現(xiàn),即輸出類的名字@實(shí)例的哈希碼的16進(jìn)制:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString方法的結(jié)果應(yīng)該是一個簡明但易于讀懂的字符串。建議Object所有的子類都重寫這個方法。
notify方法
notify方法是一個native方法,并且也是final的,不允許子類重寫。
喚醒一個在此對象監(jiān)視器上等待的線程(監(jiān)視器相當(dāng)于就是鎖的概念)。如果所有的線程都在此對象上等待,那么只會選擇一個線程。選擇是任意性的,并在對實(shí)現(xiàn)做出決定時發(fā)生。一個線程在對象監(jiān)視器上等待可以調(diào)用wait方法。
直到當(dāng)前線程放棄對象上的鎖之后,被喚醒的線程才可以繼續(xù)處理。被喚醒的線程將以常規(guī)方式與在該對象上主動同步的其他所有線程進(jìn)行競爭。例如,喚醒的線程在作為鎖定此對象的下一個線程方面沒有可靠的特權(quán)或劣勢。
notify方法只能被作為此對象監(jiān)視器的所有者的線程來調(diào)用。一個線程要想成為對象監(jiān)視器的所有者,可以使用以下3種方法:
- 執(zhí)行對象的同步實(shí)例方法
- 使用synchronized內(nèi)置鎖
- 對于Class類型的對象,執(zhí)行同步靜態(tài)方法
一次只能有一個線程擁有對象的監(jiān)視器。
如果當(dāng)前線程不是此對象監(jiān)視器的所有者的話會拋出IllegalMonitorStateException異常
注意點(diǎn):
因?yàn)閚otify只能在擁有對象監(jiān)視器的所有者線程中調(diào)用,否則會拋出IllegalMonitorStateException異常
notifyAll方法
跟notify一樣,唯一的區(qū)別就是會喚醒在此對象監(jiān)視器上等待的所有線程,而不是一個線程。
同樣,如果當(dāng)前線程不是對象監(jiān)視器的所有者,那么調(diào)用notifyAll同樣會發(fā)生IllegalMonitorStateException異常。
以下這段代碼直接調(diào)用notify或者notifyAll方法會發(fā)生IllegalMonitorStateException異常,這是因?yàn)檎{(diào)用這兩個方法需要當(dāng)前線程是對象監(jiān)視器的所有者:
Factory factory = new Factory();
factory.notify();
factory.notifyAll();
wait(long timeout) throws InterruptedException方法
wait(long timeout)方法同樣是一個native方法,并且也是final的,不允許子類重寫。
wait方法會讓當(dāng)前線程等待直到另外一個線程調(diào)用對象的notify或notifyAll方法,或者超過參數(shù)設(shè)置的timeout超時時間。
跟notify和notifyAll方法一樣,當(dāng)前線程必須是此對象的監(jiān)視器所有者,否則還是會發(fā)生IllegalMonitorStateException異常。
wait方法會讓當(dāng)前線程(我們先叫做線程T)將其自身放置在對象的等待集中,并且放棄該對象上的所有同步要求。出于線程調(diào)度目的,線程T是不可用并處于休眠狀態(tài),直到發(fā)生以下四件事中的任意一件:
- 其他某個線程調(diào)用此對象的notify方法,并且線程T碰巧被任選為被喚醒的線程
- 其他某個線程調(diào)用此對象的notifyAll方法
- 其他某個線程調(diào)用Thread.interrupt方法中斷線程T
- 時間到了參數(shù)設(shè)置的超時時間。如果timeout參數(shù)為0,則不會超時,會一直進(jìn)行等待
所以可以理解wait方法相當(dāng)于放棄了當(dāng)前線程對對象監(jiān)視器的所有者(也就是說釋放了對象的鎖)
之后,線程T會被等待集中被移除,并且重新進(jìn)行線程調(diào)度。然后,該線程以常規(guī)方式與其他線程競爭,以獲得在該對象上同步的權(quán)利;一旦獲得對該對象的控制權(quán),該對象上的所有其同步聲明都將被恢復(fù)到以前的狀態(tài),這就是調(diào)用wait方法時的情況。然后,線程T從wait方法的調(diào)用中返回。所以,從wait方法返回時,該對象和線程T的同步狀態(tài)與調(diào)用wait方法時的情況完全相同。
在沒有被通知、中斷或超時的情況下,線程還可以喚醒一個所謂的虛假喚醒 (spurious wakeup)。雖然這種情況在實(shí)踐中很少發(fā)生,但是應(yīng)用程序必須通過以下方式防止其發(fā)生,即對應(yīng)該導(dǎo)致該線程被提醒的條件進(jìn)行測試,如果不滿足該條件,則繼續(xù)等待。換句話說,等待應(yīng)總是發(fā)生在循環(huán)中,如下面的示例:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
如果當(dāng)前線程在等待之前或在等待時被任何線程中斷,則會拋出InterruptedException異常。在按上述形式恢復(fù)此對象的鎖定狀態(tài)時才會拋出此異常。
wait(long timeout, int nanos) throws InterruptedException方法
跟wait(long timeout)方法類似,多了一個nanos參數(shù),這個參數(shù)表示額外時間(以毫微秒為單位,范圍是 0-999999)。 所以超時的時間還需要加上nanos毫秒。
需要注意的是 wait(0, 0)和wait(0)效果是一樣的,即一直等待。
wait() throws InterruptedException方法
跟之前的2個wait方法一樣,只不過該方法一直等待,沒有超時時間這個概念。
以下這段代碼直接調(diào)用wait方法會發(fā)生IllegalMonitorStateException異常,這是因?yàn)檎{(diào)用wait方法需要當(dāng)前線程是對象監(jiān)視器的所有者:
Factory factory = new Factory();
factory.wait();
一般情況下,wait方法和notify方法會一起使用的,wait方法阻塞當(dāng)前線程,notify方法喚醒當(dāng)前線程,一個使用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í)例鎖住了,鎖住之后線程就成為了對象監(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方法是一個protected方法,Object類的默認(rèn)實(shí)現(xiàn)是不進(jìn)行任何操作。
該方法的作用是實(shí)例被垃圾回收器回收的時候觸發(fā)的操作,就好比 “死前的最后一波掙扎”。
直接寫個弱引用例子:
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