關於Object類(Java 10)中的方法,根據其所涉及的知識點,分爲以下4個部分:html
2018-09-15java
今天寫:equals、hashCode、toString、clone、getClass,後面的方法等學到相關知識再做補充。sql
1 public boolean equals(Object obj) { 2 return (this == obj); 3 }
equals用來判斷兩個對象是否」相等「,而對「相等」這個詞的定義不一樣,得出的結果也不一樣:編程
要實現上面的需求,咱們就須要重寫equals方法,以下:api
1 public class Point { 2 int x; 3 int y; 4 5 Point(int x, int y) { 6 this.x = x; 7 this.y = y; 8 } 9 10 @Override 11 public boolean equals(Object obj) { 12 if (this == obj) { 13 return true; 14 } 15 if (obj instanceof Point) { 16 Point p = (Point)obj; 17 return (this.x == p.x && this.y == p.y); 18 } 19 return false; 20 } 21 }
測試:安全
1 public static void main(String[] args) { 2 Point p1 = new Point(1, 1); 3 Point p2 = new Point(1, 1); 4 System.out.println("p1.equals(p2) : " + p1.equals(p2)); 5 p2.x = 2; 6 System.out.println("p1.equals(p2) : " + p1.equals(p2)); 7 } 8 /* 輸出 9 p1.equals(p2) : true 10 p1.equals(p2) : false 11 */
另記:String類也重寫了equals方法,實現了:只要兩個字符串長度相等及字符串中對應位置的字符相等,則兩字符串相等。能夠參考String類中的equals方法。數據結構
注意:在重寫equals方法時,方法定義要寫爲:public boolean equals(Object obj) {....} ,參數類型是:Object obj
併發
回到頂部oracle
1 public native int hashCode();
hashCode,官方解釋:返回對象的一個哈希碼,爲基於哈希表的數據結構提供便利,如:HashMap等。ide
Object類中的hashCode方法爲本地方法,在重寫hashCode方法時,需遵循如下規則:
關於第2條規則,咱們繼續Point類這個例子。首先,在未重寫hashCode方法的狀況下,咱們測試兩個對象的hashCode()輸出值:
1 public static void main(String[] args) { 2 Point p1 = new Point(9483, 89382); 3 Point p2 = new Point(9483, 89382); 4 System.out.println("p1.hashCode() : " + p1.hashCode()); 5 System.out.println("p2.hashCode() : " + p2.hashCode()); 6 } 7 /* 輸出: 8 p1.hashCode() : 166239592 9 p2.hashCode() : 991505714 10 */
能夠看到,在咱們定義的equals方法下相等的兩個對象,獲得的hashCode是不一樣的,如此不一致會形成什麼後果呢?咱們知道 HashMap 在存儲<Key, Value>時,若是Key1等於Key2,那麼存儲的鍵值對爲:<Key1, Value2>,即:只會存儲一個Key,使用的是最新的Value。而 HashMap 中在判斷 Key1是否等於Key2時,就使用到了它們的hashCode。在未重寫hashCode方法的狀況下,看以下測試:
1 public static void main(String[] args) { 2 Point p1 = new Point(9483, 89382); 3 Point p2 = new Point(9483, 89382); 4 5 HashMap<Point, Integer> map = new HashMap<Point, Integer>(); 6 map.put(p1, p1.hashCode()); 7 map.put(p2, p2.hashCode()); 8 for (Map.Entry<Point, Integer> m : map.entrySet()) { 9 System.out.println(m); 10 } 11 } 12 /* 輸出 13 Point@9e89d68=166239592 14 Point@3b192d32=991505714 15 */
根據咱們對Point類相等的定義,p1與p2相等,而在 HashMap 中卻存入了兩個鍵值對,顯然不符合咱們的意圖。(equals與hashCode的不一致,會形成使用時產生歧義,從而致使意想不到的錯誤。因此,咱們在重寫equals方法後,也要重寫hashCode方法,使其意義一致)如今咱們來重寫hashCode方法,再進行如上測試:
1 @Override 2 public int hashCode() { 3 return (x & y) | (x ^ y); 4 } 5 /* 輸出 6 Point@17d2f=97583 7 */
根據咱們對hashCode方法的定義,對象的hashCode只與(x, y)相關,因此 p1.hashCode() == p2.hashCode() 爲 true。這樣一來,HashMap 中只會存入一個鍵值對,符合咱們的預期。
1 public String toString() { 2 return getClass().getName() + "@" + Integer.toHexString(hashCode()); 3 }
源碼中直接返回:對象類型名@對象hashCode的十六進制,舉個例子:
1 public static void main(String[] args) { 2 Point p1 = new Point(9483, 89382); 3 System.out.println(p1.toString()); 4 } 5 /* 輸出 6 Point@17d2f 7 */
不少狀況下,咱們都要重寫toString()方法,就好比Point類,咱們想知道的是點的橫縱座標(x, y),而不是 Point@17d2f 這串不知所云的字符。
1 @Override 2 public String toString() { 3 return "(" + x + ", " + y + ")"; 4 } 5 /* 輸出 6 (9483, 89382) 7 */
1 protected native Object clone() throws CloneNotSupportedException;
從方法定義入手:
如今,咱們對以前的Point類進行部分修改,爲了節省空間,我只貼出修改部分的代碼:
首先,定義Data類,用來記錄一個點所包含的相關信息;
1 public class Data { 2 int weight; 3 String name; 4 5 Data(int weight, String name) { 6 this.weight = weight; 7 this.name = name; 8 } 9 }
而後,Point類實現Cloneable接口,而且Point類中包含一個Data類型字段,以下:
1 public class Point implements Cloneable { 2 int x; 3 int y; 4 Data data; 5 6 Point(int x, int y, Data data) { 7 this.x = x; 8 this.y = y; 9 this.data = data; 10 } 11 ... 12 }
測試:
1 public static void main(String[] args) throws Exception { 2 Data data = new Data(20, "A"); 3 Point p1 = new Point(1, 2, data); 4 Point p2 = (Point)p1.clone(); 5 6 System.out.println("p1 == p2 : " + (p1 == p2)); 7 System.out.println("p1.(x, y) = " + p1.toString() + ", p2.(x, y) = " + p2.toString()); 8 System.out.println("p1.data == p2.data : " + (p1.data == p2.data)); 9 } 10 /* 輸出 11 p1 == p2 : false 12 p1.(x, y) = (1, 2), p2.(x, y) = (1, 2) 13 p1.data == p2.data : true 14 */
對於測試的輸出,咱們能夠發現:
對於第3條,即Object類的clone方法是淺拷貝,理解如圖:
在一些併發編程情景下,咱們經常須要操做 不可變對象 來保證併發安全性。不可變對象,顧名思義就是你建立的對象不會改變,你能夠理解爲:
(更詳細的內容,能夠參考《Java併發編程實戰》)
如今,假如我要在併發環境下使用p1.clone()出來的對象p2,並要求p2是不可變的。而事實上,其餘線程能夠經過 p1.data 來改變 p2.data 的狀態,以破壞p2的不可變性。
要想使p2不可變,咱們就須要對Point類進行深拷貝,即:對Piont類中的Data類型字段也建立一個新的對象,使得 p1.data != p2.data,以下:
1 public class Data { 2 ... 3 // 自定義的clone(),並不是重寫Object類中的clone() 4 public Data clone() { 5 return new Data(weight, name); 6 } 7 } 8 public class Point implements Cloneable { 9 ... 10 @Override 11 protected Object clone() throws CloneNotSupportedException { 12 Point p = (Point)super.clone(); 13 p.data = data.clone(); // 這裏的data.clone()與Object類中的clone()無關 14 return p; 15 } 16 ... 17 } 18 /* 重複上面的測試,輸出: 19 p1 == p2 : false 20 p1.(x, y) = (1, 2), p2.(x, y) = (1, 2) 21 p1.data == p2.data : false 22 */
思考:若是一個類中一直嵌套着包含引用類型字段,那麼咱們該怎麼才能作到深拷貝呢?很明顯,對於類中每個引用類型對象都作深拷貝。(遞歸處理)
1 public final native Class<?> getClass();
getClass方法,返回對象的類對象,在反射中常用,例如:
Data類中有個私有方法 printInfo(),該方法在Point類中沒法正常調用,可是咱們能夠經過反射機制來調用該方法。
1 public class Data { 2 ... 3 private void printInfo() { 4 System.out.println("weight = " + weight); 5 System.out.println("name : " + name); 6 } 7 } 8 // 在Point類中 9 public static void main(String[] args) throws Exception { 10 Data data = new Data(20, "A"); 11 Class<?> clz = data.getClass(); 12 Method m = clz.getDeclaredMethod("printInfo"); 13 m.setAccessible(true); // 抑制Java的訪問控制檢查 14 m.invoke(data); 15 } 16 /* 輸出 17 weight = 20 18 name : A 19 */
這裏只是簡單提一下,更多關於反射的知識,會在後期總結。
2018-10-06
今天更新:wait系列、notify系列、finalize。
(我儘可能以簡單清晰的方式來展示個人內容,對於涉及到的知識點,這裏只是拋磚引玉,若想深刻研究,你們能夠進一步去查閱資料)
來看字面意思的解釋:
1000000*timeout+nanos)過去了;(注:線程被喚醒後,須要等待當前線程釋放鎖資源,而後與其餘請求該鎖的線程競爭,獲取鎖後才能獲得執行)
字面解釋就這些,下面要寫的是我在閱讀API(JavaSE 10 & JDK10)中該部份內容時的困惑及解答。先來看看這些方法的源碼吧:
1 /************************** wait **************************/ 2 public final void wait() throws InterruptedException { 3 wait(0L); // 調用 wait(long timeout) 4 } 5 6 public final native void wait(long timeout) throws InterruptedException; 7 8 public final void wait(long timeout, int nanos) throws InterruptedException { 9 if (timeout < 0) { 10 throw new IllegalArgumentException("timeout value is negative"); 11 } 12 if (nanos < 0 || nanos > 999999) { 13 throw new IllegalArgumentException( 14 "nanosecond timeout value out of range"); 15 } 16 /* 17 timeout 是ms(毫秒)計時 18 nanos 是ns(納秒)計時 19 本來我覺得某些領域須要更精確的時間控制,因此才提供wait(long timeout, int nanos)這個方法 20 而當我看到源碼的時候,這不就多了個timeout++嗎?這個判斷和加法在外部也能夠作啊。 21 因此,爲何要有這個方法呢? - -?(這個問題這裏不深刻討論) 22 */ 23 if (nanos > 0) { 24 timeout++; 25 } 26 wait(timeout); // 調用 wait(long timeout) 27 } 28 29 /************************** notify ************************/ 30 @HotSpotIntrinsicCandidate 31 public final native void notify(); 32 33 @HotSpotIntrinsicCandidate 34 public final native void notifyAll();
在API中,能夠看到wait的3個方法中都拋出如下異常:
1. IllegalMonitorStateException
若是當前線程不是該對象鎖的持有者時,拋出該異常。如何理解呢,看下面的代碼:
1 class T1 extends Thread { 2 3 @Override 4 public void run() { 5 try { 6 this.wait(1000); 7 System.out.println("wait over"); 8 } catch (InterruptedException e) {} 9 } 10 } 11 12 public class IllegalMonitorStateTest { 13 14 public static void main(String[] args) { 15 T1 t = new T1(); 16 t.start(); 17 } 18 } 19 20 /* 異常 21 Exception in thread "Thread-0" java.lang.IllegalMonitorStateException 22 at java.base/java.lang.Object.wait(Native Method) 23 at T1.run(IllegalMonitorStateTest.java:6) 24 */
拋出異常的緣由:[線程 t] 執行 this.wait(),但其並未獲取 this鎖。wait操做是要釋放當前鎖資源的,都沒有獲取如何釋放呢?
官方給出的說明:在如下3種狀況下,線程爲對象鎖的持有者:
因此,針對上面的例子,作如下修改(只貼出修改的部分),確保其不會拋出IllegalMonitorStateException異常。
1 @Override 2 public void run() { 3 synchronized (this) { 4 try { 5 this.wait(1000); 6 System.out.println("wait over"); 7 } catch (InterruptedException e) {} 8 } 9 } 10 /* 再進行測試,輸出 11 wait over 12 */
2. InterruptedException
1 class T2 extends Thread { 2 T2(String name) { 3 super(name); 4 } 5 6 @Override 7 public void run() { 8 synchronized (this) { 9 try { 10 this.wait(); 11 System.out.println("wait over"); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 } 16 } 17 } 18 19 public class InterruptedExceptionTest { 20 public static void main(String[] args) throws Exception { 21 T2 t = new T2("[線程 t]"); 22 t.start(); 23 System.out.println(t.getName() + "中斷狀態:" + t.isInterrupted()); 24 System.out.println("[線程 main]執行 t.interrupt();"); 25 t.interrupt(); 26 System.out.println(t.getName() + "中斷狀態:" + t.isInterrupted()); 27 28 System.out.println("------------------------------------------"); 29 Thread mainThread = Thread.currentThread(); 30 System.out.println("[線程 main]中斷狀態:" + mainThread.isInterrupted()); 31 System.out.println("[線程 main]執行 mainThread.interrupt();"); 32 mainThread.interrupt(); 33 System.out.println("[線程 main]中斷狀態:" + mainThread.isInterrupted()); 34 System.out.println("[線程 main]running......"); 35 } 36 } 37 38 /* 輸出 & 異常,兩個線程都有信息要輸出到控制檯,因此也可能異常信息先輸出 39 [線程 t]中斷狀態:false 40 [線程 main]執行 t.interrupt(); 41 [線程 t]中斷狀態:true 42 ------------------------------------------ 43 [線程 main]中斷狀態:false 44 [線程 main]執行 mainThread.interrupt(); 45 [線程 main]中斷狀態:true 46 [線程 main]running...... 47 java.lang.InterruptedException 48 at java.base/java.lang.Object.wait(Native Method) 49 at java.base/java.lang.Object.wait(Unknown Source) 50 at T3.run(InterruptedExceptionTest.java:10) 51 */
在當前線程等待前或等待期間,若是有其餘線程中斷當前線程,則拋出該異常。看下面的例子:
在這個示例中,咱們須要注意兩點:
3. Note that only the locks on this object are relinquished; any other objects on which the current thread may be synchronized remain locked while the thread waits.
只有基於這個對象的鎖被釋放;在線程等待期間,當前線程可能被同步的任何其餘對象都將保持鎖定。(翻譯看不懂?直接看代碼吧)
1 class T3 extends Thread { 2 Object obj; 3 4 T3(String name, Object obj) { 5 super(name); 6 this.obj = obj; 7 } 8 9 @Override 10 public void run() { 11 synchronized (this) { 12 System.out.println(Thread.currentThread().getName() + "獲取this鎖"); 13 synchronized (obj) { 14 System.out.println(Thread.currentThread().getName() + "獲取obj鎖"); 15 try { 16 this.wait(); 17 } catch (InterruptedException e) {} 18 } 19 } 20 } 21 } 22 23 public class OtherLockBeRetainedTest { 24 25 public static void main(String[] args) throws Exception { 26 Object obj = "x"; 27 T3 t1 = new T3("[線程 t1]", obj); 28 T3 t2 = new T3("[線程 t2]", obj); 29 t1.start(); 30 Thread.sleep(2000); 31 t2.start(); 32 } 33 } 34 35 /* 輸出 (程序死鎖) 36 [線程 t1]獲取this鎖 37 [線程 t1]獲取obj鎖 38 [線程 t2]獲取this鎖 39 */
順着代碼理一下程序執行過程:
4. 虛假喚醒(spurious wakeups)
(我這裏只給出一種簡單的虛假喚醒狀況,更詳細的內容,你們能夠自行查閱資料,能夠參看:《Java併發編程實戰》14.2 「使用條件隊列」,《Effective Java》第69條 「併發工具優先於wait和notify」)
在某些狀況下,咱們須要某個條件成立,線程才能往下執行,而下面的示例中,未必按這個套路出牌:
1 class T4 extends Thread { 2 boolean condition; 3 4 T4(boolean condition) { 5 this.condition = condition; 6 } 7 8 @Override 9 public void run() { 10 synchronized (this) { 11 try { 12 if (!condition) { 13 this.wait(); 14 } 15 // 當 condition == true 時,執行下面的操做 16 System.out.println("condition : " + condition); 17 } catch (InterruptedException e) {} 18 } 19 } 20 } 21 22 public class SpuriousWakeupTest { 23 public static void main(String[] args) throws Exception { 24 T4 t = new T4(false); 25 t.start(); 26 Thread.sleep(1000); 27 synchronized (t) { 28 t.notify(); 29 } 30 } 31 } 32 33 /* 輸出 34 condition : false 35 */
上面的例子,咱們的原意是:當condition爲true時,[線程 t] 繼續執行下面的操做,不然繼續等待直到條件成立。固然,[線程 t] 第一次判斷condition時,符合咱們的意圖,進行了等待;後來被主線程notify喚醒,condition依舊爲false,而 [線程 t] 卻執行了後續的操做,顯然不符合咱們的意圖。雖然 [線程 t] 真的被喚醒了,可是在咱們的業務邏輯定義下,它不該該被喚醒執行操做,應該繼續等待。對於這樣的喚醒,咱們稱爲虛假喚醒(狀況不止於此)。如何解決這個問題呢?其實只須要作一點小修改便可,即:把 if語句換成 while語句,以下:
1 @Override 2 public void run() { 3 synchronized (this) { 4 try { 5 while (!condition) { 6 this.wait(); 7 } 8 // 當 condition == true 時,執行下面的操做 9 System.out.println("condition : " + condition); 10 } catch (InterruptedException e) {} 11 } 12 }
1 @Deprecated(since="9") 2 protected void finalize() throws Throwable {}
finalize,終結方法,當GC認爲對象再也不被引用時,會先調用finalize(),再回收該對象。子類能夠重寫finalize方法,來處理一些系統資源或者完成其餘清理工做。(自Java 9 開始被棄用)
關於finalize,有如下4個點須要注意(摘自《Effective Java》第2版,第7條:避免使用終結方法):
在我看來,咱們能夠把finalize()當作是對象被GC回收前的回調,來看個「對象復活」的例子。代碼來自:http://www.javashuo.com/article/p-htdktnss-ht.html
1 public class FinalizeTest { 2 3 public static FinalizeTest SAVE_HOOK = null; 4 5 public static void main(String[] args) throws Exception { 6 SAVE_HOOK = new FinalizeTest(); 7 SAVE_HOOK = null; 8 System.gc(); 9 Thread.sleep(500); 10 if (null != SAVE_HOOK) { 11 System.out.println("Yes, I am still alive"); 12 } else { 13 System.out.println("No, I am dead"); 14 } 15 SAVE_HOOK = null; 16 System.gc(); 17 Thread.sleep(500); 18 if (null != SAVE_HOOK) { 19 System.out.println("Yes, I am still alive"); 20 } else { 21 System.out.println("No, I am dead"); 22 } 23 } 24 25 @Override 26 protected void finalize() throws Throwable { 27 super.finalize(); 28 System.out.println("execute method finalize()"); 29 SAVE_HOOK = this; 30 } 31 } 32 33 /* 輸出 34 execute method finalize() 35 Yes, I am still alive 36 No, I am dead 37 */
分析(爲了描述方便,把 new FinalizeTest() 生成的對象叫作 對象A):
總的來講,終結方法(finalizer)一般是不可預測的,也是很危險的,通常狀況下是沒必要要的。使用終結方法會致使行爲不穩定、下降性能,以及可移植性問題。
那麼,若是類的對象中封裝了資源(如文件或線程)確實須要終止,怎麼作才能不用編寫終結方法呢?
其實,咱們只須要提供一個顯示的終止方法便可,比較典型的例子:InputStream、OutputStream和java.sql.Connection上的close方法。一般,顯示的終止方法會與try-finally結構結合使用,以確保及時終止。代碼結構以下:
1 FOO foo = new Foo(); 2 try { 3 // do something 4 } finally { 5 foo.terminate(); // 如flie.close(); 6 }
終結方法有什麼好處呢?或者說,咱們何時該使用它?
由於對JVM和GC方面不太瞭解,因此在深刻理解和實踐時比較費勁,Emmmmm....再接再礪吧!
轉載請說明出處,have a good time! :D