Java Object类的各个方法
Java中所有的類都繼承自java.lang.Object類,Object類中一共有11個方法:
public final native Class<?> getClass();public native int hashCode();public boolean equals(Object obj) {return (this == obj); }protected native Object clone() throws CloneNotSupportedException;public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode()); }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 {if (timeout < 0) {throw new IllegalArgumentException("timeout value is negative");}if (nanos < 0 || nanos > 999999) {throw new IllegalArgumentException("nanosecond timeout value out of range");}if (nanos > 0) {timeout++;}wait(timeout); }public final void wait() throws InterruptedException {wait(0); }protected void finalize() throws Throwable { }getClass方法
這是一個native方法,并且是'final'的,也就是說這個方法不允許在子類中覆寫。
getClass方法返回的是當前實例對應的Class類,也就是說不管一個類有多少個實例,每個實例的getClass返回的Class對象是一樣的。請看下面的例子:
上面的代碼運行結果為true,也就是說兩個Integer的實例的getClass方法返回的Class對象是同一個。
Integer.class和int.class
Java中還有一個方法可以獲取Class,例如我們想獲取一個Integer實例對應的Class,可以直接通過Integer.class來獲取,請看下面的例子:
Integer num = new Integer(1); Class numClass = num.getClass(); Class integerClass = Integer.class; System.out.println(numClass == integerClass);上面代碼的運行結果為true,也就是說通過調用實例的getClass方法和類.class返回的Class是一樣的。與Integer對象的還有int類型的原生類,與Integer.class對應,int.class用于獲取一個原生類型int的Class。但是他們兩個返回的不是同一個對象。請看下面的例子:
Class intClass = int.class; Class intTYPE = Integer.TYPE; Class integerClass = Integer.class; System.out.println(intClass == integerClass); System.out.println(intClass == intTYPE);上面的代碼運行結果是:
false trueInteger.class和int.class返回的不是一個對象,而int.class返回的和Integer.TYPE是同一個對象。Integer.TYPE定義如下:
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");Java中為原生類型(boolean,byte,char,short,int,long,float,double)和void都創建了一個預先定義好的類型,可以通過包裝類的TYPE靜態屬性獲取。上述的Integer類中TYPE和int.class是等價的。
hashCode方法
對象的哈希碼主要用于在哈希表中的存放和查找等。Java中對于對象hashCode方法的規約如下:
為了理解這三條規約,我們來先看一下HashMap中put一個entry的過程:
public V put(K key, V value) {return putVal(hash(key), key, value, false, true); }final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) == null) // 這里key的hashcode被用來定位當前entry在哈希表中的indextab[i] = newNode(hash, key, value, null); // 如果當前哈希表中沒有key對應的entry,則直接插入else {// 當前哈希表中已經有了key對應的entry了,則找到這個節點,然后看是否需要更新這個entry的valueNode<K,V> e; K k;// 判斷當前節點就是已經存在的entry的條件:1:hashcode相等;2:當前節點的key和key是同一個對象(==)或者兩者的equals方法判定相等if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}// 是否需要更新舊值,如果需要更新則更新if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null; }HashMap中put一個鍵值對時有以下幾個步驟:
分析完了HashMap的put操作后,我們再來看看這三條規約:
equals方法
equals方法用于判定兩個對象是否相等。Object中的equals方法其實默認比較的是兩個對象是否擁有相同的地址。也就是"=="對應的內存語義。
但是在子類中我們可以覆寫equals來判定子類的兩個實例是否為同一個對象。例如對于一個人來說,他有很多屬性,但是每個人的id是唯一的,如果兩個人的id一樣則證明兩個人是同一個人,而不管其他屬性是否一樣。這個時候我們就可以覆寫人的equals方法來保證這點了。
public class Person {private long id;private String name;private int age;private String nation;@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return id == person.id; // 如果兩個person的id相同,則我們認為他們是同一個對象}@Overridepublic int hashCode() {return Objects.hash(id);} }equals方法在非空對象引用上的特性:
Java要求一個類的equals方法和hashCode方法同時覆寫。我們剛剛分析了HashMap中對于key的處理過程:首先根據key的哈希碼定位哈希表中的位置,其次根據"=="或者equals方法判定兩個key是否相同。如果Person的equals方法沒有被覆寫,則兩個Person對象即使id一樣,但是不是指向同一塊內存地址,那么哈希表中就查找不到已經存在的映射entry了。
clone方法
用于克隆一個對象,被克隆的對象需要implements Cloneable接口,否則調用這個對象的clone方法,將會拋出CloneNotSupportedException異常。克隆的對象通常情況下滿足以下三條規則:
一個對象進行clone時,原生類型和包裝類型的field的克隆原理不同。對于原生類型是直接復制一個,而對于包裝類型,則只是復制一個引用而已,并不會對引用類型本身進行克隆。
淺拷貝
淺拷貝例子:
public class ShallowCopy {public static void main(String[] args){Man man = new Man();Man manShallowCopy = (Man) man.clone();System.out.println(man == manShallowCopy);System.out.println(man.name == manShallowCopy.name);System.out.println(man.mate == manShallowCopy.mate);} }class People implements Cloneable {// primitive typepublic int id;// reference typepublic String name;@Overridepublic Object clone() {try {return super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return null;} }class Man extends People implements Cloneable {// reference typepublic People mate = new People();@Overridepublic Object clone() {return super.clone();} }上面的代碼的運行結果是:
false true true通過淺拷貝出來的Man對象manShallowCopy的name和mate屬性和原來的對象man都指向了相同的內存地址。在對Man的name和mate進行拷貝時淺拷貝只是對引用進行了拷貝,指向的還是同一塊內存地址。
深拷貝
對一個對象深拷貝時,對于對象的包裝類型的屬性,會對其再進行拷貝,從而達到深拷貝的目的,請看下面的例子:
public class DeepCopy {public static void main(String[] args){Man man = new Man();Man manDeepCopy = (Man) man.clone();System.out.println(man == manDeepCopy);System.out.println(man.mate == manDeepCopy.mate);} }class People implements Cloneable {// primitive typepublic int id;// reference typepublic String name;@Overridepublic Object clone() {try {return super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return null;} }class Man extends People implements Cloneable {// reference typepublic People mate = new People();// 深拷貝Man@Overridepublic Object clone() {Man man = (Man) super.clone();man.mate = (People) this.mate.clone(); // 再對mate屬性進行clone,從而達到深拷貝return man;} }上面代碼的運行結果為:
false falseMan對象的clone方法中,我們先對Man進行了clone,然后對mate屬性也進行了拷貝。因此man的mate和manDeepCopy的mate指向了不同的內存地址。也就是深拷貝。
通常來說對一個對象進行完完全全的深拷貝是不現實的,例如上面的例子中,雖然我們對Man的mate屬性進行了拷貝,但是無法對name(String類型)進行拷貝,拷貝的還是引用而已。
toString方法
Object中默認的toString方法如下:
public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode()); }也就是class的名稱+對象的哈希碼。一般在子類中我們可以對這個方法進行覆寫。
notify方法
notify方法是一個final類型的native方法,子類不允許覆蓋這個方法。
notify方法用于喚醒正在等待當前對象監視器的線程,喚醒的線程是隨機的。一般notify方法和wait方法配合使用來達到多線程同步的目的。
在一個線程被喚醒之后,線程必須先重新獲取對象的監視器鎖(線程調用對象的wait方法之后會讓出對象的監視器鎖),才可以繼續執行。
一個線程在調用一個對象的notify方法之前必須獲取到該對象的監視器(synchronized),否則將拋出IllegalMonitorStateException異常。同樣一個線程在調用一個對象的wait方法之前也必須獲取到該對象的監視器。
wait和notify使用的例子:
Object lock = new Object(); Thread t1 = new Thread(() -> {synchronized (lock) {System.out.println(Thread.currentThread().getName() + " is going to wait on lock's monitor");try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " relinquishes the lock's monitor");} }); Thread t2 = new Thread(() -> {synchronized (lock) {System.out.println(Thread.currentThread().getName() + " is going to notify a thread that waits on lock's monitor");lock.notify();} }); t1.start(); t2.start();notifyAll方法
notifyAll方法用于喚醒所有等待對象監視器鎖的線程,notify只喚醒所有等待線程中的一個。
同樣,如果當前線程不是對象監視器的所有者,那么調用notifyAll同樣會發生IllegalMonitorStateException異常。
wait方法
public final native void wait(long timeout) throws InterruptedException;wait方法一般和上面說的notify方法搭配使用。一個線程調用一個對象的wait方法后,線程將進入WAITING狀態或者TIMED_WAITING狀態。直到其他線程喚醒這個線程。
線程在調用對象的wait方法之前必須獲取到這個對象的monitor鎖,否則將拋出IllegalMonitorStateException異常。線程的等待是支持中斷的,如果線程在等待過程中,被其他線程中斷,則拋出InterruptedException異常。
如果wait方法的參數timeout為0,代表等待過程是不會超時的,直到其他線程notify或者被中斷。如果timeout大于0,則代表等待支持超時,超時之后線程自動被喚醒。
finalize方法
protected void finalize() throws Throwable { }垃圾回收器在回收一個無用的對象的時候,會調用對象的finalize方法,我們可以覆寫對象的finalize方法來做一些清除工作。下面是一個finalize的例子:
public class FinalizeExample {public static void main(String[] args) {WeakReference<FinalizeExample> weakReference = new WeakReference<>(new FinalizeExample());weakReference.get();System.gc();System.out.println(weakReference.get() == null);}@Overridepublic void finalize() {System.out.println("I'm finalized!");} }輸出結果:
I'm finalized! truefinalize方法中對象其實還可以"自救",避免垃圾回收器將其回收。在finalize方法中通過建立this的一個強引用來避免GC:
public class FinalizeExample {private static FinalizeExample saveHook;public static void main(String[] args) {FinalizeExample.saveHook = new FinalizeExample();saveHook = null;System.gc();System.out.println(saveHook == null);}@Overridepublic void finalize() {System.out.println("I'm finalized!");saveHook = this;} }輸出結果:
I'm finalized! false
作者:zhong0316
鏈接:https://www.jianshu.com/p/852faf4dc8f1
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。
總結
以上是生活随笔為你收集整理的Java Object类的各个方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 河南郑州冲上41℃!汽车仪表台被烧出大洞
- 下一篇: IE寿终正寝 网友们搞起了真·墓碑…