架構師面試筆試題_第1頁
架構師面試筆試題_第2頁
架構師面試筆試題_第3頁
架構師面試筆試題_第4頁
架構師面試筆試題_第5頁
已閱讀5頁,還剩122頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

JDK1.7新增的功能,1.6和1.7的不同之處首先是模塊化特性:現(xiàn)在的Java7也是采用了模塊的劃分方式來提速,一些不是必須的模塊并沒有下載和安裝,因此在使用全新的Java7的虛擬機的時候會發(fā)現(xiàn)真的很快,當虛擬機需要用到某些功能的時候,再下載和啟用相應的模塊,這樣使得最初需要下載的虛擬機大小得到了有效的控制。同時對啟動速度也有了很大的改善。如果你對OpenJDK的架構比擬熟悉,你甚至可以定制JDK的模塊。其次是多語言支持:這里的多語言不是指中文英文之類的語言,而是說Java7的虛擬機對多種動態(tài)程序語言增加了支持,比方:Rubby、Python等等。對這些動態(tài)語言的支持極大地擴展了Java虛擬機的能力。對于那些熟悉這些動態(tài)語言的程序員而言,在使用Java虛擬機的過程中同樣可以使用它們熟悉的語言進行功能的編寫,而這些語言是跑在功能強大的JVM之上的。再有是開發(fā)者的開發(fā)效率得到了改善:Java7通過多種特性來增強開發(fā)效率。比方對語言本身做了一些細小的改變來簡化程序的編寫,在多線程并發(fā)與控制方面:輕量級的別離與合并框架,一個支持并發(fā)訪問的HashMap等等。通過注解增強程序的靜態(tài)檢查。提供了一些新的API用于文件系統(tǒng)的訪問、異步的輸入輸出操作、Socket通道的配置與綁定、多點數(shù)據(jù)包的傳送等等。最后是執(zhí)行效率的提高,也是給人感覺最真切體驗的特性:壓縮了64位的對象指針,Java7通過對對象指針由64位壓縮到與32位指針相匹配的技術使得內存和內存帶塊的消耗得到了很大的降低因而提高了執(zhí)行效率。此外還提供了新的垃圾回收機制〔G1〕來降低垃圾回收的負載和增強垃圾回收的效果。G1垃圾回收機制擁有更低的暫停率和更好的可預測性。字符流和字節(jié)流的區(qū)別,使用場景,相關類Java流在處理上分為字符流和字節(jié)流。字符流處理的單元為2個字節(jié)的Unicode字符,分別操作字符、字符數(shù)組或字符串,而字節(jié)流處理單元為1個字節(jié),操作字節(jié)和字節(jié)數(shù)組。Java內用Unicode編碼存儲字符,字符流處理類負責將外部的其他編碼的字符流和java內Unicode字符流之間的轉換。而類InputStreamReader和OutputStreamWriter處理字符流和字節(jié)流的轉換。字符流〔一次可以處理一個緩沖區(qū)〕一次操作比字節(jié)流〔一次一個字節(jié)〕效率高。(一)以字節(jié)為導向的streamInputStream/OutputStreamInputStream和OutputStream是兩個abstact類,對于字節(jié)為導向的stream都擴展這兩個雞肋〔基類^_^〕;1、InputStream1.1ByteArrayInputStream--把內存中的一個緩沖區(qū)作為InputStream使用.construct(A)ByteArrayInputStream(byte[])創(chuàng)立一個新字節(jié)數(shù)組輸入流〔ByteArrayInputStream〕,它從指定字節(jié)數(shù)組中讀取數(shù)據(jù)〔使用byte作為其緩沖區(qū)數(shù)組〕(B)ByteArrayInputStream(byte[],int,int)創(chuàng)立一個新字節(jié)數(shù)組輸入流,它從指定字節(jié)數(shù)組中讀取數(shù)據(jù)。mark::該字節(jié)數(shù)組未被復制。1.2StringBufferInputStream--把一個String對象作為InputStream.constructStringBufferInputStream(String)據(jù)指定串創(chuàng)立一個讀取數(shù)據(jù)的輸入流串。注釋:不推薦使用StringBufferInputStream方法。此類不能將字符正確的轉換為字節(jié)。同JDK1.1版中的類似,從一個串創(chuàng)立一個流的最正確方法是采用StringReader類。1.3FileInputStream--把一個文件作為InputStream,實現(xiàn)對文件的讀取操作construct(A)FileInputStream(Filename)創(chuàng)立一個輸入文件流,從指定的File對象讀取數(shù)據(jù)。(B)FileInputStream(FileDescriptor)創(chuàng)立一個輸入文件流,從指定的文件描述器讀取數(shù)據(jù)。(C)-FileInputStream(String

name)創(chuàng)立一個輸入文件流,從指定名稱的文件讀取數(shù)據(jù)。methodread()從當前輸入流中讀取一字節(jié)數(shù)據(jù)。read(byte[])將當前輸入流中b.length個字節(jié)數(shù)據(jù)讀到一個字節(jié)數(shù)組中。read(byte[],int,int)將輸入流中l(wèi)en個字節(jié)數(shù)據(jù)讀入一個字節(jié)數(shù)組中。1.4PipedInputStream:實現(xiàn)了pipe的概念,主要在線程中使用.管道輸入流是指一個通訊管道的接收端。一個線程通過管道輸出流發(fā)送數(shù)據(jù),而另一個線程通過管道輸入流讀取數(shù)據(jù),這樣可實現(xiàn)兩個線程間的通訊。constructPipedInputStream()創(chuàng)立一個管道輸入流,它還未與一個管道輸出流連接。PipedInputStream(PipedOutputStream)創(chuàng)立一個管道輸入流,它已連接到一個管道輸出流。1.5SequenceInputStream:把多個InputStream合并為一個InputStream.“序列輸入流”類允許應用程序把幾個輸入流連續(xù)地合并起來,并且使它們像單個輸入流一樣出現(xiàn)。每個輸入流依次被讀取,直到到達該流的末尾。然后“序列輸入流”類關閉這個流并自動地切換到下一個輸入流。constructSequenceInputStream(Enumeration)創(chuàng)立一個新的序列輸入流,并用指定的輸入流的枚舉值初始化它。SequenceInputStream(InputStream,InputStream)創(chuàng)立一個新的序列輸入流,初始化為首先讀輸入流s1,然后讀輸入流s2。2、OutputSteam

