版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
《Java組件設(shè)計(jì)》
本書主要面向軟件架構(gòu)師、設(shè)計(jì)師、高級(jí)開發(fā)人員,講解企業(yè)應(yīng)用中核心組件的設(shè)計(jì)原則與實(shí)踐。
本書將澄清設(shè)計(jì)模式、數(shù)據(jù)結(jié)構(gòu)、多線程、接口設(shè)計(jì)等多個(gè)領(lǐng)域中的常見誤區(qū),通過大量的實(shí)例分析,
為讀者精彩講解組件設(shè)計(jì)這一最具技術(shù)含量的領(lǐng)域需要考慮的問題、設(shè)計(jì)方案與最佳實(shí)踐.
章節(jié)目錄,暫定如下:
第一章:組件設(shè)計(jì)概述
第二章:組件設(shè)計(jì)原則
第三章:預(yù)備知識(shí)
第四章:配置文件
第五章:Tcp通信組件
第六章:日志組件
第七章:數(shù)據(jù)庫訪問組件
第八章:Web服務(wù)組件
1組件設(shè)計(jì)概述
編寫計(jì)算機(jī)軟件的人很多,我們通常都把這些活動(dòng)稱為軟件開發(fā)。但是軟件的種類是不同的,每種軟
件都有自身的復(fù)雜性和挑戰(zhàn)性。本人一直工作在電信行業(yè),電信行業(yè)的軟件非常復(fù)雜,對(duì)并發(fā)、大數(shù)
據(jù)量、性能、高可靠性要求很高,這些都對(duì)軟件的設(shè)計(jì)和開發(fā)提出了嚴(yán)峻的挑戰(zhàn)。
1.1應(yīng)用軟件結(jié)構(gòu)
通常,應(yīng)用軟件的總體結(jié)構(gòu),可以分為兩大部分:應(yīng)用層和平臺(tái)層,具體如下:
平臺(tái)層提供基礎(chǔ)框架和大量可重用組件,這些組件以一定的接口方式暴露出來,供應(yīng)用層來調(diào)用。平
臺(tái)層通常不提供與具體業(yè)務(wù)相關(guān)的邏輯處理,而是提供:
1)業(yè)務(wù)無關(guān)的框架/功能組件:比如日志、安全、線程池、連接池、告警監(jiān)控等
2)多種業(yè)務(wù)之間可共用的機(jī)制:如工作流、事件通知機(jī)制等,這部分也與具體的業(yè)務(wù)無關(guān)。
應(yīng)用層提供具體應(yīng)用相關(guān)的邏輯處理部分,包括頁面、應(yīng)用邏輯、應(yīng)用數(shù)據(jù)等。
平臺(tái)層和應(yīng)用層,是個(gè)邏輯劃分的概念,實(shí)際軟件實(shí)現(xiàn)中,平臺(tái)層和應(yīng)用層都可以由多個(gè)層來實(shí)現(xiàn),
也可以合并到一個(gè)程序中,這要視項(xiàng)目的規(guī)模和具體需求而定。
從上圖可以看出,構(gòu)建一個(gè)高度可重用的平臺(tái)層,可以使應(yīng)用開發(fā)只需集中精力關(guān)注業(yè)務(wù)邏輯,業(yè)務(wù)
無關(guān)的功能組件和機(jī)制都由平臺(tái)層提供,可以直接使用,這樣極大簡(jiǎn)化了應(yīng)用開發(fā),縮短了軟件交付
周期,保障了軟件質(zhì)量。
而構(gòu)建一個(gè)高度可重用的平臺(tái)層,最核心的挑戰(zhàn)就是設(shè)計(jì)和開發(fā)高度可重用的組件,提取應(yīng)用的共性
需求,簡(jiǎn)化接口,真正做到應(yīng)用開發(fā)時(shí)可以直接拿來就用,而且非常好用。
1.2組件定義
那么,到底什么是組件呢?框架又是什么意思?類是組件嗎?控件又指什么?元件、構(gòu)件這些概念又
如何理解?
這些概念,都沒有一個(gè)統(tǒng)一的標(biāo)準(zhǔn)答案,因此在軟件開發(fā)過程中,這些術(shù)語經(jīng)常被混淆,作者根據(jù)自
己的工作體會(huì),對(duì)這些概念解釋如下:
1)對(duì)象:面向?qū)ο笳Z言(Object-OrientedLanguage)是一類以對(duì)象作為基本程序結(jié)構(gòu)單位的程序設(shè)計(jì)
語言,指用于描述的設(shè)計(jì)是以對(duì)象為核心,而對(duì)象是程序運(yùn)行時(shí)刻的基本成分。對(duì)象都具有狀態(tài)和行
為。對(duì)象,也被翻譯為實(shí)例、物件。
2)類:面向?qū)ο蟮母拍?。類是一種對(duì)包括數(shù)據(jù)成員、函數(shù)成員和嵌套類型進(jìn)行封裝的數(shù)據(jù)結(jié)構(gòu)。在程
序運(yùn)行期間,由類創(chuàng)建成對(duì)象的過程,叫類的實(shí)例化。因此,對(duì)象是個(gè)運(yùn)行期的術(shù)語,類是個(gè)編譯期
的術(shù)語。類本身就是可重用的直接實(shí)現(xiàn)手段.
3)組件:類本身是個(gè)細(xì)粒度的可重用實(shí)現(xiàn),為了解決功能或機(jī)制層面更大粒度重用的問題,又引入了
組件的概念。組件的英文是Component,其邏輯結(jié)構(gòu)如下:
組件對(duì)外暴露一個(gè)或多個(gè)接口,供外界調(diào)用。組件內(nèi)部由多個(gè)類來協(xié)同實(shí)現(xiàn)指定的功能。對(duì)于復(fù)雜的
組件,會(huì)包括很多的類,還可能包含配置文件、界面、依賴的庫文件等,組件也可以包含或者使用其
它的組件,構(gòu)成更大的組件。
一些特定范疇的組件,由軟件廠家或者國(guó)際權(quán)威組織制定并頒布了組件規(guī)范,如COM、ActiveX.EJB、
JavaBean等。本書討論的組件,指一般意義的自定義組件,不包括這些規(guī)范化的組件。
4)控件:控件的英文是Control,控件就是具有用戶界面的組件。要說的具體一點(diǎn),就得回顧早期
Windows的歷史根源,當(dāng)時(shí)控件指任何子窗口:按鈕、列表框、編輯框或者某個(gè)對(duì)話框中的靜態(tài)文本。
從概念上講,這些窗口一一控件一一類似用來操作收音機(jī)或小電器的旋鈕和按鈕。隨著控件數(shù)量的增
加(組合框、日期時(shí)間控件等等),控件逐漸成為子窗口的代名詞,無論是用在對(duì)話框中還是用在其
它種類的主窗口中。沒過多久BASIC程序員開始編寫他們自己專用的控件,自然而然地人們便想到
共享這些控件。共享代碼的方法之一是通過磁盤拷貝,但那樣顯然效率低下。必須要有一種機(jī)制使開
發(fā)者建立的控件能夠在其它程序員的應(yīng)用中輕而易舉地插入,這便是VBA控件,OLE控件,OCX和最
后ActiveX控件產(chǎn)生的動(dòng)機(jī)。因此,控件是組件的一個(gè)主要樣本(并且歷史上曾驅(qū)動(dòng)著組件的開發(fā)),
控件又不僅僅是唯一的一種組件。
5)元件:元件是個(gè)電子行業(yè)的術(shù)語,是電子元件的簡(jiǎn)稱。也有一些軟件借用這個(gè)術(shù)語,指特定的可重
用控件。
6)構(gòu)件:構(gòu)件是系統(tǒng)中實(shí)際存在的可更換部分,它實(shí)現(xiàn)特定的功能,符合一套接口標(biāo)準(zhǔn)并實(shí)現(xiàn)一組接
口。構(gòu)件代表系統(tǒng)中的一部分物理實(shí)施,包括軟件代碼(源代碼、二進(jìn)制代碼或可執(zhí)行代碼)或其等
價(jià)物(如腳本或命令文件)。
通常認(rèn)為,構(gòu)件是特定軟件開發(fā)環(huán)境中、滿足指定規(guī)范的軟件部分,其涵蓋了設(shè)計(jì)、開發(fā)、物理實(shí)施
等范疇.
7)框架:框架的英文是Framework,框架是一個(gè)應(yīng)用程序的半成品。框架提供了可在應(yīng)用程序之間共
享的可復(fù)用的公共結(jié)構(gòu)。開發(fā)者把框架融入他們自己的應(yīng)用程序,并加以擴(kuò)展,以滿足他們特定的需
要■框架和工具包的不同之處在于,框架提供了一致的結(jié)構(gòu),而不僅僅是一組工具類.(摘自?JUnit
inaction中文版》).
8)子系統(tǒng):子系統(tǒng)是個(gè)設(shè)計(jì)中使用的術(shù)語,英文是SubSystem。在軟件總體架構(gòu)中,按照功能劃分子
系統(tǒng).通常一個(gè)子系統(tǒng)包含一個(gè)或多個(gè)進(jìn)程。
9)模塊:模塊是個(gè)設(shè)計(jì)中使用的術(shù)語,英文是Module.模塊指一個(gè)子系統(tǒng)內(nèi)部,按照軟件結(jié)構(gòu)分解
的功能部分.模塊會(huì)包含多個(gè)類、使用多個(gè)組件,也會(huì)與框架交互。
一個(gè)真正的軟件系統(tǒng),會(huì)涉及到以上的多個(gè)概念,典型的軟件系統(tǒng)靜態(tài)結(jié)構(gòu)圖如下:
子系統(tǒng)1子系統(tǒng)2
框
架
1
上圖展示了一個(gè)軟件系統(tǒng),包括2個(gè)子系統(tǒng),子系統(tǒng)1和子系統(tǒng)20子系統(tǒng)1調(diào)用子系統(tǒng)2。
子系統(tǒng)1包含2個(gè)模塊:模塊1和模塊2。模塊1由兩個(gè)類(Class1和Class2)和一個(gè)組件(組件1)構(gòu)
成,模塊2由兩個(gè)類(Class3和Class4)和兩個(gè)組件(組件2和組件3)構(gòu)成,模塊2提供一個(gè)接
口給模塊1調(diào)用。模塊1和模塊2都使用了框架1。
子系統(tǒng)2包含2個(gè)模塊:模塊3和模塊4。模塊3由兩個(gè)類(Class5和Class6)和一個(gè)組件(組件4)
構(gòu)成,模塊4由一個(gè)類(Class7)和一個(gè)組件(組件5)構(gòu)成。模塊4提供一個(gè)接口給模塊3調(diào)用。
模塊3和模塊4都使用了框架2。
2組件設(shè)計(jì)原則
Java陣營(yíng)一直倡導(dǎo)開源,開源運(yùn)動(dòng)如火如荼展開,催生了無數(shù)組件。但是,坦率的講,這些開源的組件
中,能夠直接拿過來,不做任何改造,就能用于商業(yè)軟件構(gòu)建,滿足功能和性能的要求,這樣的優(yōu)秀
組件不多見。因此,核心軟件開發(fā)者時(shí)常面對(duì)的尷尬局面是:大量的開源資源,都不滿足我的要求。
實(shí)際上,組件設(shè)計(jì)是軟件設(shè)計(jì)開發(fā)最精髓所在,凝聚了數(shù)據(jù)結(jié)構(gòu)、面向?qū)ο?、設(shè)計(jì)模式、線程并發(fā)同步、
操作系統(tǒng)等諸多領(lǐng)域的最核心技術(shù),一直是設(shè)計(jì)開發(fā)領(lǐng)域彰顯技術(shù)水準(zhǔn)的最高領(lǐng)地。
一個(gè)組件,要想被廣泛重用,滿足不同系統(tǒng)的使用要求,就要求組件有足夠的能力適應(yīng)不同的應(yīng)用場(chǎng)合,
提供滿足需要的功能,并表現(xiàn)出優(yōu)秀的性能。因此,組件設(shè)計(jì)過程中,組件設(shè)計(jì)者要考慮的問題非常
廣泛而復(fù)雜,組件設(shè)計(jì)者需要具備的知識(shí)、技能和經(jīng)驗(yàn)要求非常高,一般工作經(jīng)驗(yàn)至少在5年以上才
有可能涉足組件設(shè)計(jì)這個(gè)領(lǐng)域.這也就解釋了,為什么優(yōu)秀組件不多的原因了。
本章將對(duì)組件設(shè)計(jì)過程中要考慮的核心要素、設(shè)計(jì)中要遵循的核心原則進(jìn)行總體闡述,使讀者能從總體
上掌握如何發(fā)現(xiàn)、評(píng)判、設(shè)計(jì)一個(gè)優(yōu)秀的組件。
本章將對(duì)目前業(yè)界存在的諸多技術(shù)爭(zhēng)論、誤區(qū)進(jìn)行澄清,讓讀者從所謂的“業(yè)界潮流”、教條、“黃金
定律”中走出來,真正理解組件設(shè)計(jì)過程的核心原則.這些核心原則如下:
原則一、精準(zhǔn)解決共性問題
原則二、無配置文件
原則三、與使用者概念一致
原則四、業(yè)務(wù)無關(guān)的中立性
原則五、對(duì)使用環(huán)境無依賴
原則六:?jiǎn)晤愒O(shè)計(jì)和實(shí)現(xiàn)
下面來詳細(xì)講解每個(gè)核心原則。
2.1組件定位:精準(zhǔn)解決共性問題
組件的產(chǎn)生,來源于軟件工程實(shí)踐中,對(duì)重復(fù)、反復(fù)出現(xiàn)、普遍的、有相似性的問題進(jìn)行分析,剝離掉
各個(gè)問題的特性,抽取各個(gè)問題之間的共性,然后確定要設(shè)計(jì)一個(gè)或多個(gè)組件,這樣就確定了組件要
解決的問題,而這個(gè)問題必然是共性的問題,不能是個(gè)性、特例的問題。如果不是共性的問題,那么
寫完后的代碼就只能在一種或有限的幾種情況下才能被使用,這樣就無法實(shí)現(xiàn)大規(guī)模復(fù)用。不能廣泛
的被復(fù)用,就不能叫組件。
因此,組件首先是業(yè)務(wù)驅(qū)動(dòng)的,不是技術(shù)驅(qū)動(dòng)的。不能因?yàn)槲覀冇辛诵碌募夹g(shù),就想著要寫幾個(gè)新的組
件。共性的業(yè)務(wù)問題,是組件的產(chǎn)生來源。
另外,即使確定了組件要解決的問題,組件還必須提供解決問題最合理、最有效的方式和方法。如果提
供的方法不是最好的,那么也很難得到使用者的喜歡,最終也難以推廣和復(fù)用。因此,組件設(shè)計(jì)上,
必須提供解決問題的精準(zhǔn)思路和方案.這是決定同類別的組件中,哪個(gè)組件能勝出的最主要原因。
一個(gè)組件提供的問題解決方法,是否最有效,主要評(píng)價(jià)原則如下:
1)技術(shù)與業(yè)務(wù)對(duì)齊:技術(shù)結(jié)構(gòu)和技術(shù)概念要與業(yè)務(wù)上的模型、概念保持一致,在組件對(duì)外暴露的接
口上,最好不要引入新的技術(shù)術(shù)語和概念,這樣的組件最吻合業(yè)務(wù)的需求,也最容易理解。技術(shù)與業(yè)
務(wù)對(duì)齊,這是組件設(shè)計(jì)的第一位重要原則。
2)技術(shù)方案的優(yōu)勢(shì):這是結(jié)構(gòu)設(shè)計(jì)、技術(shù)選型范疇內(nèi)要考慮的問題。實(shí)現(xiàn)同一個(gè)功能,可以有很多
不同的結(jié)構(gòu)設(shè)計(jì),也有很多不同的技術(shù)可以采用,優(yōu)秀的組件,一定在技術(shù)方案上有明顯的技術(shù)優(yōu)勢(shì)。
3)接口簡(jiǎn)單:組件暴露出來供外界使用的接口,必須足夠簡(jiǎn)單。在概念和模型層面上與業(yè)務(wù)保持一
致,另外接口封裝、抽象上要仔細(xì)推敲,保證接口提供給別人使用時(shí),調(diào)用者足夠簡(jiǎn)單的使用。
4)功能強(qiáng)大:組件如果提供的功能太少,意味著能幫用戶解決的問題比較少,因此這樣的組件實(shí)用
性大打折扣。如果組件的功能過多,就會(huì)讓用戶覺得這個(gè)組件非常臃腫和龐大,用戶只用了其中一小
部分功能,其它大部分功能都用不上或者很少用,這樣用戶要在一本厚厚的使用手冊(cè)中,仔細(xì)尋找自
己需要的部分,去除自己不需要的部分,這也算不上一個(gè)優(yōu)秀的組件。因此,一個(gè)組件提供的功能,
既要能滿足多種場(chǎng)景下不同客戶的需求,還要在不同的需求中進(jìn)行取舍和合并,使提供的功能集不會(huì)
超出客戶實(shí)際使用需求太多。
2.2組件設(shè)計(jì):無配置文件
組件自己不要帶任何配置文件,組件所依賴的各種庫也不帶任何配置文件。
這個(gè)原則極為重要!??!
如果一個(gè)組件,自己定義了配置文件,無論是XML,Properties,還是其他的文件類型,內(nèi)部的格式
都是組件設(shè)計(jì)者自己定義的。我們假設(shè)一個(gè)中等的項(xiàng)目,會(huì)使用10個(gè)組件,那么就會(huì)有10個(gè)配置文
件,這些配置文件的格式各不相同.那么我們的軟件安裝手冊(cè)、維護(hù)手冊(cè)就要仔細(xì)描述這10個(gè)配置
文件的結(jié)構(gòu),我們的實(shí)施維護(hù)人員就要學(xué)會(huì)如何配置這10個(gè)文件.如果項(xiàng)目再大,需要集成50個(gè)組
件,那就需要50個(gè)配置文件,對(duì)手冊(cè)編寫人員和實(shí)施維護(hù)人員,這是怎樣的災(zāi)難?。?/p>
歸納起來,如果一個(gè)組件自己帶配置文件,帶來的不良影響主要有:
1)不同組件的配置文件格式不統(tǒng)一,開發(fā)者、手冊(cè)編寫人員、實(shí)施人員、維護(hù)人員要學(xué)習(xí)不同格式、
不同結(jié)構(gòu)、不同的配置方式,學(xué)習(xí)的成本大大增加;
2)同一個(gè)配置(比如數(shù)據(jù)庫連接串,IP地址等),由于各個(gè)組件的配置方式不同,則實(shí)施人員要在多
個(gè)配置文件中進(jìn)行配置,增加了工作量,又難于保證升級(jí)時(shí)全部更新一致;
3)如果后續(xù)發(fā)現(xiàn)更好的組件,要用其替換原有的組件,則新的組件的配置文件與原組件的配置文件
不同,則即使這兩個(gè)組件的功能一致,我們也必需要更新實(shí)施維護(hù)手冊(cè),實(shí)施維護(hù)人員又要重新學(xué)習(xí)。
因此,基于配置文件的組件,是非常不容易被集成的.一個(gè)應(yīng)用,無論其使用多少個(gè)組件,應(yīng)該始終
只有一個(gè)配置文件,用于集中配置這個(gè)應(yīng)用的所有參數(shù)。而每個(gè)組件,都是以接口或者類的方式提供
配置函數(shù),應(yīng)用集中讀取所有配置信息,然后通過組件的配置函數(shù),將配置參數(shù)設(shè)置到組件上。這樣,
將從根本上解決組件集成過程由組件配置文件引起的各種問題。
2.3組件設(shè)計(jì):與使用者概念一致
組件提供接口,給應(yīng)用來使用。在接口上表現(xiàn)出的概念術(shù)語、使用方式都應(yīng)用與使用者的概念術(shù)語、
使用方式完全對(duì)應(yīng),也就是,技術(shù)和業(yè)務(wù)對(duì)齊。
組件設(shè)計(jì)者常犯的毛病是,組件暴露的接口,引入了新的技術(shù)術(shù)語,這些術(shù)語在使用者的概念體系中
根本就不存在,因此使用者理解起來非常困難,造成極大的學(xué)習(xí)障礙。這些與使用者概念不一致的技
術(shù)術(shù)語,突出表現(xiàn)在接口命名、類名、函數(shù)名、參數(shù)名、返回值的含義等。
從本質(zhì)上講,組件接口上暴露的與使用者不一致的概念,要么是這個(gè)概念本身就是錯(cuò)誤的或不恰當(dāng)?shù)模?/p>
其不應(yīng)該存在,要么就是這是個(gè)內(nèi)部實(shí)現(xiàn)的概念,不應(yīng)該包括在接口上。
2.4組件設(shè)計(jì):業(yè)務(wù)無關(guān)的中立性
一個(gè)組件,要在不同應(yīng)用、不同場(chǎng)景下都能廣泛的重用,這是組件設(shè)計(jì)者必需要實(shí)現(xiàn)的目標(biāo)。但一個(gè)
組件的產(chǎn)生,往往來源于一個(gè)特定的項(xiàng)目、一個(gè)特定的業(yè)務(wù)場(chǎng)景,在實(shí)現(xiàn)業(yè)務(wù)和項(xiàng)目功能的時(shí)候,組
件設(shè)計(jì)者意識(shí)到,這個(gè)功能部分可以進(jìn)行抽取,形成一個(gè)可重用的組件。因此,這個(gè)抽取的過程,組
件設(shè)計(jì)者務(wù)必把與當(dāng)前這個(gè)項(xiàng)目、這個(gè)業(yè)務(wù)特有的部分剝離掉,保留一般的、共性的功能,并重新封
裝和調(diào)整接口,以滿足通用的使用場(chǎng)景。這樣,這個(gè)組件可以與業(yè)務(wù)無關(guān),保持自己的中立性,后續(xù)
可以在其它的應(yīng)用中被重用。
如何識(shí)別那些是業(yè)務(wù)特性,那些是一般的共性,這需要依賴組件設(shè)計(jì)者多年的經(jīng)驗(yàn)。
2.5組件設(shè)計(jì)實(shí)現(xiàn):對(duì)使用環(huán)境無依賴
組件的內(nèi)部實(shí)現(xiàn),是否可以對(duì)將來組件被使用的環(huán)境做些假設(shè)?比如,是在Servlet容器中運(yùn)行,
僅使用一個(gè)組件的實(shí)例,僅處理一個(gè)目錄…….?
如果組件設(shè)計(jì)者對(duì)組件將來被使用的環(huán)境做了一些假設(shè),認(rèn)為這些條件必然被滿足,那么組件的代碼
實(shí)現(xiàn)就會(huì)和這些假設(shè)條件相關(guān)。如果這些條件不成立,則組件無法被使用。比如,組件設(shè)計(jì)者認(rèn)為,
將來應(yīng)用中一個(gè)組件的實(shí)例就能滿足需要,因此組件就設(shè)計(jì)成了單實(shí)例。當(dāng)一個(gè)特殊的應(yīng)用出現(xiàn),需
要使用兩個(gè)組件實(shí)例來處理不同的場(chǎng)景,發(fā)現(xiàn)組件已經(jīng)被設(shè)計(jì)成單實(shí)例,無法滿足應(yīng)用的這種“特殊”
需求。
這種需求,真的很特殊嗎?
客觀上講,需求來自于具體的應(yīng)用,來自客戶的使用場(chǎng)景和要解決的問題。需求,是獨(dú)立與設(shè)計(jì)與實(shí)
現(xiàn)的,尤其是與組件的設(shè)計(jì)實(shí)現(xiàn)無關(guān)。組件設(shè)計(jì)者之所以認(rèn)為這種需求很“特殊”,是因?yàn)檫@個(gè)需求
超出了組件設(shè)計(jì)者原來所做的假設(shè)。根本上講,組件設(shè)計(jì)者不應(yīng)該對(duì)組件將來的使用環(huán)境做任何假設(shè),
這樣組件的使用場(chǎng)合僅受限于組件的能力集,只有應(yīng)用需要的功能在組件的能力集范圍內(nèi),各種環(huán)境
下的不同應(yīng)用都可以使用這個(gè)組件。
這樣,組件才可以被廣泛復(fù)用。
2.6組件設(shè)計(jì)實(shí)現(xiàn):?jiǎn)晤愒O(shè)計(jì)和實(shí)現(xiàn)
怎樣的組件,才是一個(gè)優(yōu)秀的組件?從組件使用者的角度,組件要簡(jiǎn)單,易于使用,并且功能強(qiáng)大。
那么組件怎樣才能簡(jiǎn)單,易于使用?
首先,前面講過,組件提供出來的接口,要與使用者的概念完全一致,這樣使用者就非常容易理解組
件的各個(gè)類、各個(gè)接口、各個(gè)方法、各個(gè)參數(shù),這樣就會(huì)易于使用。
但組件怎么才能簡(jiǎn)單呢?
最簡(jiǎn)單的組件,就是一個(gè)類。
一個(gè)類,就會(huì)比兩個(gè)類簡(jiǎn)單,兩個(gè)類就會(huì)比四個(gè)類簡(jiǎn)單。這個(gè)道理顯而易見,其核心精髓是面向?qū)ο?/p>
的基本設(shè)計(jì)原則:高內(nèi)聚、低耦合。要嚴(yán)格控制類的數(shù)量,邏輯相關(guān)度很高的,就不允許拆成多個(gè)類。
不支持將來變化的部分,就不提供接口。
組件,作為可重用的軟件,不會(huì)承載太多的功能,組件的規(guī)模不會(huì)很大。因此,最理想的情況,組件
就是單獨(dú)的一個(gè)類。組件使用者用起來,是極致的簡(jiǎn)單。
我們從網(wǎng)上下載開源的項(xiàng)目,打開源碼一看,豁,好家伙,一大堆的類和接口,不寫幾十個(gè)、幾百個(gè)
類好象就顯不出作者的水平。其實(shí)真有必要寫那么的多的類嗎?
高內(nèi)聚,高內(nèi)聚,單類組件,簡(jiǎn)單的極致!
3預(yù)備知識(shí)
本章主要講解一些組件開發(fā)中常用的技術(shù),作為后續(xù)章節(jié)講解組件設(shè)計(jì)的預(yù)備知識(shí)。
本章假設(shè)讀者已經(jīng)熟悉了Java語言的基本語法,并且熟練使用Java語言至少3年,因此,簡(jiǎn)單的語法知識(shí)不再講
述,僅講解一些高級(jí)主題。
3.1Java語法深入
本節(jié)對(duì)Java語法中一些高級(jí)主題進(jìn)行講解.
3.1.1static
static變量
本質(zhì)上講,static關(guān)鍵字聲明了一個(gè)全局變量。
盡管Java是一門純面向?qū)ο蟮恼Z言,語言規(guī)范中沒有全局變量這種說法,但static聲明的變量,從本質(zhì)上就等同于
C或C++語言中的全局變量.整個(gè)應(yīng)用程序就一個(gè)變量實(shí)例,任何對(duì)static變量進(jìn)行的更改,在程序的其它地方都可
見。
舉個(gè)例子如下:
publicclassStaticExample{
publicstaticintcounter;
}
counter是個(gè)整型變量,前面用static關(guān)鍵字修飾后,就變成了一個(gè)全局變量,在程序的代碼執(zhí)行之前,這個(gè)counter
變量就已經(jīng)在內(nèi)存中存在了,而且是唯一的一份實(shí)例。
counter靜態(tài)變量是在StaticExample類中聲明的,但實(shí)際上counter變量是獨(dú)立于StaticExample的任何實(shí)例的。
也就是說,程序沒有創(chuàng)建任何StaticExample類的實(shí)例時(shí),counter已經(jīng)存在。程序創(chuàng)建100個(gè)StaticExample實(shí)例時(shí),
couner在內(nèi)存中仍然是一份,而不是100份。當(dāng)程序中創(chuàng)建的StaticExample類的實(shí)例都被虛擬機(jī)垃圾回收了,
counter還存在.因此靜態(tài)變量的生命周期,可以認(rèn)為程序的第一行代碼執(zhí)行之前,就已經(jīng)在內(nèi)存中準(zhǔn)備好,在程
序的最后一行代碼執(zhí)行完畢,靜態(tài)變量還在內(nèi)存中存在。靜態(tài)變量獨(dú)立于任何對(duì)象,包括聲明靜態(tài)變量的類實(shí)例對(duì)
象。
對(duì)簡(jiǎn)單類型是這樣,對(duì)復(fù)雜類型(對(duì)象類型)也是這樣。
靜態(tài)變量的這種特性,經(jīng)常被用在單實(shí)例的類實(shí)現(xiàn)中,例子如下:
publicclassLogger{
//構(gòu)造函數(shù)聲明為private,這樣就不能在類的外部用new來創(chuàng)
建對(duì)象
privateLogger(){}
privatestaticLoggerlogger=null;//單實(shí)例
//暴露給外界調(diào)用的方法
publicstaticLoggergetLogger(){
if(null==logger){〃判斷靜態(tài)實(shí)例是否初始化,如沒有就創(chuàng)
建
logger=newLogger();
I
returnlogger;//返回全局唯一的實(shí)例
■
//……其它方法聲明
}
static方法
如果一個(gè)類方法,僅依賴方法的傳入?yún)?shù)、其它static變量,則此方法不依賴于聲明方法的類實(shí)例,則此方法應(yīng)該
聲明為static,表示此方法是類方法,而非實(shí)例方法.例子如下:
publicclassConvertUtil{
publicstaticinttolnt(Strings){
returnInteger.parselnt(s);
}
}
tolnt方法的實(shí)現(xiàn),僅依賴于方法傳入的參數(shù)s,不依賴ConvertUtil對(duì)象的成員變量,因此tolnt方法是個(gè)類方法,
不是個(gè)實(shí)例方法,因此用static來修飾tolnt方法的聲明。這樣,調(diào)用者調(diào)用時(shí)的代碼就象下面這樣:
intiValue=ConvertUtil.tolnt(str);
如果不用static修飾,則調(diào)用者的代碼就需要修改為如下:
ConvertUtilutil=newConvertUtil();
intiValue=util.tolnt(str);
但實(shí)際上,tolnt方法根本就不需要?jiǎng)?chuàng)建一個(gè)ConvertUtil類的實(shí)例,這個(gè)對(duì)象是個(gè)浪費(fèi)。
組件設(shè)計(jì)中,常用的工具類方法,基本都是static聲明的。
static類
一個(gè)普通的Java類聲明,用static修飾,是非法的,編譯器會(huì)提示出錯(cuò)。這一點(diǎn),與C++的靜態(tài)類的概念是完全不
同的.在C++中,一個(gè)類的聲明用static修飾,則這個(gè)類中的所有成員變量和成員函數(shù)都是靜態(tài)方法,獨(dú)立于對(duì)象
存在。
對(duì)于嵌套類的聲明,可以用static修飾,則為另外的含義,在后續(xù)的嵌套類中進(jìn)行講解。
3.1.2嵌套類
一個(gè)類的聲明,是在另外一個(gè)類中,這種在類中聲明的類叫嵌套類,也叫類中類、內(nèi)部類。例子如下:
publicclassOuter{
privateStringouterld;
privateInnerinner=newlnner();
publicStringgetld(){
//訪問內(nèi)部類的私有成員
returnouterld++inner.innerld;
}
publicvoidsetldfStringid){
String[]strArray=id.split("-");
outerld=strArray[O];
inner.setld(strArray[l]);//調(diào)用內(nèi)部類的私有方法
}
privatevoidprintStr(Stringstr){
System.out.println(str);
)
//內(nèi)部類定義
publicclassInner{
privateStringinnerld;
privateStringgetld(){returninnerld;}
privatevoidsetld(Stringid){
innerld=id;
}
protectedvoidprintld(){
Stringstr="outerld="+outerld+",innerld="+innerld;//
訪問外部類的私有成員
printStr(str);//訪問外部類的私有方法
)
)
}
總結(jié)如下:
1)內(nèi)部類可以訪問外部類的任何成員變量,包括外部類的私有成員變量;
2)內(nèi)部類可以調(diào)用外部類的任何成員函數(shù),包括外部類私有成員函數(shù);
3)外部類可以訪問內(nèi)部類的任何成員變量,包括內(nèi)部類的私有成員變量;
4)外部類可以調(diào)用內(nèi)部類的任何成員函數(shù),包括內(nèi)部類的私有成員函數(shù);
因此,對(duì)于內(nèi)部類的成員變量和成員函數(shù),用private,protected,public,package修飾,就如同沒有這些修飾詞一樣。
另外,內(nèi)部類是不能獨(dú)立于外部類而單獨(dú)存在的,對(duì)象構(gòu)造的順序是由外向內(nèi),先生成外部類,然后生成內(nèi)部類。
3.1.3靜態(tài)嵌套類
嵌套類,在類聲明的時(shí)候用static修飾,就成了靜態(tài)嵌套類。靜態(tài)嵌套類與普通嵌套類是完全不同的。這里,static
的唯一作用,就相當(dāng)于一個(gè)分隔符,將內(nèi)部類和外部類隔離開來,使內(nèi)部類獨(dú)立于外部類單獨(dú)存在。也就是說,內(nèi)
部類對(duì)外部類沒有任何的依賴關(guān)系。而普通的內(nèi)部類,是必須依賴于外部類而存在的。
因此,外部類與靜態(tài)內(nèi)部類之間的關(guān)系,就和兩個(gè)獨(dú)立的類之間的關(guān)系相同,二者之間互相訪問,與兩個(gè)獨(dú)立類之
間的訪問規(guī)則相同。因此,用private,protected,public,package修飾,將直接影響可訪問性。示例如下:
publicclassOuterClass{
privateStringouterld;
privateStaticlnnerinner=newStaticlnner();
publicStringgetld(){
//returnouterld++inner.innerld();//私有成員,不允許訪
問
returnouterld++inner.getlnnerld();//公有方法,可以訪問
)
publicvoidsetld(Stringid){
String[]strArray=id.split("-");
outerld=strArray[O];
inner.setlnnerld(strArray[l]);//公有方法,可以訪問
}
privatevoidprintStr(Stringstr){
System.out.println(str);
)
publicstaticclassStaticlnner{
privateStringinnerld;
publicStringgetlnnerld(){
returninnerld;
■)
publicvoidsetlnnerld(Stringinnerld){
this.innerld=innerld;
}
publicvoidprintld(){
//無法訪問外部類的私有成員
//Stringstr="outerld="+outerld+",innerld="+innerld;
//printStr(str);//無法訪問外部類的私有方法
OuterClassouter=newOuterClass();
outer.printStr(innerld);//同一package中,可以訪問私有方
法
I)
I
3.2反射
我們通常寫的程序,都是靜態(tài)的代碼,比如:調(diào)用classA示例的put方法:
Aa=newA();
a.put("Hello!”);
第二行,a.put就是一種靜態(tài)的寫法。在編譯階段,就完成對(duì)a類型的解析,對(duì)a是否具有put方法進(jìn)行了判斷。
如果a對(duì)象沒有put方法,則編譯不通過。
可是,在另外一些情況下,我們需要在運(yùn)行時(shí)動(dòng)態(tài)的對(duì)一些對(duì)象的指定方法進(jìn)行調(diào)用,比如我們想寫一個(gè)通用的函
數(shù),傳入一個(gè)對(duì)象,把這個(gè)對(duì)象的屬性名和屬性值都打印出來,這就需要使用反射技術(shù)。
反射,是在JDK1.5引入的,JDK1.4以及以前的版本不支持。
在java.lang.reflect包中,定義了一些類,用于動(dòng)態(tài)解析和調(diào)用對(duì)象,常用的如下:
Constructor:構(gòu)造函數(shù)
Field:成員變量
Method:成員方法
Type:類型
另外,javaJang包中,Class類是非常值得重視的,通常用它來動(dòng)態(tài)創(chuàng)建對(duì)象。對(duì)于聲明了無參數(shù)的構(gòu)造函數(shù)、或
者有默認(rèn)構(gòu)造函數(shù)的類,動(dòng)態(tài)創(chuàng)建一個(gè)對(duì)象的代碼如下:
Classcis=Class.forName("com.test.ServicelmpI");
Objectobj=cls.newlnstance();
這樣,一個(gè)com.test.ServicelmpI類的實(shí)例就創(chuàng)建出來了。
假如Servicelmpl類有個(gè)registerUser方法,實(shí)現(xiàn)用戶注冊(cè)功能,如下:
publicintregisterllser(StringuserName,Stringpasswd);
如何動(dòng)態(tài)調(diào)用呢?代碼示例如下:
//動(dòng)態(tài)加載類
Classcis=Class.forName("com.test.Servicelmpl");
//動(dòng)態(tài)創(chuàng)建對(duì)象
Objectobj=cls.newlnstance();
//獲取相應(yīng)的方法
Methodmethod=cls.getDeclaredMethodC?egisterUser",String.class,String.class);
StringuserName="zhangsan";
Stringpasswd="hellol23";
//動(dòng)態(tài)調(diào)用對(duì)象的方法
Objectresult=method.invoke(obj,newObject[]{userName,passwd});
//強(qiáng)制類型轉(zhuǎn)換,獲取函數(shù)調(diào)用的返回值
intuserid=((lnteger)result).intValue();
System.out.println(nuserld=H+userid);
以上代碼,需用try{}包括起來,捕獲異常。
3.3數(shù)據(jù)結(jié)構(gòu)
本節(jié)對(duì)編程中經(jīng)常使用的線性數(shù)據(jù)結(jié)構(gòu)和非線性數(shù)據(jù)結(jié)構(gòu)進(jìn)行了簡(jiǎn)單介紹。線性數(shù)據(jù)結(jié)構(gòu)介紹兩種:ArrayList和
LinkedList.非線性數(shù)據(jù)結(jié)構(gòu)介紹兩種:HashSet和HashMap。這些數(shù)據(jù)結(jié)構(gòu),都在java.util包中定義。
3.3.1ArrayList
ArrayList是個(gè)線性的數(shù)據(jù)結(jié)構(gòu),可以在任何位置對(duì)元素進(jìn)行增加和刪除。但請(qǐng)注意以下要點(diǎn):
ArrayList是非同步的.也就是說,在多線程并發(fā)操作ArrayList的場(chǎng)景下,需要我們來寫代碼對(duì)ArrayList進(jìn)行同
步;
ArrayList內(nèi)部是用數(shù)組來實(shí)現(xiàn)的,對(duì)元素的增加和刪除都會(huì)引起數(shù)組的內(nèi)存分配空間動(dòng)態(tài)發(fā)生變化。因此,對(duì)其
進(jìn)行插入和刪除速度較慢,但檢索速度很快。
3)ArrayList中可以同時(shí)存放不同的對(duì)象;
ArrayList常用的方法有:
booleanadd(Objectelement);//在尾部添加一個(gè)對(duì)象
voidadd(intindex,Objectelement);//在指定位置插入元素,index從0計(jì)起
Objectget(intindex);//獲取指定位置的元素
booleancontains(Objectelem);//判斷是否包含此元素
intindexOf(Objectelem);//獲取指定元素在鏈表中的位置
Objectremove(intindex);//刪除指定位置的元素
booleanremove(Objecto);//刪除指定的對(duì)象
intsize();//獲取鏈表中元素的數(shù)量
voidclear();//清空鏈表
3.3.2LinkedList
LinkedList也是一個(gè)線性的數(shù)據(jù)結(jié)構(gòu),可以在任何位置對(duì)元素進(jìn)行增加和刪除。使用LinkedList時(shí),需注意以下:
1)LinkedList是非同步的。也就是說,在多線程并發(fā)操作LinkedList的場(chǎng)景下,需要我們來寫代碼對(duì)LinkedList進(jìn)行
同步;
2)LinkedList內(nèi)部就是用動(dòng)態(tài)鏈表來實(shí)現(xiàn)的,因此與ArrayList相比,其增加和刪除元素速度很快,整個(gè)List不需要
重新分配空間,但檢索速度較慢;
3)LinkedList實(shí)現(xiàn)了Queue接口,可以用做一個(gè)先進(jìn)先出的隊(duì)列來使用;
LinkedList常用的方法與ArrayList類似,增加了Queue接口的方法如下:
booleanoffer(Objectelement);//增加元素到隊(duì)列尾部
Objectpeek();//獲取隊(duì)列頭部的元素,并且不從隊(duì)列中移出
Objectpoll();//獲取隊(duì)列頭部的元素,并且從隊(duì)列中移出
Objectremove();//將隊(duì)列頭部元素移出
3.3.3HashSet
HashSet是個(gè)集合類型的數(shù)據(jù)結(jié)構(gòu),實(shí)現(xiàn)了Set接口,滿足數(shù)學(xué)上對(duì)集合的定義:無序、不重復(fù)。使用HashSet
時(shí)需要注意:
1)HashSet是非同步的。也就是說,在多線程并發(fā)操作HashSet的場(chǎng)景下,需要我們來寫代碼對(duì)HashSet進(jìn)行同步;
2)HashSet內(nèi)部是用HashMap來實(shí)現(xiàn)的,對(duì)元素的增加、刪除、檢索有很高的性能;
3)HashSet中元素是無序的,因此對(duì)HashSet進(jìn)行遍歷,不保證遍歷元素的順序都一致;
4)HashSet中沒有重復(fù)的元素,因此將相同的多個(gè)對(duì)象加入到HashSet中,則最終HashSet中只有一個(gè)對(duì)象;、
HashSet常用的方法如下:
booleanadd(Objecto);//增加元素到集合中
booleanremove(Objecto);//從集合中刪除元素
booleancontains(Objecto);//判斷集合中是否包含指定的對(duì)象
Iteratoriterator();//返回集合的迭代器,用于遍歷集合
booleanisEmpty();//判斷集合是否為空
intsize();//獲取集合中元素的數(shù)量
voidclear();//清空集合
3.3.4HashMap
HashMap,又稱雜湊表、散列表,它是一個(gè)非線性的數(shù)據(jù)結(jié)構(gòu)。其中每個(gè)元素由Key和Value對(duì)構(gòu)成,Key用來
做索引,Value是實(shí)際要存儲(chǔ)的值。使用HashMap時(shí)需要注意:
)HashMap是非同步的。也就是說,在多線程并發(fā)操作HashMap的場(chǎng)景下,需要我們來寫代碼對(duì)HashMap進(jìn)行同
步;
)HashMap對(duì)元素插入、刪除、檢索的速度都非???,在高性能的情況下被大量使用,其缺點(diǎn)是內(nèi)存占用較多。
)HashMap中可以存放不同類型的對(duì)象,包括null。
HashMap常用的方法如下:
Objectput(Objectkey,Objectvalue);//將Key-Value對(duì)放入Map中
Objectget(Objectkey);//用Key獲取Value對(duì)象
Objectremove(Objectkey);//從Map中刪除Key-Value對(duì)
booleancontainsKey(Objectkey);//判斷是否包括指定的Key
booleancontainsValue(Objectvalue);//判斷是否包含指定的Value
SetkeySet();〃獲取Key的集合,配合get方法,可以遍歷Map
SetentrySet();//獲取Map.Entry的集合,可以通過Map.Entry直接遍歷每個(gè)Key和Value
booleanisEmpty();//判斷是否為空
intsize();//判斷Map中的元素個(gè)數(shù)
voidclear();//清空Map
3.4泛型
3.4.1泛型簡(jiǎn)介
先拿一個(gè)例子來說明泛型是什么。
有兩個(gè)類如下,要構(gòu)造兩個(gè)類的對(duì)象,并打印出各自的成員X。
publicclassStringFoo{
privateStringx;
publicStringgetX(){
returnx;
1
publicvoidsetX(Stringx){
this.x=x;
)
publicclassDoubleFoo{
privateDoublex;
publicDoublegetX(){
returnx;
)
publicvoidsetX(Doublex){
this.x=x;
)
}
如果要實(shí)現(xiàn)對(duì)Integer、Long、Date等類型的操作,還要寫相應(yīng)的類,實(shí)在是無聊之極.
因此,對(duì)上面的兩個(gè)類進(jìn)行重構(gòu),寫成一個(gè)類,考慮如下:
上面的類中,成員和方法的邏輯都一樣,就是類型不一樣。Object是所有類的父類,因此可以考慮用Object做為成
員類型,這樣就可以實(shí)現(xiàn)通用了。
publicclassObjectFoo{
privateObjectx;
publicObjectgetX(){
returnx;
)
publicvoidsetX(Objectx){
this.x=x;
)
}
調(diào)用的代碼如下:
publicclassObjectFooDemo{
publicstaticvoidmain(Stringargs[]){
ObjectFoostrFoo=newObjectFoo();
strFoo.setX("HelloGenerics!");
ObjectFoodouFoo=newObjectFoo();
douFoo.setX(newDouble("33"));
ObjectFooobjFoo=newObjectFoo();
objFoo.setX(newObject());
Stringstr=(String)strFoo.getX();
Doubled=(Double)douFoo.getX();
Objectobj=objFoo.getX();
System.out.println(HstrFoo.getX="+str);
System.out.println("douFoo.getX=n+d);
System.out.println(,'strFoo.getX=H+obj);
)
}
以上,是沒有泛型的情況下,我們編寫的代碼,采用最頂層基類Object進(jìn)行類型聲明,然后將值傳入,取出時(shí)要進(jìn)
行強(qiáng)制類型轉(zhuǎn)換。
JDK從1.5開始引入了泛型的概念,來優(yōu)雅解決此類問題。采用泛型技術(shù),編寫的代碼如下:
publicclassGenericsFoo<T>{
privateTx;
publicTgetX(){
returnx;
}
publicvoidsetX(Tx){
this.x=x;
)
}
調(diào)用的代碼如下:
publicclassGenericsFooDemo{
publicstaticvoidmain(Stringargs[]){
GenericsFoo<String>strFoo=newGenericsFoo<String>();
strFoo.setX("HelloGenerics!");
GenericsFoo<Double>douFoo=newGenericsFoo<Double>();
douFoo.setX(newDouble("33n);
GenericsFoo<Object>objFoo=newGenericsFoo<Object>();
objFoo.setX(newObject());
Stringstr=strFoo.getX();
Doubled=douFoo.getX();
Objectobj=objFoo.getX();
System.out.println("strFoo.getX="+str);
System.out.println("douFoo.getX=n+d);
System.out.println("strFoo.getX="+obj);
注意,有幾點(diǎn)明顯的改變:
1.對(duì)象創(chuàng)建時(shí),明確給出類型,如GenericsFoo<String>。
2.對(duì)象通過getX方法取出時(shí),不需要進(jìn)行類型轉(zhuǎn)換。
3.對(duì)各個(gè)方法的調(diào)用,如果參數(shù)類型與創(chuàng)建時(shí)指定的類型不匹配時(shí),編譯器就會(huì)報(bào)錯(cuò)。
那么我們?yōu)槭裁匆盒湍??有兩個(gè)好處:
1.可以在編譯時(shí)檢查存儲(chǔ)的數(shù)據(jù)是否正確。我們開發(fā)有一個(gè)趨向就是盡早的發(fā)現(xiàn)錯(cuò)誤,最好就是在編譯階段,泛
型正好符合這一條件。
2.減少了強(qiáng)制轉(zhuǎn)換,Stringstr=(String)strList.get(O);這樣的操作屬于一種向下轉(zhuǎn)型,是比較危險(xiǎn)的操作,當(dāng)List
內(nèi)存儲(chǔ)的對(duì)象不適String時(shí)就會(huì)拋出異常.
JDK1.5中,java.util包中的各種數(shù)據(jù)類型工具類,都支持泛型,在編程中被廣泛使用,需要好好掌握。
泛型最常見的應(yīng)用是應(yīng)用在類、接口和方法上,下面分別介紹。
3.4.2泛型應(yīng)用在接口上:
publicinterfaceValuePair<A,B>{
publicAgetA();
publicBgetB();
publicStringtoString();
這里A和B都是代表類型。尖角號(hào)<>中,可以使用一個(gè)類型,也可以使用多個(gè)類型。
3.4.3泛型應(yīng)用在類上:
publicclassValuePairlmpl<A,B>{
publicfinalAfirst;
publicfinalBsecond;
publicValuePairlmpl(AazBb){first=a;second=b;}
publicAgetA(){returnfirst;}
publicBgetB(){returnsecond;}
publicStringtoString(){
return+first+","+second+
}
)
如果這個(gè)類實(shí)現(xiàn)泛型接口,則相應(yīng)的寫法為:
publicclassValuePairlmpl<A,B>implementsValuePair<A,B>{
}
3.4.4泛型應(yīng)用在方法上:
泛型也可以應(yīng)用在單獨(dú)的方法上,示例如下:
publicclassGenericMethod{
public<T>voidprintValue(Tv){
Stringstr=v.getClass().getName()+“="+v.toString();
System.out.println(str);
)
)
注意語法:在public修飾符后面是<>,然后是函數(shù)返回值,接著是函數(shù)名,函數(shù)參數(shù)。當(dāng)然,返回值也可以是泛型
的類型。
3.4.5限制泛型的可用類型
以上介紹的三種泛型應(yīng)用,應(yīng)用在接口、類、方法上,是一種通用的做法,對(duì)泛型可以傳入的類型沒有任何限制。
但有些場(chǎng)景下,我們希望對(duì)可用的類型進(jìn)行限制,比如希望傳入的類型必須從某個(gè)類繼承(也就是說,必須是某
個(gè)類的子類、孫類等),這種情況下就用到了泛型限制的語法。
extends:限制泛型類型必須為某個(gè)類的后代,包括本類型。
語法:<TextendsparentClass>
這里,T為泛型類型,extends關(guān)鍵字限制泛型類型必須是parentclass的后代。parentclass指定父類的類型,也
可以是接口。
在Java語言中,對(duì)類只能單繼承,對(duì)接口可以多繼承,如果要限制指定類型必須從某個(gè)類繼承,并且實(shí)現(xiàn)了多個(gè)
接口,則語法為:
<Textendsparentclass&parentlnterfacel&parentlnterface2>
注意,類必須在接口前面。
舉例如下:
publicclassBaseClass{
intvalue;
publicBaseClass(intvalue){
this.value=value;
)
publicintgetValue(){
returnvalue;
)
publicvoidsetValue(intvalue){
this.value=value;
publicclassSubClassextendsBaseClass{
publicSubClass(intvalue){
super(value*2);
publicclassGenericBound<TextendsBaseClass>{
publiclongsum(List<T>tList){
longiValue=0;
for(BaseClassbase:tList){
iValue+=base.getValue();
returniValue;
publicstaticvoidmain(String[]args){
GenericBound<SubClass>obj=new
GenericBound<SubClass>();
List<SubClass>list=newLinkedList<SubClass>();
list.add(newSubClass(5));
list.add(newSubClass(6));
System.out.println(obj.sum(list));
)
)
運(yùn)行,輸出結(jié)果為22.
接著,我們?cè)偕钊胩接懸幌?。把上面的例子該寫如?
publicclassGenericBound<TextendsBaseClass>{
publiclongsum(List<T>tList){
longiValue=0;
for(BaseClassbase:tList){
iValue+=base.getValue();
}
returniValue;
)
publicstaticvoidmain(String[]args){
//注意?。?!
//將obj的類型由GenericBoundvSubClass>改為GenericBound〈BaseClass>,無法通過編譯
GenericBound<BaseClass>obj=new
GenericBound<SubClass>();
List<SubClass>list=newLinkedList<SubClass>();
list.add(newSubClass(5));
list.add(newSubClass(6));
System.out.println(obj.sum(list));
)
}
語句GenericBound<BaseClass>obj=newGenericBound<SubClass>();無法通過編譯,其根本原因在于,GenericBound
類聲明處的<TextendsBaseClass>,限制了構(gòu)造此類實(shí)例的時(shí)候T是確定的一個(gè)類型,這個(gè)類型是BaseClass的后代。
但是BaseClass的后代還又很多,如SubClass3,SubClass4,如果針對(duì)每一種都要寫出具體的子類類型,那也太麻
煩了,干脆還不如用Object通用一下。能不能象普通類那樣,用父類的類型引入各種子類的實(shí)例,這樣不就簡(jiǎn)單
了很多?答案是肯定的,泛型針對(duì)這種情況提供了更好的解決方案,那就是“通配符泛型”,下面詳細(xì)講解。
3.4.6通配符泛型
Java的泛型類型如同java.Iang.String,java.io.File一樣,屬于普通的Java類型。比方說,下面兩個(gè)變量的類型就
是互不相同的:
Box<Object>boxObj=newBox<Object>();
Box<String>boxStr=newBox<String>();
雖然String是Object的子類,但是Box<String>和Box<Object>之間并沒有什么關(guān)系----Box<String>不是
Box<Object>的子類或者子類型,因此,以下賦值語句是非法的:
boxObj=boxStr;//無法通過編譯
因此,我們希望使用泛型時(shí),能象普通類那樣,用父類的類型引入各種子類的實(shí)例,從而簡(jiǎn)化程序的開發(fā)。Java的
泛型中,提供?通配符來滿足這個(gè)要求。
代碼示例如下:
publicclassWildcardGeneric{
publicvoidprint(List<?>1st){
for(inti=0;i<lst.size();i++){
System.ouLprintln(lst.get(i));
)
)
publicstaticvoidmain(String[]args){
WildcardGenericwg=newWildcardGeneric();
ArrayList<String>strList=newArrayList<String>();
strList.addf'One");
strList.add("Two");
wg.print(strList);
LinkedList<lnteger>intList=newLinkedList<lnteger>();
intList.add(25);
intList.add(30);
wg.print(intList);
)
)
但是這種情況下,WildcardGeneric.print方法的參數(shù)可以接受類型可能對(duì)于程序員設(shè)計(jì)的意圖而言太廣泛了一點(diǎn)。
因?yàn)槲覀兛赡苤皇窍M鹥rint可以接受一個(gè)List,但這個(gè)List中的元素必須是Number的后代。因此,我們要對(duì)通配
符有所限制,這時(shí)可以使用邊界通配符(boundedwildcard)形式來滿足這個(gè)要求。我們將print方法再修改一下:
publicvoidprint(List<?extendsNumber>1st){
for(inti=0;i<lst.size();i++){
System.ouLprintln(lst.get(i));
)
)
it#,List<lnteger>.List<Short>等等類型的變量就可以傳給print方法,而儲(chǔ)存其他類型元素的List的泛型類型
變量(如List<String>)傳給print方法將是非法的。
除了?extends上邊界通配符(upperboundedwildcard)以外,我們還可以使用下邊界通配符(lowerbounded
wildcard),例如List<?superViewWindow〉。
最后總結(jié)一下使用通配符的泛型類型的三種形式:
GenericType<?>
GenericType<?extendsupperBoundType>
GenericType<?superlowerBoundType>
3.4.7泛型深入
我們已經(jīng)初步掌握了泛型的基本用法,接著再來探討一下深入的主題。
我們還是先來看一段代碼:
publicclassGenericsFoo<T>{
privateTx;
publicTgetX(){
returnx;
)
publicvoidsetX(Tx){
this.x=x;
}
publicstaticvoidmain(String[]args){
GenericsFoo<String>gf=newGenericsFoo<String>();
gf.setX("Hello");
GenericsFoo<?>gf2=gf;
gf2.setX("World");//報(bào)錯(cuò)!!!
Stringst
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 給同事的感謝信匯編十篇
- 簡(jiǎn)單辭職申請(qǐng)書模板匯編九篇
- 2021過中秋節(jié)作文【5篇】
- 八年級(jí)物理教學(xué)計(jì)劃模板八篇
- 生物類實(shí)習(xí)報(bào)告模板集錦7篇
- 酒店辭職報(bào)告書集錦15篇
- 邊城讀后感匯編15篇
- 法律法規(guī)及事故案例講座
- 甘肅省定西市岷縣2024-2025學(xué)年九年級(jí)上學(xué)期期末質(zhì)量監(jiān)測(cè)歷史試卷(無答案)
- 交管12123駕駛證學(xué)法減分題庫及答案
- T∕ZSQX 008-2020 建設(shè)工程全過程質(zhì)量行為導(dǎo)則
- ISO-IEC17025-2017實(shí)驗(yàn)室管理體系全套程序文件
- 業(yè)務(wù)員手冊(cè)內(nèi)容
- pH值的測(cè)定方法
- 深圳智能水表項(xiàng)目商業(yè)計(jì)劃書_參考模板
- 輸出軸的機(jī)械加工工藝規(guī)程及夾具設(shè)計(jì)
- 元旦文藝匯演校長(zhǎng)致辭
- 國(guó)家開放大學(xué)電大本科《管理案例分析》2023-2024期末試題及答案試卷編號(hào):1304
- 離合器接合叉機(jī)械工藝說明書
- PWM脈寬直流調(diào)速系統(tǒng)設(shè)計(jì)及 matlab仿真驗(yàn)證
- 蜂窩煤成型機(jī)設(shè)計(jì)方案.doc
評(píng)論
0/150
提交評(píng)論