應用
47.?不可變的引用類型
- BigInteger?total?=?BigInteger.ZERO;??
- total.add( new ?BigInteger( "1" ));??
- total.add( new ?BigInteger( "10" ));??
- System.out.println(total); //0 ??
上面程序的結果為11嗎?答案是0。
?
BigInteger實例是不可變的。String、BigDecimal以及包裝類型:Integer、Long、Short、Byte、Character、Boolean、Float和Double也是如此。對這些類型的操作將返回新的實例。
?
不可變類型更容易設計、實現與作用;它們出錯的可能性更小,并且更加安全。
?
本程序修改如下:
- BigInteger?total?=?BigInteger.ZERO;??
- total=total.add( new ?BigInteger( "1" ));??
- total=total.add( new ?BigInteger( "10" ));??
- System.out.println(total); //11 ??
?
48.?請同時重寫equals()與hashCode()
- class ?T?{??
- ? private ?String?str;??
- ??
- ?T(String?str)?{??
- ?? this .str?=?str;??
- ?}??
- ??
- ? public ? boolean ?equals(Object?obj)?{??
- ?? if (!(obj? instanceof ?T)){??
- ??? return ? false ;??
- ??}??
- ??T?t?=?(T)obj;??
- ?? return ?t.equals( this .str);??
- ?}??
- ??
- ? public ? static ? void ?main(String[]?args)?{??
- ??Set?set?=? new ?HashSet();??
- ??set.add( new ?T( "str" ));??
- ??System.out.println(set.contains( new ?T( "str" ))); //false ??
- ?}??
- }??
上面的程序不會打印true,而是false,為什么?
?
hashCode約定要求相等的對象要具有相同的散列碼。
?
無論何時,只要你重寫了equals方法,你就必須同時重寫hashCode方法。
?
如果將自定的類型對象放入HashSet、HashMap、Hashtable、LinkedHashSet、LinkedHashMap這此散列集合時,一定需要重寫equals與hashCode方法,這樣在放入進去之后還能查找出來。如果放入其他非散列類型的集合時,其實只需要
重寫equals就可以了。
?
本程序解決辦法重寫hashCode()方法:
- public ? int ?hashCode()?{??
- ? return ? 37 ?*? this .str.hashCode();??
- }??
?
49.?日期設置
- Calendar?c?=?Calendar.getInstance();??
- c.set( 2010 ,? 12 ,? 31 ); //?月是從0開始的,11其實表示12月 ??
- System.out.println(c.get(Calendar.YEAR)?+? "?" ?+?c.get(Calendar.MONTH));??
- c?=?Calendar.getInstance();??
- c.set( 2010 ,? 11 ,? 31 );??
- System.out.println(c.get(Calendar.YEAR)?+? "?" ?+?c.get(Calendar.MONTH));??
本程序較簡單,只需注意月是從0開始的就可以了,如果你設置月為12,則會自動轉換為下一年。
50.?IdentityHashMap
?
- class ?T?{??
- ? private ?String?str;??
- ??
- ?T(String?str)?{??
- ?? this .str?=?str;??
- ?}??
- ??
- ? public ? int ?hashCode()?{??
- ?? return ? 37 ?*? this .str.hashCode();??
- ?}??
- ??
- ? public ? boolean ?equals(Object?obj)?{??
- ?? return ? this .str.equals(((T)?obj).str);??
- ?}??
- ??
- ? public ? static ? void ?put(Map?m)?{??
- ??m.put( "str" ,? "1" );??
- ?? /* ?
- ???*?由于上面程序將?"str"?放入了字符串常量池, ?
- ???*?所以str是同一個對象,不管是什么樣類型的 ?
- ???*?Map,即使使用IdentityHashMap都只放入一次 ?
- ???*/ ??
- ??m.put( "str" ,? "2" );??
- ??m.put( new ?T( "str" ),? "3" );??
- ??m.put( new ?T( "str" ),? "4" );??
- ?}??
- ??
- ? public ? static ? void ?main(String[]?args)?{??
- ??Map?m?=? new ?HashMap();??
- ??put(m);??
- ??System.out.println(m.size()); //?2 ??
- ?? //IdentityHashMap比較時使用==替換equals()方法 ??
- ??m?=? new ?IdentityHashMap();??
- ??put(m);??
- ??System.out.println(m.size()); //?3 ??
- ?}??
- }??
?
51.?靜態導入的優先權
- import ? static ?java.util.Arrays.toString;??
- import ?java.util.Arrays;??
- public ? class ?T?{??
- ? public ? static ? void ?main(String[]?args)?{??
- ??prt( 1 ,? 2 ,? 3 );??
- ?}??
- ? static ? void ?prt(Object...?args)?{??
- ?? //?自身繼承至Object類的toString的優先級高于靜態導入的方法 ??
- ?? //!!?System.out.println(toString(args));//不能編譯 ??
- ??System.out.println(Arrays.toString(args));??
- ?}??
- }??
本身就屬于某個范圍的成員在該范圍內與靜態導入相比具有優先權。
52.?PrintStream對輸出結果的緩沖
- public ? static ? void ?main(String[]?args)?{??
- ?String?str?=? "Hello?World" ;??
- ? for ?( int ?i?=? 0 ;?i?<?str.length();?i++)?{??
- ??System.out.write(str.charAt(i));??
- ?}??
- }??
上面的程序沒有輸出結果。
?
這里的問題在于System.out是帶有緩沖的。輸出的結果被寫入了System.out的緩沖區,但是緩沖區從來都沒有被刷新。大多數人認為,當有輸出產生的時候System.out和System.err會自動地進制刷新,但這并不完全正確,這兩個流都屬于PrintStream類型,請看API DOC描述:一個PrintStream被創建為自動刷新,這意味著當一個字節數組(byte[])被寫入、或者某個println方法被調用、或者一個換行字符或字節('\n')被寫入之后,PrintStream類型的flush方法就會被自動調用。
?
令人奇怪的是,如果這個程序用print(char)去替代write(int),它就會刷新System.out并輸出結果,這種行為與print(char)的文檔是矛盾的,因為其文檔敘述道:“打印一個字符,這個字符將根據平臺缺省的字符編碼方式翻譯成一個或多個字節,并且這些字節將完全按照write(int)方法的方式輸出。”,但這里沒有換行符卻也自動的刷新了。
?
類似的,如果程序改用print(String),它也會對流進行刷新。所以調用print方法也是會自動刷新的。
?
53.?調用操作系統命令時被阻塞問題
- public ? static ? void ?main(String[]?args)? throws ?IOException,??
- ??InterruptedException?{??
- ?String?command?=? "java?ProcessTest?exc" ;??
- ? if ?(args.length?!=? 0 )?{??
- ?? for ?( int ?i?=? 0 ;?i?<? 200 ;?i++)?{??
- ???System.out.println(command);??
- ???System.err.println(command);??
- ??}??
- ?}? else ?{??
- ??Process?process?=?Runtime.getRuntime().exec(command);????
- ?? int ?exitValue?=?process.waitFor();??
- ??System.out.println( "exit?value?=?" ?+?exitValue);??
- ?}??
- }??
執行java ProcessTest發現程序阻塞。
?
Process文檔描述:由于某些本地平臺只提供有限大小的緩沖,所以如果不能迅速地讀取子進程的輸出流,就有可能會導致子進程的阻塞,甚至是死鎖。這恰好就是這里所發生的事情:沒有足夠的緩沖空間來保存這些輸出結果。為了結子進程(Process線程),父進程(Main線程)必須排空它的輸出流(標準流與錯誤流都需要排空),即要去緩存中讀取結果:
- static ? void ?readResult( final ?InputStream?is)?{??
- ? new ?Thread( new ?Runnable()?{??
- ?? public ? void ?run()?{??
- ??? try ?{??
- ???? //?排空緩存內容 ??
- ???? while ?(is.read()?>=? 0 );??
- ???}? catch ?(IOException?e)?{??
- ????e.printStackTrace();??
- ???}??
- ??}??
- ?}).start();??
- }??
?
然后在process.waitFor()之前加上
- readResult(process.getErrorStream());??
- readResult(process.getInputStream());??
即可輸出exit value = 0。
?
另外,只能根據process.waitFor返回的結果來判斷操作系統命令執行是否成功(成功:0,失?。?),我們不能根據
錯誤流中是否有內容來判斷是否執行成功。
54.?實現Serializable的單例問題
- class ?Dog? implements ?Serializable{??
- ? public ? static ? final ?Dog?INSTANCE?=? new ?Dog();??
- ? private ?Dog(){}??
- }??
?上面能控制只生成一個單實例嗎?
?
如果對實現了Serializable的對象進行序列化后,再反序列化,內中會不只一個實例了,因為反序列化時會重新生成一個對象。
?
既然INSTANCE為靜態域,那序列化時返回的對象如果也是INSTANCE就可以解決問題了,而打開API我們發現Serializable接口確實有這樣兩個特殊的方法描述:
??將對象寫入流時需要指定要使用的替代對象的可序列化類,應使用準確的簽名來實現此特殊方法:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
此 writeReplace 方法將由序列化調用,前提是如果此方法存在,而且它可以通過被序列化對象的類中定義的一個方法訪問。因此,該方法可以擁有私有 (private)、受保護的 (protected) 和包私有 (package-private) 訪問。子類對此方法的訪問遵循 java 訪問規則。?
??在從流中讀取類的一個實例時需要指定替代的類應使用的準確簽名來實現此特殊方法:
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
此 readResolve 方法遵循與 writeReplace 相同的調用規則和訪問規則。
?
上述兩個方法的只要出現,就會履蓋以下兩個方法(這兩個方法本質的意義就是用來替換序列與反序列的對象),雖然會執行它們,但最后得到的結果卻是writeReplace、readResolve兩個方法寫入或讀出的對象:
??private void writeObject(java.io.ObjectOutputStream out) throws IOException
??private void readObject(java.io.ObjectInputStream in)throws IOException, ClassNotFoundException;
?
另外,writeObject與readObject需成對實現,而writeReplace與readResolve則不需要成對出現,一般單獨使用。如果同時出現這四個方法,最后寫入與讀出的結果以writeReplace和readResolve方法的結果為準。
?
所以下要解決真真單實例問題,我們如下修正:
- class ?Dog? implements ?Serializable?{??
- ? public ? static ? final ?Dog?INSTANCE?=? new ?Dog();??
- ? private ?Dog()?{}??
- ? private ?Object?readResolve()?{??
- ?? return ?INSTANCE;??
- ?}??
- }??
- ??
- public ? class ?SerialDog?{??
- ? public ? static ? void ?main(String[]?args)? throws ?IOException,??
- ???ClassNotFoundException?{??
- ??ByteArrayOutputStream?bos?=? new ?ByteArrayOutputStream();??
- ?? new ?ObjectOutputStream(bos).writeObject(Dog.INSTANCE);??
- ??ByteArrayInputStream?bin?=? new ?ByteArrayInputStream(bos.toByteArray());??
- ??Dog?dog?=?(Dog)? new ?ObjectInputStream(bin).readObject();??
- ??System.out.println(dog?==?Dog.INSTANCE); //true ??
- ?}??
- }??
一個實現了Serializable的單例類,必須有一個readResolve方法,用以返回它的唯一實例。
55.?thread. isInterrupted()與Thread.interrupted()
- public ? class ?SelfInerruption?{??
- ? public ? static ? void ?main(String[]?args)?{??
- ??Thread.currentThread().interrupt();??
- ?? if ?(Thread.interrupted())?{??
- ??? //?Interruped:false ??
- ???System.out.println( "Interruped:" ?+?Thread.interrupted());??
- ??}? else ?{??
- ???System.out.println( "Not?interruped:" ?+?Thread.interrupted());??
- ??}??
- ?}??
- }??
上面結果走的是第一個分支,但結果卻不是Interruped:true?
?
Thread.interrupted()為Thread的靜態方法,調用它首先會返回當前線程的中斷狀態(如果當前線程上調用了interrupt()方法,則返回true,否則為false),然后再清除當前線程的中斷狀態,即將中斷狀態設置為false。換句話說,如果連續兩次調用該方法,則第二次調用將返回 false。
?
而isInterrupted()方法為實例方法,測試線程是否已經中斷,并不會清除當前線程中斷狀態。
?
所以這里應該使用isInterrupted()實例方法,就可以修復該問題。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