2.1ByteArrayOutputStream:把信息存入內存中的一個緩沖區(qū)中.該類實現(xiàn)一個以字節(jié)數(shù)組形式寫入數(shù)據(jù)的輸出流。當數(shù)據(jù)寫入緩沖區(qū)時,它自動擴大。用toByteArray()和toString()能檢索數(shù)據(jù)。constructor(A)ByteArrayOutputStream()創(chuàng)立一個新的字節(jié)數(shù)組輸出流。(B)ByteArrayOutputStream()創(chuàng)立一個新的字節(jié)數(shù)組輸出流。(C)ByteArrayOutputStream(int)創(chuàng)立一個新的字節(jié)數(shù)組輸出流,并帶有指定大小字節(jié)的緩沖區(qū)容量。toString(String)根據(jù)指定字符編碼將緩沖區(qū)內容轉換為字符串,并將字節(jié)轉換為字符。write(byte[],int,int)將指定字節(jié)數(shù)組中從偏移量off開始的len個字節(jié)寫入該字節(jié)數(shù)組輸出流。write(int)將指定字節(jié)寫入該字節(jié)數(shù)組輸出流。writeTo(OutputStream)用out.write(buf,0,count)調用輸出流的寫方法將該字節(jié)數(shù)組輸出流的全部內容寫入指定的輸出流參數(shù)。2.2

FileOutputStream:文件輸出流是向File或FileDescriptor輸出數(shù)據(jù)的一個輸出流。constructor(A)FileOutputStream(File

name)創(chuàng)立一個文件輸出流,向指定的File對象輸出數(shù)據(jù)。(B)FileOutputStream(FileDescriptor)創(chuàng)立一個文件輸出流,向指定的文件描述器輸出數(shù)據(jù)。(C)FileOutputStream(String

name)創(chuàng)立一個文件輸出流,向指定名稱的文件輸出數(shù)據(jù)。(D)FileOutputStream(String,boolean)用指定系統(tǒng)的文件名,創(chuàng)立一個輸出文件。2.3PipedOutputStream:管道輸出流是指一個通訊管道的發(fā)送端。一個線程通過管道輸出流發(fā)送數(shù)據(jù),而另一個線程通過管道輸入流讀取數(shù)據(jù),這樣可實現(xiàn)兩個線程間的通訊。constructor(A)PipedOutputStream()創(chuàng)立一個管道輸出流,它還未與一個管道輸入流連接。(B)PipedOutputStream(PipedInputStream)創(chuàng)立一個管道輸出流,它已連接到一個管道輸入流。(二)以字符為導向的streamReader/Writer以Unicode字符為導向的stream,表示以Unicode字符為單位從stream中讀取或往stream中寫入信息。Reader/Writer為abstact類以Unicode字符為導向的stream包括下面幾種類型:1.Reader1.1

CharArrayReader:與ByteArrayInputStream對應此類實現(xiàn)一個可用作字符輸入流的字符緩沖區(qū)constructorCharArrayReader(char[])用指定字符數(shù)組創(chuàng)立一個CharArrayReader。CharArrayReader(char[],int,int)用指定字符數(shù)組創(chuàng)立一個CharArrayReader1.2StringReader:與StringBufferInputStream對應其源為一個字符串的字符流。StringReader(String)創(chuàng)立一新的串讀取者。1.3FileReader:與FileInputStream對應1.4PipedReader:與PipedInputStream對應2.

Writer2.1

CharArrayWrite:與ByteArrayOutputStream對應2.2

StringWrite:無與之對應的以字節(jié)為導向的stream2.3

FileWrite:與FileOutputStream對應2.4

PipedWrite:與PipedOutputStream對應3、兩種不同導向的stream之間的轉換3.1InputStreamReader和OutputStreamReader:把一個以字節(jié)為導向的stream轉換成一個以字符為導向的stream。InputStreamReader類是從字節(jié)流到字符流的橋梁:它讀入字節(jié),并根據(jù)指定的編碼方式,將之轉換為字符流。使用的編碼方式可能由名稱指定,或平臺可接受的缺省編碼方式。InputStreamReader的read()方法之一的每次調用,可能促使從根本字節(jié)輸入流中讀取一個或多個字節(jié)。為了到達更高效率,考慮用BufferedReader封裝InputStreamReader,BufferedReaderin=newBufferedReader(newInputStreamReader(System.in));例如://實現(xiàn)從鍵盤輸入一個整數(shù)viewplaincopytoclipboardprint?

Strings=null;

InputStreamReaderre=newInputStreamReader(System.in);

BufferedReaderbr=newBufferedReader(re);

try{

s=br.readLine();

System.out.println("s="+Integer.parseInt(s));

br.close();

}

catch(IOExceptione)

{

e.printStackTrace();

}

catch(NumberFormatExceptione)//當應用程序試圖將字符串轉換成一種數(shù)值類型,但該字符串不能轉換為適當格式時,拋出該異常。

{

System.out.println("輸入的不是數(shù)字");

}

Strings=null;

InputStreamReaderre=newInputStreamReader(System.in);

BufferedReaderbr=newBufferedReader(re);

try{

s=br.readLine();

System.out.println("s="+Integer.parseInt(s));

br.close();

}

catch(IOExceptione)

{

e.printStackTrace();

}

catch(NumberFormatExceptione)//當應用程序試圖將字符串轉換成一種數(shù)值類型,但該字符串不能轉換為適當格式時,拋出該異常。

{

System.out.println("輸入的不是數(shù)字");

}InputStreamReader(InputStream)用缺省的字符編碼方式,創(chuàng)立一個InputStreamReader。InputStreamReader(InputStream,String)用已命名的字符編碼方式,創(chuàng)立一個InputStreamReader。OutputStreamWriter將多個字符寫入到一個輸出流,根據(jù)指定的字符編碼將多個字符轉換為字節(jié)。每個OutputStreamWriter合并它自己的CharToByteConverter,因而是從字符流到字節(jié)流的橋梁。〔三〕JavaIO的一般使用原那么:一、按數(shù)據(jù)來源〔去向〕分類:1、是文件:FileInputStream,FileOutputStream,(字節(jié)流)FileReader,FileWriter(字符)2、是byte[]:ByteArrayInputStream,ByteArrayOutputStream(字節(jié)流)3、是Char[]:CharArrayReader,CharArrayWriter(字符流)4、是String:StringBufferInputStream,StringBufferOuputStream(字節(jié)流)StringReader,StringWriter(字符流)5、網(wǎng)絡數(shù)據(jù)流:InputStream,OutputStream,(字節(jié)流)Reader,Writer(字符流)二、按是否格式化輸出分:1、要格式化輸出:PrintStream,PrintWriter三、按是否要緩沖分:1、要緩沖:BufferedInputStream,BufferedOutputStream,(字節(jié)流)BufferedReader,BufferedWriter(字符流)四、按數(shù)據(jù)格式分:1、二進制格式〔只要不能確定是純文本的〕:InputStream,OutputStream及其所有帶Stream結束的子類2、純文本格式〔含純英文與漢字或其他編碼方式〕;Reader,Writer及其所有帶Reader,Writer的子類五、按輸入輸出分:1、輸入:Reader,InputStream類型的子類2、輸出:Writer,OutputStream類型的子類六、特殊需要:1、從Stream到Reader,Writer的轉換類:InputStreamReader,OutputStreamWriter2、對象輸入輸出:ObjectInputStream,ObjectOutputStream3、進程間通信:PipeInputStream,PipeOutputStream,PipeReader,PipeWriter4、合并輸入:SequenceInputStream5、更特殊的需要:PushbackInputStream,PushbackReader,LineNumberInputStream,LineNumberReader決定使用哪個類以及它的構造進程的一般準那么如下〔不考慮特殊需要〕:首先,考慮最原始的數(shù)據(jù)格式是什么:原那么四第二,是輸入還是輸出:原那么五第三,是否需要轉換流:原那么六第1點第四,數(shù)據(jù)來源〔去向〕是什么:原那么一第五,是否要緩沖:原那么三〔特別注明:一定要注意的是readLine()是否有定義,有什么比read,write更特殊的輸入或輸出方法〕第六,是否要格式化輸出:原那么二線程平安的概念,實現(xiàn)線程平安的幾種方法

