《Java組件設(shè)計(jì)》教材_第1頁
《Java組件設(shè)計(jì)》教材_第2頁
《Java組件設(shè)計(jì)》教材_第3頁
《Java組件設(shè)計(jì)》教材_第4頁
《Java組件設(shè)計(jì)》教材_第5頁
已閱讀5頁,還剩57頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(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ì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論