您好,登錄后才能下訂單哦!
這篇文章主要介紹了Java中Object類的方法怎么用的相關(guān)知識,內(nèi)容詳細(xì)易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Java中Object類的方法怎么用文章都會有所收獲,下面我們一起來看看吧。
前言:
Java的Object 類的完整路徑是java.lang.Object ,是所有類的父類編譯,當(dāng)我們創(chuàng)建一個類時,如果沒有明確繼承一個父類,那么它就會自動繼承 Object,成為 Object 的子類(隱式繼承)。Object類有九大常用方法,分別是getClass()、finalize()、toString()、equals()、hashcode()、wait()、notify()、notifyAll()和clone()。
首先,getClass()方法用于獲取一個對象的運行時類(Class),進(jìn)而通過返回的Class對象獲取Person的相關(guān)信息,比如獲取該類的構(gòu)造方法、該類有哪些方法、該類有哪些成員變量等信息。不同VM針對Class做了不同的優(yōu)化,所以getClass()的實現(xiàn)也并不相同:
// Java用native方法實現(xiàn)getClass() public final native Class<?> getClass(); // Android特殊的實現(xiàn)方式 private transient Class<?> shadow$_klass_; public final Class<?> getClass() { return shadow$_klass_; }
這是因為是Java默認(rèn)的Hotspot虛擬機并沒有開辟單獨的Method Area空間,而是有GC Heap的老生代的Metaspace實現(xiàn)的。而Android采用ART VM,這才造成了這種差異。想深入了解不同VM的實現(xiàn)的運行時數(shù)據(jù)分區(qū)、ClassLoader和Class類要讀很多書,本文不做過多討論。
finalize()是Object的protected方法,在發(fā)生GC時觸發(fā)該方法,大致流程是當(dāng)對象變成GC Roots不可達(dá)時,GC判斷該對象是否覆蓋了finalize()方法,若未覆蓋,則直接將其回收。否則,若對象未執(zhí)行過finalize()方法,將其放入F-Queue隊列,由一低優(yōu)先級線程執(zhí)行該隊列中對象的finalize()方法。執(zhí)行finalize()方法完畢后,GC會再次判斷該對象是否可達(dá),若不可達(dá),則進(jìn)行回收;否則,對象“復(fù)活”。
子類可以override方法以實現(xiàn)(1)防止對象被回收、(2)防止對象不被回收。防止對象被回收只需讓該對象與GC ROOTS之間存在可達(dá)鏈即可。我們重點看看FileInputStream、FileOutputStream、Connection等類怎么防止用戶忘記釋放資源呢,如下是FileInputStream的部分源碼:
protected void finalize() throws IOException { // Android新增CloseGuard確保FlieInputStream回收更安全 if (guard != null) { guard.warnIfOpen(); } // Java利用FileDescriptor確保FileInputStream不可達(dá),可以被安全回收 if ((fd != null) && (fd != FileDescriptor.in)) { close(); } }
toString()方法返回該對象的String表示,這也是連每個初級程序員都很熟悉的一個方法了。你仔細(xì)讀過Java的源碼,就知道很多類的toString()方法都是精雕細(xì)琢的,就像Integer的toString()方法,就針對Android做了一定適配:
public String toString() { return toString(this.value); } //返回指定十進(jìn)制整數(shù)的String public static String toString(int i) { if (i == Integer.MIN_VALUE) return "-2147483648"; // small是Android特有的變量,用二維Array緩存較?。▋晌粩?shù))數(shù)字的String boolean negative = i < 0; boolean small = negative ? i > -100 : i < 100; if (small) { final String[] smallValues = negative ? SMALL_NEG_VALUES : SMALL_NONNEG_VALUES; if (negative) { i = -i; if (smallValues[i] == null) { smallValues[i] = i < 10 ? new String(new char[]{'-', DigitOnes[i]}) : new String(new char[]{'-', DigitTens[i], DigitOnes[i]}); } } else { if (smallValues[i] == null) { smallValues[i] = i < 10 ? new String(new char[]{DigitOnes[i]}) : new String(new char[]{DigitTens[i], DigitOnes[i]}); } } return smallValues[i]; } int size = negative ? stringSize(-i) + 1 : stringSize(i); // getChars()方法略 char[] buf = new char[size]; getChars(i, size, buf); return new String(buf); }
在實際開發(fā)中,復(fù)雜對象的toString()方法用Gson生成JSON來實現(xiàn)。
equals()方法和hashcode()方法,我要放在一起說。
// 其實Object方法默認(rèn)的equals()也是比較引用是否相同 public boolean equals(Object obj) { return (this == obj); }
一般來說==比較的是引用是否相同,而equals()則是需要重寫來比較值是否相同。重寫equals()要注意以下幾點注意事項:
(1)對任意x,x.equals(x)一定返回true
(2)對任意x,y,如果x.equals(y)返回true,則y.equals(x)也一定返回true
(3)對任意x,y,z,如果x.equals(y)返回true,y.equals(z)也返回true,則x.equals(z)也一定返回true
(4)對任意x,y,如果對象中用于比較的信息沒有改變,那么無論調(diào)用多少次x.equals(y),返回的結(jié)果應(yīng)該保持一致,要么一直返回true,要么一直返回false
(5)對任意不是null的x,x.equals(null)一定返回false,如果兩個對象equals()方法相等則它們的hashCode返回值一定要相同。我們先看一下String是如何實現(xiàn)equals()的:
//比較這個String和另一個對象,當(dāng)且僅當(dāng)那個對象不為null且與這個String有相同的字符排列順序時返回true public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = length(); if (n == anotherString.length()) { int i = 0; while (n-- != 0) { if (charAt(i) != anotherString.charAt(i)) return false; i++; } return true; } } return false; }
我們再看一下String是如何實現(xiàn)hashcode()的:
// 緩存String的hashcode() private int hash; // 默認(rèn)為0 public int hashCode() { int h = hash; final int len = length(); if (h == 0 && len > 0) { for (int i = 0; i < len; i++) { h = 31 * h + charAt(i); } hash = h; } return h; }
反之,如果兩個對象的hashCode返回值相同,它們的equals()方法可以不返回true。這種情況叫做hash碰撞。HashMap處理hash碰撞的方法叫鏈地址法,除此以外hash碰撞還可以用ArrayMap采用的開放地址法解決,這些不在今天的話題討論范圍之內(nèi),不做贅述。
wait()、notify()和notifyAll()三個方法實現(xiàn)了Java的wait-notify機制。
先看wait()方法,wait()方法用來讓持有此對象的監(jiān)視器的線程處于阻塞狀態(tài),有參數(shù)不同的三個同名方法:
// 無參方法的Java與Android實現(xiàn)方式?jīng)]有區(qū)別 // 如果不在synchronized修飾的方法或代碼塊里調(diào)用,如果沒有獲取鎖,則會拋出IllegalMonitorStateException 異常 // 如果當(dāng)前線程在等待時被中斷,則拋出InterruptedException異常 public final void wait() throws InterruptedException { wait(0); } // timeout是線程等待時間,時間結(jié)束則自動喚醒,單位ms // Java默認(rèn)的實現(xiàn)方式,native實現(xiàn) public final native void wait(long timeout) throws InterruptedException; // Android的特殊處理 public final void wait(long timeout) throws InterruptedException { wait(timeout, 0); } // nanos是更精確的線程等待時間,單位ns(1 ms == 1,000,000 ns) // Java默認(rèn)的實現(xiàn)方式 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); } // Android的特殊處理,改為native實現(xiàn) @FastNative public final native void wait(long timeout, int nanos) throws InterruptedException;
ns是納秒的意思,1s == 1,000,000,000ns,光速是世界上最快的速度,光在1ns時間內(nèi)僅能傳播0.3m。一般PC的CPU計算一道簡單指令,比如2+3=5的時間為2~4ns。我們一般只用一個參數(shù)的wait()方法或者無參方法就足夠了,第二個參數(shù)在現(xiàn)實開發(fā)中幾乎用不到。
含參的wait()方法調(diào)用后,線程可以在等待時間結(jié)束后進(jìn)入就緒狀態(tài)(以下簡稱“喚醒”);無參的wait()方法調(diào)用后,則必須等待持有該對象監(jiān)視器的線程主動調(diào)用notify()或notifyAll()方法后才能被喚醒。區(qū)別在于notify()方法喚醒在此對象監(jiān)視器上等待的單個線程,如果所有線程都在此對象上等待,則會隨機喚醒其中一個線程;而notifyAll()方法則喚醒在此對象監(jiān)視器上等待的所有線程。
public final native void notify(); public final native void notifyAll();
wait()、notify()和notifyAll()都是final native方法,我們暫時不需要深入理解內(nèi)部是怎樣實現(xiàn)的,我們只要知道這就是Java的等待-通知(wait-notify)機制,學(xué)習(xí)它們的應(yīng)用場景就好了。
打個比方:
(1)用人單位決定錄用程序員的時間是不確定的,比如可能要面試好多人,需要綜合考慮,不能及時反饋
(2)每個程序員面試結(jié)束后需要wait()
(3)用人單位綜合考慮之后覺得最合適的程序員,讓HR notify()
(4)最后被選中程序員高高興興去上班了,其他程序員就等著吧
在開發(fā)中,wait-notify機制的最廣泛用途就是實現(xiàn)生產(chǎn)者/消費者模型,生產(chǎn)者/消費者模型能解決絕大多數(shù)并發(fā)問題,通過平衡生產(chǎn)線程和消費線程的工作能力來提高程序的整體處理數(shù)據(jù)的速度。
使用wait-notify機制的注意事項:
(1)wait()、notify()和notifyAll()必須在synchronized修飾的方法或代碼塊中使用
(2)在while循環(huán)里而不是if語句下使用wait(),確保在線程睡眠前后都檢查wait()觸發(fā)的條件(防止虛假喚醒)
(3)wait()方法必須在多線程共享的對象上調(diào)用
我們先定義注意(1)和(2)的一個生產(chǎn)者,往隊列里添加元素:
// 生產(chǎn)者,有詳細(xì)的注釋 public class Producer implements Runnable{ private Queue<Integer> queue; private int maxSize; public Producer(Queue<Integer> queue, int maxSize){ this.queue = queue; this.maxSize = maxSize; } @Override public void run() { // 這里為了方便演示做了一個死循環(huán),現(xiàn)實開發(fā)中不要這樣搞 while (true){ //(1)wait()、notify()和notifyAll()必須在synchronized修飾的方法或代碼塊中使用 synchronized (queue){ //(2)在while循環(huán)里而不是if語句下使用wait(),確保在線程睡眠前后都檢查wait()觸發(fā)的條件(防止虛假喚醒) while (queue.size() == maxSize){ try{ System.out.println("Queue is Full"); // 生產(chǎn)者線程進(jìn)入等待狀態(tài),在此對象監(jiān)視器上等待的所有線程(其實只有那個消費者線程)開始爭奪鎖 queue.wait(); }catch (InterruptedException ie){ ie.printStackTrace(); } } Random random = new Random(); int i = random.nextInt(); System.out.println("Produce " + i); queue.add(i); // 喚醒這個Queue對象的等待池中的所有線程(其實只有那個消費者線程),等待獲取對象監(jiān)視器 queue.notifyAll(); } } } }
再定義一個一模一樣的消費者,除了從隊列里移除元素之外,其他代碼同上
// 消費者,注釋略 public class Consumer implements Runnable{ private Queue<Integer> queue; private int maxSize; public Consumer(Queue<Integer> queue, int maxSize){ this.queue = queue; this.maxSize = maxSize; } @Override public void run() { while (true){ synchronized (queue){ while (queue.isEmpty()){ System.out.println("Queue is Empty"); try{ queue.wait(); }catch (InterruptedException ie){ ie.printStackTrace(); } } int v = queue.remove(); System.out.println("Consume " + v); queue.notifyAll(); } } } }
最后編寫符合(3)的測試代碼:
public void test(){ //(3)wait()方法必須在多線程共享的對象上調(diào)用 // 這個隊列就是給消費者、生產(chǎn)者兩個線程共享的對象 Queue<Integer> queue = new LinkedList<>(); int maxSize = 5; Producer p = new Producer(queue, maxSize); Consumer c = new Consumer(queue, maxSize); Thread pT = new Thread(p); Thread pC = new Thread(c); // 生產(chǎn)者線程啟動,獲取鎖 pT.start(); // 消費者線程啟動 pC.start(); }
查看運行結(jié)果:
Produce 1604006010
Produce 1312202442
Produce -1478853208
Produce 1460408111
Produce 1802825495
Queue is Full
Consume 1604006010
Consume 1312202442
Consume -1478853208
Consume 1460408111
Consume 1802825495
Queue is Empty
除了以上介紹的用synchronized關(guān)鍵字配合Object的wait()/notity()實現(xiàn),生產(chǎn)者-消費者模型還可以用Lock接口配合Condition的await()、signalAll()實現(xiàn),此外還可以用BlockingQueue實現(xiàn),但這些都不在本文的話題討論范圍之內(nèi),就不再贅述了。
Java語言的Object類實現(xiàn)了Cloneable接口,一個對象可以通過調(diào)用Clone()方法生成對象。需要注意的是,clone()方法并不是Cloneable接口里的,而是Object類里的,Cloneable是一個標(biāo)識接口,標(biāo)識這個類的對象是可被拷貝的,如果沒有實現(xiàn)Cloneable接口卻調(diào)用了clone()方法就會報錯。
// protected native Object clone() throws CloneNotSupportedException; protected Object clone() throws CloneNotSupportedException { if (!(this instanceof Cloneable)) { throw new CloneNotSupportedException("Class " + getClass().getName() + " doesn't implement Cloneable"); } return internalClone(); } // Native helper method for cloning. private native Object internalClone();
對象除了new出來和clone()出來,還可以通過反射和反序列化兩種方式產(chǎn)生,但這兩種方式不在我們今天的話題討論范圍之內(nèi)。
所謂原型模式,就是利用clone()生成對象的設(shè)計模式。需要提前了解一下深拷貝和淺拷貝的概念。Java中的數(shù)據(jù)類型分為基本類型和引用類型,在一個方法里的變量如果是基本類型的話,變量就直接存儲在這個方法的棧幀里,例如int、long等;而引用類型則在棧幀里存儲這個變量的指針,指向堆中該實體的地址,例如String、Array等。深拷貝和淺拷貝是只針對引用數(shù)據(jù)類型的
比如一個方法有一個基本類型參數(shù)和一個引用類型參數(shù),在方法體里對參數(shù)重新賦值,會影響傳入的引用類型參數(shù),而不會影響基本類型參數(shù),因為基本類型參數(shù)是值傳遞,而引用類型是引用傳遞。需要注意的是,較真來說Java只有值傳遞,因為Java的引用傳遞傳的是引用類型對象在堆內(nèi)存空間的地址,引用傳遞只是一種習(xí)慣性的說法,這個涉及到JVM和操作系統(tǒng),不做過多討論。
先定義一個用戶類:
// 這是一個非常簡單的用戶類 public class User { private String name; private int age; public User(String name, int age) { this.name=name; this.age=age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{name='" + name + ", age=" + age +'}'; } }
編寫測試代碼:
private int x=10; public void updateValue(int value){ value = 3 * value; } private User user= new User("唐茜靖",18); public void updateUser(User student){ student.setName("管晨辰"); student.setAge(16); } public void test(){ System.out.println("調(diào)用前x的值:"+x); updateValue(x); System.out.println("調(diào)用后x的值:"+x); System.out.println("調(diào)用前user的值:"+user.toString()); updateUser(user); System.out.println("調(diào)用后user的值:"+user.toString()); }
Log打印結(jié)果如下:
調(diào)用前x的值:10 調(diào)用后x的值:10 調(diào)用前user的值:User{name='唐茜靖, age=18} 調(diào)用后user的值:User{name='管晨辰, age=16} |
傳遞基本類型的方法(updateValue())流程圖:
傳遞引用類型的方法(updateUser())流程圖:
但也有例外,比如String類型和<=127的Long類型雖然也是引用類型,卻像基本類型一樣不受影響,這是因為它們會先比較常量池維護(hù)的值,這涉及VM的內(nèi)容,今天不做過多討論。淺拷貝是在按位(bit)拷貝對象,這個對象有著原始對象屬性值的一份精確拷貝。我們結(jié)合應(yīng)用場景分析一下,還是剛才的User類,我們增加一個存放地址的內(nèi)部類Address,我們需要用戶信息可以被其他module查詢,但是不允許它們被其他module修改,新增代碼如下:
// 這是一個稍微復(fù)雜的、支持拷貝的用戶類 public class User implements Cloneable { // ……省略上文代碼…… private Address address; @NonNull @NotNull @Override public User clone() { try{ return (User)super.clone(); }catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } public class Address{ // 地市 public String city; // 區(qū)縣 public String county; // 鄉(xiāng)鎮(zhèn)街道 public String street; } }
我們可以注意到Address還是指向以前的引用,淺拷貝會帶來數(shù)據(jù)安全方面的隱患,這就到了需要深拷貝的時候了。對于有多層對象的,每個對象都需要實現(xiàn) Cloneable 并重寫 clone() 方法,才可以實現(xiàn)了對象的串行層層拷貝。就像這樣:
// 這是一個更復(fù)雜的、支持深拷貝的用戶類 public class User implements Cloneable { // ……省略上文代碼…… @NonNull @NotNull @Override public User clone() { try{ User newUser = (User)super.clone(); newUser.setName(this.name); newUser.setAddress(this.address.clone()); return newUser; }catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } public class Address implements Cloneable{ // ……省略上文代碼…… @NonNull @NotNull @Override public Address clone() { try{ Address newAddress = (Address)super.clone(); newAddress.city = this.city; newAddress.county = this.county; newAddress.street = this.street; return newAddress; }catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } } }
至于徹底深拷貝幾乎是不可能實現(xiàn)的,不但可能存在引用關(guān)系非常復(fù)雜的情況,也可能存在引用鏈的某一級上引用了一個沒有實現(xiàn)Cloneable接口的第三方對象的情況。
最后總結(jié)一下,原型模式的用途之一是保護(hù)性拷貝,防止外部對只讀對象進(jìn)行修改,剛才我舉的例子就是保護(hù)性拷貝。另一個重要用途則是解決構(gòu)建復(fù)雜對象的資源消耗問題,提升創(chuàng)建對象的效率,這是因為clone()方法的原理是在內(nèi)存中拷貝二進(jìn)制流,比new一個對象的性能好很多,非常適用于需要在循環(huán)體內(nèi)產(chǎn)生大量對象的時候。絕大多數(shù)設(shè)計模式都是犧牲性能提升開發(fā)效率的,原型模式是為數(shù)不多的犧牲開發(fā)效率提升性能的。
我們做一下new和clone的對比:
private User user= new User("唐茜靖",18); public void testNew(){ User user1 = new User("管晨辰",16); } public void testClone(){ User user2 = user.clone(); }
通過ASM工具查看bytecode,可以對比出二者對棧資源的消耗:
// access flags 0x1 public testNew()V ……省略…… MAXSTACK = 4 MAXLOCALS = 2 // access flags 0x1 public testClone()V ……省略…… MAXSTACK = 1 MAXLOCALS = 2
此外還需要注意一點,拷貝不會執(zhí)行構(gòu)造函數(shù),所以有時候我們需要注意這個潛在的問題。幸好這個問題不是不可避免的,這是Android第五大組件Intent的clone()的實現(xiàn),沒有用拷貝:
@Override public Object clone() { return new Intent(this); }
關(guān)于“Java中Object類的方法怎么用”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對“Java中Object類的方法怎么用”知識都有一定的了解,大家如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。