Java編程語言為編寫多線程應用程序提供強大的語言支持。但是,編寫有用的、沒有錯誤的多線程程序仍然比擬困難。本文試圖概述幾種方法,程序員可用這幾種方法來創(chuàng)立高效的線程平安類。并發(fā)性只有當要解決的問題需要一定程度的并發(fā)性時,程序員才會從多線程應用程序中受益。例如,如果打印隊列應用程序僅支持一臺打印機和一臺客戶機,那么不應該將它編寫為多線程的。一般說來,包含并發(fā)性的編碼問題通常都包含一些可以并發(fā)執(zhí)行的操作,同時也包含一些不可并發(fā)執(zhí)行的操作。例如,為多個客戶機和一個打印機提供效勞的打印隊列可以支持對打印的并發(fā)請求,但向打印機的輸出必須是串行形式的。多線程實現(xiàn)還可以改善交互式應用程序的響應時間。Synchronized關鍵字雖然多線程應用程序中的大多數(shù)操作都可以并行進行,但也有某些操作〔如更新全局標志或處理共享文件〕不能并行進行。在這些情況下,必須獲得一個鎖來防止其他線程在執(zhí)行此操作的線程完成之前訪問同一個方法。在Java程序中,這個鎖是通過synchronized關鍵字提供的。清單1說明了它的用法。清單1.使用synchronized關鍵字來獲取鎖publicclassMaxScore{intmax;publicMaxScore(){max=0;}publicsynchronizedvoidcurrentScore(ints){if(s>max){max=s;}}publicintmax(){returnmax;}}這里,兩個線程不能同時調用currentScore()方法;當一個線程工作時,另一個線程必須阻塞。但是,可以有任意數(shù)量的線程同時通過max()方法訪問最大值,因為max()不是同步方法,因此它與鎖定無關。試考慮在MaxScore類中添加另一個方法的影響,該方法的實現(xiàn)如清單2所示。清單2.添加另一個方法publicsynchronizedvoidreset(){max=0;}這個方法〔當被訪問時〕不僅將阻塞reset()方法的其他調用,而且也將阻塞MaxScore類的同一個實例中的currentScore()方法,因為這兩個方法都訪問同一個鎖。如果兩個方法必須不彼此阻塞,那么程序員必須在更低的級別使用同步。清單3是另一種情況,其中兩個同步的方法可能需要彼此獨立。清單3.兩個獨立的同步方法importjava.util.*;publicclassJury{Vectormembers;Vectoralternates;publicJury(){members=newVector(12,1);alternates=newVector(12,1);}publicsynchronizedvoidaddMember(Stringname){members.add(name);}publicsynchronizedvoidaddAlt(Stringname){alternates.add(name);}publicsynchronizedVectorall(){Vectorretval=newVector(members);retval.addAll(alternates);returnretval;}}此處,兩個不同的線程可以將members和alternates添加到Jury對象中。請記住,synchronized關鍵字既可用于方法,更一般地,也可用于任何代碼塊。清單4中的兩段代碼是等效的。清單4.等效的代碼synchronizedvoidf(){voidf(){//執(zhí)行某些操作synchronized(this){}//執(zhí)行某些操作}}所以,為了確保addMember()和addAlt()方法不彼此阻塞,可按清單5所示重寫Jury類。清單5.重寫后的Jury類importjava.util.*;publicclassJury{Vectormembers;Vectoralternates;publicJury(){members=newVector(12,1);alternates=newVector(12,1);}publicvoidaddMember(Stringname){synchronized(members){members.add(name);}}publicvoidaddAlt(Stringname){synchronized(alternates){alternates.add(name);}}publicVectorall(){Vectorretval;synchronized(members){retval=newVector(members);}synchronized(alternates){retval.addAll(alternates);}returnretval;}}請注意,我們還必須修改all()方法,因為對Jury對象同步已沒有意義。在改寫后的版本中,addMember()、addAlt()和all()方法只訪問與members和alternates對象相關的鎖,因此鎖定Jury對象毫無用處。另請注意,all()方法本來可以寫為清單6所示的形式。清單6.將members和alternates用作同步的對象publicVectorall(){synchronized(members){synchronized(alternates){Vectorretval;retval=newVector(members);retval.addAll(alternates);}}returnretval;}但是,因為我們早在需要之前就獲得members和alternates的鎖,所以這效率不高。清單5中的改寫形式是一個較好的例如,因為它只在最短的時間內持有鎖,并且每次只獲得一個鎖。這樣就完全防止了當以后增加代碼時可能產生的潛在死鎖問題。同步方法的分解正如在前面看到的那樣,同步方法獲取對象的一個鎖。如果該方法由不同的線程頻繁調用,那么此方法將成為瓶頸,因為它會對并行性造成限制,從而會對效率造成限制。這樣,作為一個一般的原那么,應該盡可能地少用同步方法。盡管有這個原那么,但有時一個方法可能需要完成需要鎖定一個對象幾項任務,同時還要完成相當耗時的其他任務。在這些情況下,可使用一個動態(tài)的“鎖定-釋放-鎖定-釋放”方法。例如,清單7和清單8顯示了可按這種方式變換的代碼。清單7.最初的低效率代碼publicsynchonizedvoiddoWork(){unsafe1();write_file();unsafe2();}清單8.重寫后效率較高的代碼publicvoiddoWork(){synchonized(this){unsafe1();}write_file();synchonized(this){unsafe2();}}清單7和清單8假定第一個和第三個方法需要對象被鎖定,而更耗時的write_file()方法不需要對象被鎖定。如您所見,重寫此方法以后,對此對象的鎖在第一個方法完成以后被釋放,然后在第三個方法需要時重新獲得。這樣,當write_file()方法執(zhí)行時,等待此對象的鎖的任何其他方法仍然可以運行。將同步方法分解為這種混合代碼可以明顯改善性能。但是,您需要注意不要在這種代碼中引入邏輯錯誤。嵌套類內部類在Java程序中實現(xiàn)了一個令人關注的概念,它允許將整個類嵌套在另一個類中。嵌套類作為包含它的類的一個成員變量。如果定期被調用的的一個特定方法需要一個類,就可以構造一個嵌套類,此嵌套類的唯一任務就是定期調用所需的方法。這消除了對程序的其他局部的相依性,并使代碼進一步模塊化。清單9,一個圖形時鐘的根底,使用了內部類。清單9.圖形時鐘例如publicclassClock{protectedclassRefresherextendsThread{intrefreshTime;publicRefresher(intx){super("Refresher");refreshTime=x;}publicvoidrun(){while(true){try{sleep(refreshTime);}catch(Exceptione){}repaint();}}}publicClock(){Refresherr=newRefresher(1000);r.start();}privatevoidrepaint(){//獲取時間的系統(tǒng)調用//重繪時鐘指針}}清單9中的代碼例如不靠任何其他代碼來調用repaint()方法。這樣,將一個時鐘并入一個較大的用戶界面就相當簡單。事件驅動處理當應用程序需要對事件或條件〔內部的和外部的〕作出反映時,有兩種方法或用來設計系統(tǒng)。在第一種方法〔稱為輪詢〕中,系統(tǒng)定期確定這一狀態(tài)并據(jù)此作出反映。這種方法〔雖然簡單〕也效率不高,因為您始終無法預知何時需要調用它。第二種方法〔稱為事件驅動處理〕效率較高,但實現(xiàn)起來也較為復雜。在事件驅動處理的情況下,需要一種發(fā)信機制來控制某一特定線程何時應該運行。在Java程序中,您可以使用wait()、notify()和notifyAll()方法向線程發(fā)送信號。這些方法允許線程在一個對象上阻塞,直到所需的條件得到滿足為止,然后再次開始運行。這種設計減少了CPU占用,因為線程在阻塞時不消耗執(zhí)行時間,并且可在notify()方法被調用時立即喚醒。與輪詢相比,事件驅動方法可以提供更短的響應時間。創(chuàng)立高效的線程平安類的步驟編寫線程平安類的最簡單的方法是用synchronized聲明每個方法。雖然這種方案可以消除數(shù)據(jù)損壞,但它同時也會消除您預期從多線程獲得的任何收益。這樣,您就需要分析并確保在synchronized塊內部僅占用最少的執(zhí)行時間。您必須格外關注訪問緩慢資源―文件、目錄、網(wǎng)絡套接字和數(shù)據(jù)庫

―的方法,這些方法可能降低您的程序的效率。盡量將對這類資源的訪問放在一個單獨的線程中,最好在任何synchronized代碼之外。一個線程平安類的例如被設計為要處理的文件的中心儲存庫。它與使用getWork()和finishWork()與WorkTable類對接的一組線程一起工作。本例旨在讓您體驗一下全功能的線程平安類,該類使用了helper線程和混合同步。請注意繼續(xù)添加要處理的新文件的Refresherhelper線程的用法。本例沒有調整到最正確性能,很明顯有許多地方可以改寫以改善性能,比方將Refresher線程改為使用wait()/notify()方法事件驅動的,改寫populateTable()方法以減少列出磁盤上的文件〔這是高本錢的操作〕所產生的影響。小結通過使用可用的全部語言支持,Java程序中的多線程編程相當簡單。但是,使線程平安類具有較高的效率仍然比擬困難。為了改善性能,您必須事先考慮并謹慎使用鎖定功能。抽象類和接口的區(qū)別,使用場景在Java語言中,abstractclass和interafce是支持抽象類定義的兩種機制。正是由于這兩種機制的存在,才賦予了Java強大的面向對象能力。abstractclass和interface之間在對于抽象類定義的支持方面具有很大的相似性,甚至可以相互替換,因此很多開發(fā)者在進行抽象類定義時對于abstractclass和interface的選擇顯得比擬隨意。其實,兩者之間還是有很大的區(qū)別的,對于它們的選擇甚至反映出對于問題領域本質的理解、對于設計意圖的理解是否正確、合理。本文將對它們之間的區(qū)別進行一番剖析,試圖給開發(fā)者提供一個在二者之間進行選擇的依據(jù)。理解抽象類abstractclass和interface在Java語言中都是用來進行抽象類〔本文中的抽象類并非從abstractclass翻譯而來,它表示的是一個抽象體,而abstractclass為Java語言中用于定義抽象類的一種方法,請讀者注意區(qū)分〕定義的,那么什么是抽象類,使用抽象類能為我們帶來什么好處呢?在面向對象的概念中,我們知道所有的對象都是通過類來描繪的,但是反過來卻不是這樣。并不是所有的類都是用來描繪對象的,如果一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類。抽象類往往用來表征我們在對問題領域進行分析、設計中得出的抽象概念,是對一系列看上去不同,但是本質上相同的具體概念的抽象。比方:如果我們進行一個圖形編輯軟件的開發(fā),就會發(fā)現(xiàn)問題領域存在著圓、三角形這樣一些具體概念,它們是不同的,但是它們又都屬于形狀這樣一個概念,形狀這個概念在問題領域是不存在的,它就是一個抽象概念。正是因為抽象的概念在問題領域沒有對應的具體概念,所以用以表征抽象概念的抽象類是不能夠實例化的。在面向對象領域,抽象類主要用來進行類型隱藏。我們可以構造出一個固定的一組行為的抽象描述,但是這組行為卻能夠有任意個可能的具體實現(xiàn)方式。這個抽象描述就是抽象類,而這一組任意個可能的具體實現(xiàn)那么表現(xiàn)為所有可能的派生類。模塊可以操作一個抽象體。由于模塊依賴于一個固定的抽象體,因此它可以是不允許修改的;同時,通過從這個抽象體派生,也可擴展此模塊的行為功能。熟悉OCP的讀者一定知道,為了能夠實現(xiàn)面向對象設計的一個最核心的原那么OCP(Open-ClosedPrinciple),抽象類是其中的關鍵所在。從語法定義層面看abstractclass和interface在語法層面,Java語言對于abstractclass和interface給出了不同的定義方式,下面以定義一個名為Demo的抽象類為例來說明這種不同。使用abstractclass的方式定義Demo抽象類的方式如下:abstractclassDemo{

abstractvoidmethod1();

abstractvoidmethod2();

}使用interface的方式定義Demo抽象類的方式如下:interfaceDemo{

voidmethod1();

voidmethod2();

}在abstractclass方式中,Demo可以有自己的數(shù)據(jù)成員,也可以有非abstract的成員方法,而在interface方式的實現(xiàn)中,Demo只能夠有靜態(tài)的不能被修改的數(shù)據(jù)成員〔也就是必須是staticfinal的,不過在interface中一般不定義數(shù)據(jù)成員〕,所有的成員方法都是abstract的。從某種意義上說,interface是一種特殊形式的abstractclass。從編程的角度來看,abstractclass和interface都可以用來實現(xiàn)"designbycontract"的思想。但是在具體的使用上面還是有一些區(qū)別的。首先,abstractclass在Java語言中表示的是一種繼承關系,一個類只能使用一次繼承關系(因為Java不支持多繼承--轉注)。但是,一個類卻可以實現(xiàn)多個interface。也許,這是Java語言的設計者在考慮Java對于多重繼承的支持方面的一種折中考慮吧。其次,在abstractclass的定義中,我們可以賦予方法的默認行為。但是在interface的定義中,方法卻不能擁有默認行為,為了繞過這個限制,必須使用委托,但是這會增加一些復雜性,有時會造成很大的麻煩。在抽象類中不能定義默認行為還存在另一個比擬嚴重的問題,那就是可能會造成維護上的麻煩。因為如果后來想修改類的界面〔一般通過abstractclass或者interface來表示〕以適應新的情況〔比方,添加新的方法或者給已用的方法中添加新的參數(shù)〕時,就會非常的麻煩,可能要花費很多的時間〔對于派生類很多的情況,尤為如此〕。但是如果界面是通過abstractclass來實現(xiàn)的,那么可能就只需要修改定義在abstractclass中的默認行為就可以了。同樣,如果不能在抽象類中定義默認行為,就會導致同樣的方法實現(xiàn)出現(xiàn)在該抽象類的每一個派生類中,違反了"onerule,oneplace"原那么,造成代碼重復,同樣不利于以后的維護。因此,在abstractclass和interface間進行選擇時要非常的小心。從設計理念層面看abstractclass和interface上面主要從語法定義和編程的角度論述了abstractclass和interface的區(qū)別,這些層面的區(qū)別是比擬低層次的、非本質的。本小節(jié)將從另一個層面:abstractclass和interface所反映出的設計理念,來分析一下二者的區(qū)別。作者認為,從這個層面進行分析才能理解二者概念的本質所在。前面已經(jīng)提到過,abstractclass在Java語言中表達了一種繼承關系,要想使得繼承關系合理,父類和派生類之間必須存在"is-a"關系,即父類和派生類在概念本質上應該是相同的。對于interface來說那么不然,并不要求interface的實現(xiàn)者和interface定義在概念本質上是一致的,僅僅是實現(xiàn)了interface定義的契約而已。為了使論述便于理解,下面將通過一個簡單的實例進行說明。考慮這樣一個例子,假設在我們的問題領域中有一個關于Door的抽象概念,該Door具有執(zhí)行兩個動作open和close,此時我們可以通過abstractclass或者interface來定義一個表示該抽象概念的類型,定義方式分別如下所示:使用abstractclass方式定義Door:abstractclassDoor{

abstractvoidopen();

abstractvoidclose();

}使用interface方式定義Door:interfaceDoor{

voidopen();

voidclose();

}其他具體的Door類型可以extends使用abstractclass方式定義的Door或者implements使用interface方式定義的Door??雌饋砗盟剖褂胊bstractclass和interface沒有大的區(qū)別。如果現(xiàn)在要求Door還要具有報警的功能。我們該如何設計針對該例子的類結構呢〔在本例中,主要是為了展示abstractclass和interface反映在設計理念上的區(qū)別,其他方面無關的問題都做了簡化或者忽略〕?下面將羅列出可能的解決方案,并從設計理念層面對這些不同的方案進行分析。解決方案一:簡單的在Door的定義中增加一個alarm方法,如下:abstractclassDoor{

abstractvoidopen();

abstractvoidclose();

abstractvoidalarm();

}或者interfaceDoor{

voidopen();

voidclose();

voidalarm();

}那么具有報警功能的AlarmDoor的定義方式如下:classAlarmDoorextendsDoor{

voidopen(){…}

voidclose(){…}

voidalarm(){…}

}或者classAlarmDoorimplementsDoor{

voidopen(){…}

voidclose(){…}

voidalarm(){…}

}這種方法違反了面向對象設計中的一個核心原那么ISP(InterfaceSegregationPrinciple),在Door的定義中把Door概念本身固有的行為方法和另外一個概念"報警器"的行為方法混在了一起。這樣引起的一個問題是那些僅僅依賴于Door這個概念的模塊會因為"報警器"這個概念的改變〔比方:修改alarm方法的參數(shù)〕而改變,反之依然。解決方案二:既然open、close和alarm屬于兩個不同的概念,根據(jù)ISP原那么應該把它們分別定義在代表這兩個概念的抽象類中。定義方式有:這兩個概念都使用abstractclass方式定義;兩個概念都使用interface方式定義;一個概念使用abstractclass方式定義,另一個概念使用interface方式定義。顯然,由于Java語言不支持多重繼承,所以兩個概念都使用abstractclass方式定義是不可行的。后面兩種方式都是可行的,但是對于它們的選擇卻反映出對于問題領域中的概念本質的理解、對于設計意圖的反映是否正確、合理。我們一一來分析、說明。如果兩個概念都使用interface方式來定義,那么就反映出兩個問題:1、我們可能沒有理解清楚問題領域,AlarmDoor在概念本質上到底是Door還是報警器?2、如果我們對于問題領域的理解沒有問題,比方:我們通過對于問題領域的分析發(fā)現(xiàn)AlarmDoor在概念本質上和Door是一致的,那么我們在實現(xiàn)時就沒有能夠正確的揭示我們的設計意圖,因為在這兩個概念的定義上〔均使用interface方式定義〕反映不出上述含義。如果我們對于問題領域的理解是:AlarmDoor在概念本質上是Door,同時它有具有報警的功能。我們該如何來設計、實現(xiàn)來明確的反映出我們的意思呢?前面已經(jīng)說過,abstractclass在Java語言中表示一種繼承關系,而繼承關系在本質上是"is-a"關系。所以對于Door這個概念,我們應該使用abstarctclass方式來定義。另外,AlarmDoor又具有報警功能,說明它又能夠完成報警概念中定義的行為,所以報警概念可以通過interface方式定義。如下所示:abstractclassDoor{

abstractvoidopen();

abstractvoidclose();

}

interfaceAlarm{

voidalarm();

}

classAlarmDoorextendsDoorimplementsAlarm{

voidopen(){…}

voidclose(){…}

voidalarm(){…}

}這種實現(xiàn)方式根本上能夠明確的反映出我們對于問題領域的理解,正確的揭示我們的設計意圖。其實abstractclass表示的是"is-a"關系,interface表示的是"like-a"關系,大家在選擇時可以作為一個依據(jù),當然這是建立在對問題領域的理解上的,比方:如果我們認為AlarmDoor在概念本質上是報警器,同時又具有Door的功能,那么上述的定義方式就要反過來了。小結1.abstractclass在Java語言中表示的是一種繼承關系,一個類只能使用一次繼承關系。但是,一個類卻可以實現(xiàn)多個interface。2.在abstractclass中可以有自己的數(shù)據(jù)成員,也可以有非abstarct的成員方法,而在interface中,只能夠有靜態(tài)的不能被修改的數(shù)據(jù)成員〔也就是必須是staticfinal的,不過在interface中一般不定義數(shù)據(jù)成員〕,所有的成員方法都是abstract的。3.abstractclass和interface所反映出的設計理念不同。其實abstractclass表示的是"is-a"關系,interface表示的是"like-a"關系。4.實現(xiàn)抽象類和接口的類必須實現(xiàn)其中的所有方法。抽象類中可以有非抽象方法。接口中那么不能有實現(xiàn)方法。5.接口中定義的變量默認是publicstaticfinal型,且必須給其初值,所以實現(xiàn)類中不能重新定義,也不能改變其值。6.抽象類中的變量默認是friendly型,其值可以在子類中重新定義,也可以重新賦值。7.接口中的方法默認都是public,abstract類型的。結論abstractclass和interface是Java語言中的兩種定義抽象類的方式,它們之間有很大的相似性。但是對于它們的選擇卻又往往反映出對于問題領域中的概念本質的理解、對于設計意圖的反映是否正確、合理,因為它們表現(xiàn)了概念間的不同的關系〔雖然都能夠實現(xiàn)需求的功能〕。這其實也是語言的一種的慣用法,希望讀者朋友能夠細細體會.hash算法的實現(xiàn)原理,hashcode的實現(xiàn)原理1.引言哈希表〔HashTable〕的應用近兩年才在NOI中出現(xiàn),作為一種高效的數(shù)據(jù)結構,它正在競賽中發(fā)揮著越來越重要的作用。哈希表最大的優(yōu)點,就是把數(shù)據(jù)的存儲和查找消耗的時間大大降低,幾乎可以看成是常數(shù)時間;而代價僅僅是消耗比擬多的內存。然而在當前可利用內存越來越多的情況下,用空間換時間的做法是值得的。另外,編碼比擬容易也是它的特點之一。哈希表又叫做散列表,分為“開散列”和“閉散列”。考慮到競賽時多數(shù)人通常防止使用動態(tài)存儲結構,本文中的“哈希表”僅指“閉散列”,關于其他方面讀者可參閱其他書籍。

2.根底操作

2.1根本原理我們使用一個下標范圍比擬大的數(shù)組來存儲元素??梢栽O計一個函數(shù)〔哈希函數(shù),也叫做散列函數(shù)〕,使得每個元素的關鍵字都與一個函數(shù)值〔即數(shù)組下標〕相對應,于是用這個數(shù)組單元來存儲這個元素;也可以簡單的理解為,按照關鍵字為每一個元素“分類”,然后將這個元素存儲在相應“類”所對應的地方。但是,不能夠保證每個元素的關鍵字與函數(shù)值是一一對應的,因此極有可能出現(xiàn)對于不同的元素,卻計算出了相同的函數(shù)值,這樣就產生了“沖突”,換句話說,就是把不同的元素分在了相同的“類”之中。后面我們將看到一種解決“沖突”的簡便做法??偟膩碚f,“直接定址”與“解決沖突”是哈希表的兩大特點。2.2函數(shù)構造構造函數(shù)的常用方法〔下面為了表達簡潔,設h(k)表示關鍵字為k的元素所對應的函數(shù)值〕:

a)除余法:選擇一個適當?shù)恼麛?shù)p,令h(k)=kmodp,這里,p如果選取的是比擬大的素數(shù),效果比擬好。而且此法非常容易實現(xiàn),因此是最常用的方法。

b)數(shù)字選擇法:如果關鍵字的位數(shù)比擬多,超過長整型范圍而無法直接運算,可以選擇其中數(shù)字分布比擬均勻的假設干位,所組成的新的值作為關鍵字或者直接作為函數(shù)值。2.3沖突處理線性重新散列技術易于實現(xiàn)且可以較好的到達目的。令數(shù)組元素個數(shù)為S,那么當h(k)已經(jīng)存儲了元素的時候,依次探查(h(k)+i)modS,i=1,2,3……,直到找到空的存儲單元為止〔或者從頭到尾掃描一圈仍未發(fā)現(xiàn)空單元,這就是哈希表已經(jīng)滿了,發(fā)生了錯誤。當然這是可以通過擴大數(shù)組范圍防止的〕。2.4支持運算哈希表支持的運算主要有:初始化(makenull)、哈希函數(shù)值的運算(h(x))、插入元素(insert)、查找元素(member)。設插入的元素的關鍵字為x,A為存儲的數(shù)組。初始化比擬容易,例如:

constempty=maxlongint;//用非常大的整數(shù)代表這個位置沒有存儲元素

p=9997;//表的大小

proceduremakenull;

vari:integer;

begin

fori:=0top-1do

A[i]:=empty;

End;

哈希函數(shù)值的運算根據(jù)函數(shù)的不同而變化,例如除余法的一個例子:

functionh(x:longint):Integer;

begin

h:=xmodp;

end;

我們注意到,插入和查找首先都需要對這個元素定位,即如果這個元素假設存在,它應該存儲在什么位置,因此參加一個定位的函數(shù)locate

functionlocate(x:longint):integer;

varorig,i:integer;

begin

orig:=h(x);

i:=0;

while(i<S)and(A[(orig+i)modS]<>x)and(A[(orig+i)modS]<>empty)do

inc(i);

//當這個循環(huán)停下來時,要么找到一個空的存儲單元,要么找到這個元

//素存儲的單元,要么表已經(jīng)滿了

locate:=(orig+i)modS;

end;

插入元素

procedureinsert(x:longint);

varposi:integer;

begin

posi:=locate(x);//定位函數(shù)的返回值ifA[posi]=emptythenA[posi]:=x

elseerror;//error即為發(fā)生了錯誤,當然這是可以防止的

end;

查找元素是否已經(jīng)在表中

proceduremember(x:longint):boolean;

varposi:integer;

begin

posi:=locate(x);

ifA[posi]=xthenmember:=true

elsemember:=false;

end;

這些就是建立在哈希表上的常用根本運算。初步結論:當數(shù)據(jù)規(guī)模接近哈希表上界或者下界的時候,哈希表完全不能夠表達高效的特點,甚至還不如一般算法。但是如果規(guī)模在中央,它高效的特點可以充分表達。試驗說明當元素充滿哈希表的90%的時候,效率就已經(jīng)開始明顯下降。這就給了我們提示:如果確定使用哈希表,應該盡量使數(shù)組開大,但對最太大的數(shù)組進行操作也比擬費時間,需要找到一個平衡點。通常使它的容量至少是題目最大需求的120%,效果比擬好〔這個僅僅是經(jīng)驗,沒有嚴格證明〕。4.應用舉例

4.1應用的簡單原那么什么時候適合應用哈希表呢?如果發(fā)現(xiàn)解決這個問題時經(jīng)常要詢問:“某個元素是否在集合中?”,也就是需要高效的數(shù)據(jù)存儲和查找,那么使用哈希表是最好不過的了!那么,在應用哈希表的過程中,值得注意的是什么呢?哈希函數(shù)的設計很重要。一個不好的哈希函數(shù),就是指造成很多沖突的情況,從前面的例子已經(jīng)可以看出來,解決沖突會浪費掉大量時間,因此我們的目標就是盡力防止沖突。前面提到,在使用“除余法”的時候,h(k)=kmodp,p最好是一個大素數(shù)。這就是為了盡力防止沖突。為什么呢?假設p=1000,那么哈希函數(shù)分類的標準實際上就變成了按照末三位數(shù)分類,這樣最多1000類,沖突會很多。一般地說,如果p的約數(shù)越多,那么沖突的幾率就越大。簡單的證明:假設p是一個有較多約數(shù)的數(shù),同時在數(shù)據(jù)中存在q滿足**(p,q)=d>1,即有p=a*d,q=b*d,那么有qmodp=q–p*[qdivp]=q–p*[bdiva].①其中[bdiva]的取值范圍是不會超過[0,b]的正整數(shù)。也就是說,[bdiva]的值只有b+1種可能,而p是一個預先確定的數(shù)。因此①式的值就只有b+1種可能了。這樣,雖然mod運算之后的余數(shù)仍然在[0,p-1]內,但是它的取值僅限于①可能取到的那些值。也就是說余數(shù)的分布變得不均勻了。容易看出,p的約數(shù)越多,發(fā)生這種余數(shù)分布不均勻的情況就越頻繁,沖突的幾率越高。而素數(shù)的約數(shù)是最少的,因此我們選用大素數(shù)。記住“素數(shù)是我們的得力助手”。另一方面,一味的追求低沖突率也不好。理論上,是可以設計出一個幾乎完美,幾乎沒有沖突的函數(shù)的。然而,這樣做顯然不值得,因為這樣的函數(shù)設計很浪費時間而且編碼一定很復雜,與其花費這么大的精力去設計函數(shù),還不如用一個雖然沖突多一些但是編碼簡單的函數(shù)。因此,函數(shù)還需要易于編碼,即易于實現(xiàn)。綜上所述,設計一個好的哈希函數(shù)是很關鍵的。而“好”的標準,就是較低的沖突率和易于實現(xiàn)。另外,使用哈希表并不是記住了前面的根本操作就能以不變應萬變的。有的時候,需要按照題目的要求對哈希表的結構作一些改良。往往一些簡單的改良就可以帶來巨大的方便。這些只是一般原那么,真正遇到試題的時候實際情況千變萬化,需要具體問題具體分析才行。error和exception的區(qū)別,RuntimeException和非RuntimeException的區(qū)別1.異常機制異常機制是指當程序出現(xiàn)錯誤后,程序如何處理。具體來說,異常機制提供了程序退出的平安通道。當出現(xiàn)錯誤后,程序執(zhí)行的流程發(fā)生改變,程序的控制權轉移到異常處理器。傳統(tǒng)的處理異常的方法是,函數(shù)返回一個特殊的結果來表示出現(xiàn)異?!餐ǔ_@個特殊結果是大家約定俗稱的〕,調用該函數(shù)的程序負責檢查并分析函數(shù)返回的結果。這樣做有如下的弊端:例如函數(shù)返回-1代表出現(xiàn)異常,但是如果函數(shù)確實要返回-1這個正確的值時就會出現(xiàn)混淆;可讀性降低,將程序代碼與處理異常的代碼混爹在一起;由調用函數(shù)的程序來分析錯誤,這就要求客戶程序員對庫函數(shù)有很深的了解。異常處理的流程:①遇到錯誤,方法立即結束,并不返回一個值;同時,拋出一個異常對象。②調用該方法的程序也不會繼續(xù)執(zhí)行下去,而是搜索一個可以處理該異常的異常處理器,并執(zhí)行其中的代碼。2異常的分類異常的分類:①異常的繼承結構:基類為Throwable,Error和Exception繼承Throwable,RuntimeException和IOException等繼承Exception,具體的RuntimeException繼承RuntimeException。②Error和RuntimeException及其子類成為未檢查異常〔unchecked〕,其它異常成為已檢查異?!瞔hecked〕。每個類型的異常的特點

Error體系:

Error類體系描述了Java運行系統(tǒng)中的內部錯誤以及資源耗盡的情形。應用程序不應該拋出這種類型的對象〔一般是由虛擬機拋出〕。如果出現(xiàn)這種錯誤,除了盡力使程序平安退出外,在其他方面是無能為力的。所以,在進行程序設計時,應該更關注Exception體系。Exception體系包括RuntimeException體系和其他非RuntimeException的體系:①RuntimeException:RuntimeException體系包括錯誤的類型轉換、數(shù)組越界訪問和試圖訪問空指針等等。處理RuntimeException的原那么是:如果出現(xiàn)RuntimeException,那么一定是程序員的錯誤。例如,可以通過檢查數(shù)組下標和數(shù)組邊界來防止數(shù)組越界訪問異常。②其他非RuntimeException〔IOException等等〕:這類異常一般是外部錯誤,例如試圖從文件尾后讀取數(shù)據(jù)等,這并不是程序本身的錯誤,而是在應用環(huán)境中出現(xiàn)的外部錯誤。與C++異常分類的不同:①Java中RuntimeException這個類名起的并不恰當,因為任何異常都是運行時出現(xiàn)的?!苍诰幾g時出現(xiàn)的錯誤并不是異常,換句話說,異常就是為了解決程序運行時出現(xiàn)的的錯誤〕。②C++中l(wèi)ogic_error與Java中的RuntimeException是等價的,而runtime_error與Java中非RuntimeException類型的異常是等價的。3異常的使用方法聲明方法拋出異常①語法:throws〔略〕②為什么要聲明方法拋出異常?方法是否拋出異常與方法返回值的類型一樣重要。假設方法拋出異常確沒有聲明該方法將拋出異常,那么客戶程序員可以調用這個方法而且不用編寫處理異常的代碼。那么,一旦出現(xiàn)異常,那么這個異常就沒有適宜的異??刂破鱽斫鉀Q。③為什么拋出的異常一定是已檢查異常?

RuntimeException與Error可以在任何代碼中產生,它們不需要由程序員顯示的拋出,一旦出現(xiàn)錯誤,那么相應的異常會被自動拋出。而已檢查異常是由程序員拋出的,這分為兩種情況:客戶程序員調用會拋出異常的庫函數(shù)〔庫函數(shù)的異常由庫程序員拋出〕;客戶程序員自己使用throw語句拋出異常。遇到Error,程序員一般是無能為力的;遇到RuntimeException,那么一定是程序存在邏輯錯誤,要對程序進行修改〔相當于調試的一種方法〕;只有已檢查異常才是程序員所關心的,程序應該且僅應該拋出或處理已檢查異常。注意:覆蓋父類某方法的子類方法不能拋出比父類方法更多的異常,所以,有時設計父類的方法時會聲明拋出異常,但實際的實現(xiàn)方法的代碼卻并不拋出異常,這樣做的目的就是為了方便子類方法覆蓋父類方法時可以拋出異常。如何拋出異常①語法:throw〔略〕②拋出什么異常?對于一個異常對象,真正有用的信息時異常的對象類型,而異常對象本身毫無意義。比方一個異常對象的類型是ClassCastException,那么這個類名就是唯一有用的信息。所以,在選擇拋出什么異常時,最關鍵的就是選擇異常的類名能夠明確說明異常情況的類。③異常對象通常有兩種構造函數(shù):一種是無參數(shù)的構造函數(shù);另一種是帶一個字符串的構造函數(shù),這個字符串將作為這個異常對象除了類型名以外的額外說明。④創(chuàng)立自己的異常:當Java內置的異常都不能明確的說明異常情況的時候,需要創(chuàng)立自己的異常。需要注意的是,唯一有用的就是類型名這個信息,所以不要在異常類的設計上花費精力。捕獲異常如果一個異常沒有被處理,那么,對于一個非圖形界面的程序而言,該程序會被中止并輸出異常信息;對于一個圖形界面程序,也會輸出異常的信息,但是程序并不中止,而是返回用錯誤頁面。語法:try、catch和finally〔略〕,控制器模塊必須緊接在try塊后面。假設擲出一個異常,異常控制機制會搜尋參數(shù)與異常類型相符的第一個控制器隨后它會進入那個catch從句,并認為異常已得到控制。一旦catch從句結束對控制器的搜索也會停止。捕獲多個異常〔注意語法與捕獲的順序〕〔略〕

finally的用法與異常處理流程〔略〕異常處理做什么?對于Java來說,由于有了垃圾收集,所以異常處理并不需要回收內存。但是依然有一些資源需要程序員來收集,比方文件、網(wǎng)絡連接和圖片等資源。應該聲明方法拋出異常還是在方法中捕獲異常?原那么:捕捉并處理哪些知道如何處理的異常,而傳遞哪些不知道如何處理的異常。再次拋出異常①為什么要再次拋出異常?在本級中,只能處理一局部內容,有些處理需要在更高一級的環(huán)境中完成,所以應該再次拋出異常。這樣可以使每級的異常處理器處理它能夠處理的異常。②異常處理流程:對應與同一try塊的catch塊將被忽略,拋出的異常將進入更高的一級。4關于異常的其他問題①過度使用異常:首先,使用異常很方便,所以程序員一般不再愿意編寫處理錯誤的代碼,而僅僅是簡簡單單的拋出一個異常。這樣做是不對的,對于完全的錯誤,應該編寫處理這種錯誤的代碼,增加程序的魯棒性。另外,異常機制的效率很差。②將異常與普通錯誤區(qū)分開:對于普通的完全一致的錯誤,應該編寫處理這種錯誤的代碼,增加程序的魯棒性。只有外部的不能確定和預知的運行時錯誤才需要使用異常。③異常對象中包含的信息:一般情況下,異常對象唯一有用的信息就是類型信息。但使用異常帶字符串的構造函數(shù)時,這個字符串還可以作為額外的信息。調用異常對象的getMessage()、toString()或者printStackTrace()方法可以分別得到異常對象的額外信息、類名和調用堆棧的信息。并且后一種包含的信息是前一種的超集。繼承與組合的區(qū)別,使用場景1、繼承假設我們有一個名為Insect〔昆蟲〕的類,這個類包含兩個方法:1〕移動move();2〕攻擊attack()。

代碼如下:12345678910111213141516171819202122232425262728293031323334classInsect{

privateintsize;

privateStringcolor;

publicInsect(intsize,Stringcolor){

this.size=size;

this.color=color;

}

publicintgetSize(){

returnsize;

}

publicvoidsetSize(intsize){

this.size=size;

}

publicStringgetColor(){

returncolor;

}

publicvoidsetColor(Stringcolor){

this.color=color;

}

publicvoidmove(){

System.out.println("Move");

}

publicvoidattack(){

move();

//假設昆蟲在攻擊前必須要先移動一次

System.out.println("Attack");

}}現(xiàn)在,你想要定義一個名為Bee〔蜜蜂〕的類。Bee〔蜜蜂〕是Insect〔昆蟲〕的一種,但實現(xiàn)了不同于Insect〔昆蟲〕的attack()和move方法。這時候我們可以用繼承的設計機制來實現(xiàn)Bee類,就像下面的代碼一樣:1234567891011121314classBeeextendsInsect{

publicBee(intsize,Stringcolor){

super(size,color);

}

publicvoidmove(){

System.out.println("Fly");

}

publicvoidattack(){

move();

super.attack();

}}123456publicclassInheritanceVSComposition{

publicstaticvoidmain(String[]args){

Insecti=newBee(1,"red");

i.attack();

}}InheritanceVSComposition作為一個測試類,在其main方法中生成了一個Bee類的實例,并賦值給Insect類型的引用變量i。所以調用i的attack方法時,對應的是Bee類實例的attack方法,也就是調用了Bee類的attack方法。類的繼承結構圖如下,非常簡單:輸出:123FlyFlyAttackFly被打印了兩次,也就是說move方法被調用了兩次。但按理來講,move方法只應當被調用一次,因為無論是昆蟲還是蜜蜂,一次攻擊前只移動一次。問題出在子類〔即Bee類〕的attack方法的重載代碼中,也就是super.attack()這一句。因為在父類〔即Insect類〕中,調用attack方法時會先調用move方法,所以當子類〔Bee〕調用super.attack()時,相當于也同時調用了被重載的move方法〔注意是子類被重載的move方法,而不是父類的move方法〕。為了解決這個問題,我們可以采取以下方法:刪除子類的attack方法。這么做會使得子類的attack方法的實現(xiàn)完全依賴于父類對于該方法的實現(xiàn)〔因為子類繼承了父類的attack方法〕。如果父類的attack方法不受控制而產生了變更。比方說,父類的attack方法中調用了另外的move方法,那么子類的attack方法也會產生相應的變化,這是一種很糟糕的封裝。也可以重寫子類的attack方法,像下面這樣:1234publicvoidattack(){

move();

System.out.println("Attack");}這樣保證了結果的正確性,因為子類的attack方法不再依賴于父類。但是,子類attack方法的代碼與父類產生了重復〔重復的attack方法會使得很多事情變得復雜,不僅僅是多打印了一條輸出語句〕。所以第二種方法也不行,它不符合軟件工程中關于重用的思想。如此看來,繼承機制是有缺點的:子類依賴于父類的實現(xiàn)細節(jié),如果父類產生了變更,子類的后果將不堪設想。2、組合在上面的例子中,可以用組合的機制來替代繼承。我們先看一下運用組合如何實現(xiàn)。attack這一功能不再是一個方法,而是被抽象為一個接口。1234interfaceAttack{

publicvoidmove();

publicvoidattack();}通過對Attack接口的實現(xiàn),就可以在實現(xiàn)類當中定義不同

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論