




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
《Java組件設(shè)計》
本書主要面向軟件架構(gòu)師、設(shè)計師、高級開發(fā)人員,講解企業(yè)應(yīng)用中核心組件的設(shè)計原則與實踐。
本書將澄清設(shè)計模式、數(shù)據(jù)結(jié)構(gòu)、多線程、接口設(shè)計等多個領(lǐng)域中的常見誤區(qū),通過大量的實例分析,
為讀者精彩講解組件設(shè)計這一最具技術(shù)含量的領(lǐng)域需要考慮的問題、設(shè)計方案與最佳實踐.
章節(jié)目錄,暫定如下:
第一章:組件設(shè)計概述
第二章:組件設(shè)計原則
第三章:預(yù)備知識
第四章:配置文件
第五章:Tcp通信組件
第六章:日志組件
第七章:數(shù)據(jù)庫訪問組件
第八章:Web服務(wù)組件
1組件設(shè)計概述
編寫計算機(jī)軟件的人很多,我們通常都把這些活動稱為軟件開發(fā)。但是軟件的種類是不同的,每種軟
件都有自身的復(fù)雜性和挑戰(zhàn)性。本人一直工作在電信行業(yè),電信行業(yè)的軟件非常復(fù)雜,對并發(fā)、大數(shù)
據(jù)量、性能、高可靠性要求很高,這些都對軟件的設(shè)計和開發(fā)提出了嚴(yán)峻的挑戰(zhàn)。
1.1應(yīng)用軟件結(jié)構(gòu)
通常,應(yīng)用軟件的總體結(jié)構(gòu),可以分為兩大部分:應(yīng)用層和平臺層,具體如下:
平臺層提供基礎(chǔ)框架和大量可重用組件,這些組件以一定的接口方式暴露出來,供應(yīng)用層來調(diào)用。平
臺層通常不提供與具體業(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ù)等。
平臺層和應(yīng)用層,是個邏輯劃分的概念,實際軟件實現(xiàn)中,平臺層和應(yīng)用層都可以由多個層來實現(xiàn),
也可以合并到一個程序中,這要視項目的規(guī)模和具體需求而定。
從上圖可以看出,構(gòu)建一個高度可重用的平臺層,可以使應(yīng)用開發(fā)只需集中精力關(guān)注業(yè)務(wù)邏輯,業(yè)務(wù)
無關(guān)的功能組件和機(jī)制都由平臺層提供,可以直接使用,這樣極大簡化了應(yīng)用開發(fā),縮短了軟件交付
周期,保障了軟件質(zhì)量。
而構(gòu)建一個高度可重用的平臺層,最核心的挑戰(zhàn)就是設(shè)計和開發(fā)高度可重用的組件,提取應(yīng)用的共性
需求,簡化接口,真正做到應(yīng)用開發(fā)時可以直接拿來就用,而且非常好用。
1.2組件定義
那么,到底什么是組件呢?框架又是什么意思?類是組件嗎?控件又指什么?元件、構(gòu)件這些概念又
如何理解?
這些概念,都沒有一個統(tǒng)一的標(biāo)準(zhǔn)答案,因此在軟件開發(fā)過程中,這些術(shù)語經(jīng)常被混淆,作者根據(jù)自
己的工作體會,對這些概念解釋如下:
1)對象:面向?qū)ο笳Z言(Object-OrientedLanguage)是一類以對象作為基本程序結(jié)構(gòu)單位的程序設(shè)計
語言,指用于描述的設(shè)計是以對象為核心,而對象是程序運行時刻的基本成分。對象都具有狀態(tài)和行
為。對象,也被翻譯為實例、物件。
2)類:面向?qū)ο蟮母拍?。類是一種對包括數(shù)據(jù)成員、函數(shù)成員和嵌套類型進(jìn)行封裝的數(shù)據(jù)結(jié)構(gòu)。在程
序運行期間,由類創(chuàng)建成對象的過程,叫類的實例化。因此,對象是個運行期的術(shù)語,類是個編譯期
的術(shù)語。類本身就是可重用的直接實現(xiàn)手段.
3)組件:類本身是個細(xì)粒度的可重用實現(xiàn),為了解決功能或機(jī)制層面更大粒度重用的問題,又引入了
組件的概念。組件的英文是Component,其邏輯結(jié)構(gòu)如下:
組件對外暴露一個或多個接口,供外界調(diào)用。組件內(nèi)部由多個類來協(xié)同實現(xiàn)指定的功能。對于復(fù)雜的
組件,會包括很多的類,還可能包含配置文件、界面、依賴的庫文件等,組件也可以包含或者使用其
它的組件,構(gòu)成更大的組件。
一些特定范疇的組件,由軟件廠家或者國際權(quán)威組織制定并頒布了組件規(guī)范,如COM、ActiveX.EJB、
JavaBean等。本書討論的組件,指一般意義的自定義組件,不包括這些規(guī)范化的組件。
4)控件:控件的英文是Control,控件就是具有用戶界面的組件。要說的具體一點,就得回顧早期
Windows的歷史根源,當(dāng)時控件指任何子窗口:按鈕、列表框、編輯框或者某個對話框中的靜態(tài)文本。
從概念上講,這些窗口一一控件一一類似用來操作收音機(jī)或小電器的旋鈕和按鈕。隨著控件數(shù)量的增
加(組合框、日期時間控件等等),控件逐漸成為子窗口的代名詞,無論是用在對話框中還是用在其
它種類的主窗口中。沒過多久BASIC程序員開始編寫他們自己專用的控件,自然而然地人們便想到
共享這些控件。共享代碼的方法之一是通過磁盤拷貝,但那樣顯然效率低下。必須要有一種機(jī)制使開
發(fā)者建立的控件能夠在其它程序員的應(yīng)用中輕而易舉地插入,這便是VBA控件,OLE控件,OCX和最
后ActiveX控件產(chǎn)生的動機(jī)。因此,控件是組件的一個主要樣本(并且歷史上曾驅(qū)動著組件的開發(fā)),
控件又不僅僅是唯一的一種組件。
5)元件:元件是個電子行業(yè)的術(shù)語,是電子元件的簡稱。也有一些軟件借用這個術(shù)語,指特定的可重
用控件。
6)構(gòu)件:構(gòu)件是系統(tǒng)中實際存在的可更換部分,它實現(xiàn)特定的功能,符合一套接口標(biāo)準(zhǔn)并實現(xiàn)一組接
口。構(gòu)件代表系統(tǒng)中的一部分物理實施,包括軟件代碼(源代碼、二進(jìn)制代碼或可執(zhí)行代碼)或其等
價物(如腳本或命令文件)。
通常認(rèn)為,構(gòu)件是特定軟件開發(fā)環(huán)境中、滿足指定規(guī)范的軟件部分,其涵蓋了設(shè)計、開發(fā)、物理實施
等范疇.
7)框架:框架的英文是Framework,框架是一個應(yīng)用程序的半成品??蚣芴峁┝丝稍趹?yīng)用程序之間共
享的可復(fù)用的公共結(jié)構(gòu)。開發(fā)者把框架融入他們自己的應(yīng)用程序,并加以擴(kuò)展,以滿足他們特定的需
要■框架和工具包的不同之處在于,框架提供了一致的結(jié)構(gòu),而不僅僅是一組工具類.(摘自?JUnit
inaction中文版》).
8)子系統(tǒng):子系統(tǒng)是個設(shè)計中使用的術(shù)語,英文是SubSystem。在軟件總體架構(gòu)中,按照功能劃分子
系統(tǒng).通常一個子系統(tǒng)包含一個或多個進(jìn)程。
9)模塊:模塊是個設(shè)計中使用的術(shù)語,英文是Module.模塊指一個子系統(tǒng)內(nèi)部,按照軟件結(jié)構(gòu)分解
的功能部分.模塊會包含多個類、使用多個組件,也會與框架交互。
一個真正的軟件系統(tǒng),會涉及到以上的多個概念,典型的軟件系統(tǒng)靜態(tài)結(jié)構(gòu)圖如下:
子系統(tǒng)1子系統(tǒng)2
框
架
1
上圖展示了一個軟件系統(tǒng),包括2個子系統(tǒng),子系統(tǒng)1和子系統(tǒng)20子系統(tǒng)1調(diào)用子系統(tǒng)2。
子系統(tǒng)1包含2個模塊:模塊1和模塊2。模塊1由兩個類(Class1和Class2)和一個組件(組件1)構(gòu)
成,模塊2由兩個類(Class3和Class4)和兩個組件(組件2和組件3)構(gòu)成,模塊2提供一個接
口給模塊1調(diào)用。模塊1和模塊2都使用了框架1。
子系統(tǒng)2包含2個模塊:模塊3和模塊4。模塊3由兩個類(Class5和Class6)和一個組件(組件4)
構(gòu)成,模塊4由一個類(Class7)和一個組件(組件5)構(gòu)成。模塊4提供一個接口給模塊3調(diào)用。
模塊3和模塊4都使用了框架2。
2組件設(shè)計原則
Java陣營一直倡導(dǎo)開源,開源運動如火如荼展開,催生了無數(shù)組件。但是,坦率的講,這些開源的組件
中,能夠直接拿過來,不做任何改造,就能用于商業(yè)軟件構(gòu)建,滿足功能和性能的要求,這樣的優(yōu)秀
組件不多見。因此,核心軟件開發(fā)者時常面對的尷尬局面是:大量的開源資源,都不滿足我的要求。
實際上,組件設(shè)計是軟件設(shè)計開發(fā)最精髓所在,凝聚了數(shù)據(jù)結(jié)構(gòu)、面向?qū)ο蟆⒃O(shè)計模式、線程并發(fā)同步、
操作系統(tǒng)等諸多領(lǐng)域的最核心技術(shù),一直是設(shè)計開發(fā)領(lǐng)域彰顯技術(shù)水準(zhǔn)的最高領(lǐng)地。
一個組件,要想被廣泛重用,滿足不同系統(tǒng)的使用要求,就要求組件有足夠的能力適應(yīng)不同的應(yīng)用場合,
提供滿足需要的功能,并表現(xiàn)出優(yōu)秀的性能。因此,組件設(shè)計過程中,組件設(shè)計者要考慮的問題非常
廣泛而復(fù)雜,組件設(shè)計者需要具備的知識、技能和經(jīng)驗要求非常高,一般工作經(jīng)驗至少在5年以上才
有可能涉足組件設(shè)計這個領(lǐng)域.這也就解釋了,為什么優(yōu)秀組件不多的原因了。
本章將對組件設(shè)計過程中要考慮的核心要素、設(shè)計中要遵循的核心原則進(jìn)行總體闡述,使讀者能從總體
上掌握如何發(fā)現(xiàn)、評判、設(shè)計一個優(yōu)秀的組件。
本章將對目前業(yè)界存在的諸多技術(shù)爭論、誤區(qū)進(jìn)行澄清,讓讀者從所謂的“業(yè)界潮流”、教條、“黃金
定律”中走出來,真正理解組件設(shè)計過程的核心原則.這些核心原則如下:
原則一、精準(zhǔn)解決共性問題
原則二、無配置文件
原則三、與使用者概念一致
原則四、業(yè)務(wù)無關(guān)的中立性
原則五、對使用環(huán)境無依賴
原則六:單類設(shè)計和實現(xiàn)
下面來詳細(xì)講解每個核心原則。
2.1組件定位:精準(zhǔn)解決共性問題
組件的產(chǎn)生,來源于軟件工程實踐中,對重復(fù)、反復(fù)出現(xiàn)、普遍的、有相似性的問題進(jìn)行分析,剝離掉
各個問題的特性,抽取各個問題之間的共性,然后確定要設(shè)計一個或多個組件,這樣就確定了組件要
解決的問題,而這個問題必然是共性的問題,不能是個性、特例的問題。如果不是共性的問題,那么
寫完后的代碼就只能在一種或有限的幾種情況下才能被使用,這樣就無法實現(xiàn)大規(guī)模復(fù)用。不能廣泛
的被復(fù)用,就不能叫組件。
因此,組件首先是業(yè)務(wù)驅(qū)動的,不是技術(shù)驅(qū)動的。不能因為我們有了新的技術(shù),就想著要寫幾個新的組
件。共性的業(yè)務(wù)問題,是組件的產(chǎn)生來源。
另外,即使確定了組件要解決的問題,組件還必須提供解決問題最合理、最有效的方式和方法。如果提
供的方法不是最好的,那么也很難得到使用者的喜歡,最終也難以推廣和復(fù)用。因此,組件設(shè)計上,
必須提供解決問題的精準(zhǔn)思路和方案.這是決定同類別的組件中,哪個組件能勝出的最主要原因。
一個組件提供的問題解決方法,是否最有效,主要評價原則如下:
1)技術(shù)與業(yè)務(wù)對齊:技術(shù)結(jié)構(gòu)和技術(shù)概念要與業(yè)務(wù)上的模型、概念保持一致,在組件對外暴露的接
口上,最好不要引入新的技術(shù)術(shù)語和概念,這樣的組件最吻合業(yè)務(wù)的需求,也最容易理解。技術(shù)與業(yè)
務(wù)對齊,這是組件設(shè)計的第一位重要原則。
2)技術(shù)方案的優(yōu)勢:這是結(jié)構(gòu)設(shè)計、技術(shù)選型范疇內(nèi)要考慮的問題。實現(xiàn)同一個功能,可以有很多
不同的結(jié)構(gòu)設(shè)計,也有很多不同的技術(shù)可以采用,優(yōu)秀的組件,一定在技術(shù)方案上有明顯的技術(shù)優(yōu)勢。
3)接口簡單:組件暴露出來供外界使用的接口,必須足夠簡單。在概念和模型層面上與業(yè)務(wù)保持一
致,另外接口封裝、抽象上要仔細(xì)推敲,保證接口提供給別人使用時,調(diào)用者足夠簡單的使用。
4)功能強大:組件如果提供的功能太少,意味著能幫用戶解決的問題比較少,因此這樣的組件實用
性大打折扣。如果組件的功能過多,就會讓用戶覺得這個組件非常臃腫和龐大,用戶只用了其中一小
部分功能,其它大部分功能都用不上或者很少用,這樣用戶要在一本厚厚的使用手冊中,仔細(xì)尋找自
己需要的部分,去除自己不需要的部分,這也算不上一個優(yōu)秀的組件。因此,一個組件提供的功能,
既要能滿足多種場景下不同客戶的需求,還要在不同的需求中進(jìn)行取舍和合并,使提供的功能集不會
超出客戶實際使用需求太多。
2.2組件設(shè)計:無配置文件
組件自己不要帶任何配置文件,組件所依賴的各種庫也不帶任何配置文件。
這個原則極為重要?。。?/p>
如果一個組件,自己定義了配置文件,無論是XML,Properties,還是其他的文件類型,內(nèi)部的格式
都是組件設(shè)計者自己定義的。我們假設(shè)一個中等的項目,會使用10個組件,那么就會有10個配置文
件,這些配置文件的格式各不相同.那么我們的軟件安裝手冊、維護(hù)手冊就要仔細(xì)描述這10個配置
文件的結(jié)構(gòu),我們的實施維護(hù)人員就要學(xué)會如何配置這10個文件.如果項目再大,需要集成50個組
件,那就需要50個配置文件,對手冊編寫人員和實施維護(hù)人員,這是怎樣的災(zāi)難??!
歸納起來,如果一個組件自己帶配置文件,帶來的不良影響主要有:
1)不同組件的配置文件格式不統(tǒng)一,開發(fā)者、手冊編寫人員、實施人員、維護(hù)人員要學(xué)習(xí)不同格式、
不同結(jié)構(gòu)、不同的配置方式,學(xué)習(xí)的成本大大增加;
2)同一個配置(比如數(shù)據(jù)庫連接串,IP地址等),由于各個組件的配置方式不同,則實施人員要在多
個配置文件中進(jìn)行配置,增加了工作量,又難于保證升級時全部更新一致;
3)如果后續(xù)發(fā)現(xiàn)更好的組件,要用其替換原有的組件,則新的組件的配置文件與原組件的配置文件
不同,則即使這兩個組件的功能一致,我們也必需要更新實施維護(hù)手冊,實施維護(hù)人員又要重新學(xué)習(xí)。
因此,基于配置文件的組件,是非常不容易被集成的.一個應(yīng)用,無論其使用多少個組件,應(yīng)該始終
只有一個配置文件,用于集中配置這個應(yīng)用的所有參數(shù)。而每個組件,都是以接口或者類的方式提供
配置函數(shù),應(yīng)用集中讀取所有配置信息,然后通過組件的配置函數(shù),將配置參數(shù)設(shè)置到組件上。這樣,
將從根本上解決組件集成過程由組件配置文件引起的各種問題。
2.3組件設(shè)計:與使用者概念一致
組件提供接口,給應(yīng)用來使用。在接口上表現(xiàn)出的概念術(shù)語、使用方式都應(yīng)用與使用者的概念術(shù)語、
使用方式完全對應(yīng),也就是,技術(shù)和業(yè)務(wù)對齊。
組件設(shè)計者常犯的毛病是,組件暴露的接口,引入了新的技術(shù)術(shù)語,這些術(shù)語在使用者的概念體系中
根本就不存在,因此使用者理解起來非常困難,造成極大的學(xué)習(xí)障礙。這些與使用者概念不一致的技
術(shù)術(shù)語,突出表現(xiàn)在接口命名、類名、函數(shù)名、參數(shù)名、返回值的含義等。
從本質(zhì)上講,組件接口上暴露的與使用者不一致的概念,要么是這個概念本身就是錯誤的或不恰當(dāng)?shù)模?/p>
其不應(yīng)該存在,要么就是這是個內(nèi)部實現(xiàn)的概念,不應(yīng)該包括在接口上。
2.4組件設(shè)計:業(yè)務(wù)無關(guān)的中立性
一個組件,要在不同應(yīng)用、不同場景下都能廣泛的重用,這是組件設(shè)計者必需要實現(xiàn)的目標(biāo)。但一個
組件的產(chǎn)生,往往來源于一個特定的項目、一個特定的業(yè)務(wù)場景,在實現(xiàn)業(yè)務(wù)和項目功能的時候,組
件設(shè)計者意識到,這個功能部分可以進(jìn)行抽取,形成一個可重用的組件。因此,這個抽取的過程,組
件設(shè)計者務(wù)必把與當(dāng)前這個項目、這個業(yè)務(wù)特有的部分剝離掉,保留一般的、共性的功能,并重新封
裝和調(diào)整接口,以滿足通用的使用場景。這樣,這個組件可以與業(yè)務(wù)無關(guān),保持自己的中立性,后續(xù)
可以在其它的應(yīng)用中被重用。
如何識別那些是業(yè)務(wù)特性,那些是一般的共性,這需要依賴組件設(shè)計者多年的經(jīng)驗。
2.5組件設(shè)計實現(xiàn):對使用環(huán)境無依賴
組件的內(nèi)部實現(xiàn),是否可以對將來組件被使用的環(huán)境做些假設(shè)?比如,是在Servlet容器中運行,
僅使用一個組件的實例,僅處理一個目錄…….?
如果組件設(shè)計者對組件將來被使用的環(huán)境做了一些假設(shè),認(rèn)為這些條件必然被滿足,那么組件的代碼
實現(xiàn)就會和這些假設(shè)條件相關(guān)。如果這些條件不成立,則組件無法被使用。比如,組件設(shè)計者認(rèn)為,
將來應(yīng)用中一個組件的實例就能滿足需要,因此組件就設(shè)計成了單實例。當(dāng)一個特殊的應(yīng)用出現(xiàn),需
要使用兩個組件實例來處理不同的場景,發(fā)現(xiàn)組件已經(jīng)被設(shè)計成單實例,無法滿足應(yīng)用的這種“特殊”
需求。
這種需求,真的很特殊嗎?
客觀上講,需求來自于具體的應(yīng)用,來自客戶的使用場景和要解決的問題。需求,是獨立與設(shè)計與實
現(xiàn)的,尤其是與組件的設(shè)計實現(xiàn)無關(guān)。組件設(shè)計者之所以認(rèn)為這種需求很“特殊”,是因為這個需求
超出了組件設(shè)計者原來所做的假設(shè)。根本上講,組件設(shè)計者不應(yīng)該對組件將來的使用環(huán)境做任何假設(shè),
這樣組件的使用場合僅受限于組件的能力集,只有應(yīng)用需要的功能在組件的能力集范圍內(nèi),各種環(huán)境
下的不同應(yīng)用都可以使用這個組件。
這樣,組件才可以被廣泛復(fù)用。
2.6組件設(shè)計實現(xiàn):單類設(shè)計和實現(xiàn)
怎樣的組件,才是一個優(yōu)秀的組件?從組件使用者的角度,組件要簡單,易于使用,并且功能強大。
那么組件怎樣才能簡單,易于使用?
首先,前面講過,組件提供出來的接口,要與使用者的概念完全一致,這樣使用者就非常容易理解組
件的各個類、各個接口、各個方法、各個參數(shù),這樣就會易于使用。
但組件怎么才能簡單呢?
最簡單的組件,就是一個類。
一個類,就會比兩個類簡單,兩個類就會比四個類簡單。這個道理顯而易見,其核心精髓是面向?qū)ο?/p>
的基本設(shè)計原則:高內(nèi)聚、低耦合。要嚴(yán)格控制類的數(shù)量,邏輯相關(guān)度很高的,就不允許拆成多個類。
不支持將來變化的部分,就不提供接口。
組件,作為可重用的軟件,不會承載太多的功能,組件的規(guī)模不會很大。因此,最理想的情況,組件
就是單獨的一個類。組件使用者用起來,是極致的簡單。
我們從網(wǎng)上下載開源的項目,打開源碼一看,豁,好家伙,一大堆的類和接口,不寫幾十個、幾百個
類好象就顯不出作者的水平。其實真有必要寫那么的多的類嗎?
高內(nèi)聚,高內(nèi)聚,單類組件,簡單的極致!
3預(yù)備知識
本章主要講解一些組件開發(fā)中常用的技術(shù),作為后續(xù)章節(jié)講解組件設(shè)計的預(yù)備知識。
本章假設(shè)讀者已經(jīng)熟悉了Java語言的基本語法,并且熟練使用Java語言至少3年,因此,簡單的語法知識不再講
述,僅講解一些高級主題。
3.1Java語法深入
本節(jié)對Java語法中一些高級主題進(jìn)行講解.
3.1.1static
static變量
本質(zhì)上講,static關(guān)鍵字聲明了一個全局變量。
盡管Java是一門純面向?qū)ο蟮恼Z言,語言規(guī)范中沒有全局變量這種說法,但static聲明的變量,從本質(zhì)上就等同于
C或C++語言中的全局變量.整個應(yīng)用程序就一個變量實例,任何對static變量進(jìn)行的更改,在程序的其它地方都可
見。
舉個例子如下:
publicclassStaticExample{
publicstaticintcounter;
}
counter是個整型變量,前面用static關(guān)鍵字修飾后,就變成了一個全局變量,在程序的代碼執(zhí)行之前,這個counter
變量就已經(jīng)在內(nèi)存中存在了,而且是唯一的一份實例。
counter靜態(tài)變量是在StaticExample類中聲明的,但實際上counter變量是獨立于StaticExample的任何實例的。
也就是說,程序沒有創(chuàng)建任何StaticExample類的實例時,counter已經(jīng)存在。程序創(chuàng)建100個StaticExample實例時,
couner在內(nèi)存中仍然是一份,而不是100份。當(dāng)程序中創(chuàng)建的StaticExample類的實例都被虛擬機(jī)垃圾回收了,
counter還存在.因此靜態(tài)變量的生命周期,可以認(rèn)為程序的第一行代碼執(zhí)行之前,就已經(jīng)在內(nèi)存中準(zhǔn)備好,在程
序的最后一行代碼執(zhí)行完畢,靜態(tài)變量還在內(nèi)存中存在。靜態(tài)變量獨立于任何對象,包括聲明靜態(tài)變量的類實例對
象。
對簡單類型是這樣,對復(fù)雜類型(對象類型)也是這樣。
靜態(tài)變量的這種特性,經(jīng)常被用在單實例的類實現(xiàn)中,例子如下:
publicclassLogger{
//構(gòu)造函數(shù)聲明為private,這樣就不能在類的外部用new來創(chuàng)
建對象
privateLogger(){}
privatestaticLoggerlogger=null;//單實例
//暴露給外界調(diào)用的方法
publicstaticLoggergetLogger(){
if(null==logger){〃判斷靜態(tài)實例是否初始化,如沒有就創(chuàng)
建
logger=newLogger();
I
returnlogger;//返回全局唯一的實例
■
//……其它方法聲明
}
static方法
如果一個類方法,僅依賴方法的傳入?yún)?shù)、其它static變量,則此方法不依賴于聲明方法的類實例,則此方法應(yīng)該
聲明為static,表示此方法是類方法,而非實例方法.例子如下:
publicclassConvertUtil{
publicstaticinttolnt(Strings){
returnInteger.parselnt(s);
}
}
tolnt方法的實現(xiàn),僅依賴于方法傳入的參數(shù)s,不依賴ConvertUtil對象的成員變量,因此tolnt方法是個類方法,
不是個實例方法,因此用static來修飾tolnt方法的聲明。這樣,調(diào)用者調(diào)用時的代碼就象下面這樣:
intiValue=ConvertUtil.tolnt(str);
如果不用static修飾,則調(diào)用者的代碼就需要修改為如下:
ConvertUtilutil=newConvertUtil();
intiValue=util.tolnt(str);
但實際上,tolnt方法根本就不需要創(chuàng)建一個ConvertUtil類的實例,這個對象是個浪費。
組件設(shè)計中,常用的工具類方法,基本都是static聲明的。
static類
一個普通的Java類聲明,用static修飾,是非法的,編譯器會提示出錯。這一點,與C++的靜態(tài)類的概念是完全不
同的.在C++中,一個類的聲明用static修飾,則這個類中的所有成員變量和成員函數(shù)都是靜態(tài)方法,獨立于對象
存在。
對于嵌套類的聲明,可以用static修飾,則為另外的含義,在后續(xù)的嵌套類中進(jìn)行講解。
3.1.2嵌套類
一個類的聲明,是在另外一個類中,這種在類中聲明的類叫嵌套類,也叫類中類、內(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ù);
因此,對于內(nèi)部類的成員變量和成員函數(shù),用private,protected,public,package修飾,就如同沒有這些修飾詞一樣。
另外,內(nèi)部類是不能獨立于外部類而單獨存在的,對象構(gòu)造的順序是由外向內(nèi),先生成外部類,然后生成內(nèi)部類。
3.1.3靜態(tài)嵌套類
嵌套類,在類聲明的時候用static修飾,就成了靜態(tài)嵌套類。靜態(tài)嵌套類與普通嵌套類是完全不同的。這里,static
的唯一作用,就相當(dāng)于一個分隔符,將內(nèi)部類和外部類隔離開來,使內(nèi)部類獨立于外部類單獨存在。也就是說,內(nèi)
部類對外部類沒有任何的依賴關(guān)系。而普通的內(nèi)部類,是必須依賴于外部類而存在的。
因此,外部類與靜態(tài)內(nèi)部類之間的關(guān)系,就和兩個獨立的類之間的關(guān)系相同,二者之間互相訪問,與兩個獨立類之
間的訪問規(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)的寫法。在編譯階段,就完成對a類型的解析,對a是否具有put方法進(jìn)行了判斷。
如果a對象沒有put方法,則編譯不通過。
可是,在另外一些情況下,我們需要在運行時動態(tài)的對一些對象的指定方法進(jìn)行調(diào)用,比如我們想寫一個通用的函
數(shù),傳入一個對象,把這個對象的屬性名和屬性值都打印出來,這就需要使用反射技術(shù)。
反射,是在JDK1.5引入的,JDK1.4以及以前的版本不支持。
在java.lang.reflect包中,定義了一些類,用于動態(tài)解析和調(diào)用對象,常用的如下:
Constructor:構(gòu)造函數(shù)
Field:成員變量
Method:成員方法
Type:類型
另外,javaJang包中,Class類是非常值得重視的,通常用它來動態(tài)創(chuàng)建對象。對于聲明了無參數(shù)的構(gòu)造函數(shù)、或
者有默認(rèn)構(gòu)造函數(shù)的類,動態(tài)創(chuàng)建一個對象的代碼如下:
Classcis=Class.forName("com.test.ServicelmpI");
Objectobj=cls.newlnstance();
這樣,一個com.test.ServicelmpI類的實例就創(chuàng)建出來了。
假如Servicelmpl類有個registerUser方法,實現(xiàn)用戶注冊功能,如下:
publicintregisterllser(StringuserName,Stringpasswd);
如何動態(tài)調(diào)用呢?代碼示例如下:
//動態(tài)加載類
Classcis=Class.forName("com.test.Servicelmpl");
//動態(tài)創(chuàng)建對象
Objectobj=cls.newlnstance();
//獲取相應(yīng)的方法
Methodmethod=cls.getDeclaredMethodC?egisterUser",String.class,String.class);
StringuserName="zhangsan";
Stringpasswd="hellol23";
//動態(tài)調(diào)用對象的方法
Objectresult=method.invoke(obj,newObject[]{userName,passwd});
//強制類型轉(zhuǎn)換,獲取函數(shù)調(diào)用的返回值
intuserid=((lnteger)result).intValue();
System.out.println(nuserld=H+userid);
以上代碼,需用try{}包括起來,捕獲異常。
3.3數(shù)據(jù)結(jié)構(gòu)
本節(jié)對編程中經(jīng)常使用的線性數(shù)據(jù)結(jié)構(gòu)和非線性數(shù)據(jù)結(jié)構(gòu)進(jì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是個線性的數(shù)據(jù)結(jié)構(gòu),可以在任何位置對元素進(jìn)行增加和刪除。但請注意以下要點:
ArrayList是非同步的.也就是說,在多線程并發(fā)操作ArrayList的場景下,需要我們來寫代碼對ArrayList進(jìn)行同
步;
ArrayList內(nèi)部是用數(shù)組來實現(xiàn)的,對元素的增加和刪除都會引起數(shù)組的內(nèi)存分配空間動態(tài)發(fā)生變化。因此,對其
進(jìn)行插入和刪除速度較慢,但檢索速度很快。
3)ArrayList中可以同時存放不同的對象;
ArrayList常用的方法有:
booleanadd(Objectelement);//在尾部添加一個對象
voidadd(intindex,Objectelement);//在指定位置插入元素,index從0計起
Objectget(intindex);//獲取指定位置的元素
booleancontains(Objectelem);//判斷是否包含此元素
intindexOf(Objectelem);//獲取指定元素在鏈表中的位置
Objectremove(intindex);//刪除指定位置的元素
booleanremove(Objecto);//刪除指定的對象
intsize();//獲取鏈表中元素的數(shù)量
voidclear();//清空鏈表
3.3.2LinkedList
LinkedList也是一個線性的數(shù)據(jù)結(jié)構(gòu),可以在任何位置對元素進(jìn)行增加和刪除。使用LinkedList時,需注意以下:
1)LinkedList是非同步的。也就是說,在多線程并發(fā)操作LinkedList的場景下,需要我們來寫代碼對LinkedList進(jìn)行
同步;
2)LinkedList內(nèi)部就是用動態(tài)鏈表來實現(xiàn)的,因此與ArrayList相比,其增加和刪除元素速度很快,整個List不需要
重新分配空間,但檢索速度較慢;
3)LinkedList實現(xiàn)了Queue接口,可以用做一個先進(jìn)先出的隊列來使用;
LinkedList常用的方法與ArrayList類似,增加了Queue接口的方法如下:
booleanoffer(Objectelement);//增加元素到隊列尾部
Objectpeek();//獲取隊列頭部的元素,并且不從隊列中移出
Objectpoll();//獲取隊列頭部的元素,并且從隊列中移出
Objectremove();//將隊列頭部元素移出
3.3.3HashSet
HashSet是個集合類型的數(shù)據(jù)結(jié)構(gòu),實現(xiàn)了Set接口,滿足數(shù)學(xué)上對集合的定義:無序、不重復(fù)。使用HashSet
時需要注意:
1)HashSet是非同步的。也就是說,在多線程并發(fā)操作HashSet的場景下,需要我們來寫代碼對HashSet進(jìn)行同步;
2)HashSet內(nèi)部是用HashMap來實現(xiàn)的,對元素的增加、刪除、檢索有很高的性能;
3)HashSet中元素是無序的,因此對HashSet進(jìn)行遍歷,不保證遍歷元素的順序都一致;
4)HashSet中沒有重復(fù)的元素,因此將相同的多個對象加入到HashSet中,則最終HashSet中只有一個對象;、
HashSet常用的方法如下:
booleanadd(Objecto);//增加元素到集合中
booleanremove(Objecto);//從集合中刪除元素
booleancontains(Objecto);//判斷集合中是否包含指定的對象
Iteratoriterator();//返回集合的迭代器,用于遍歷集合
booleanisEmpty();//判斷集合是否為空
intsize();//獲取集合中元素的數(shù)量
voidclear();//清空集合
3.3.4HashMap
HashMap,又稱雜湊表、散列表,它是一個非線性的數(shù)據(jù)結(jié)構(gòu)。其中每個元素由Key和Value對構(gòu)成,Key用來
做索引,Value是實際要存儲的值。使用HashMap時需要注意:
)HashMap是非同步的。也就是說,在多線程并發(fā)操作HashMap的場景下,需要我們來寫代碼對HashMap進(jìn)行同
步;
)HashMap對元素插入、刪除、檢索的速度都非???,在高性能的情況下被大量使用,其缺點是內(nèi)存占用較多。
)HashMap中可以存放不同類型的對象,包括null。
HashMap常用的方法如下:
Objectput(Objectkey,Objectvalue);//將Key-Value對放入Map中
Objectget(Objectkey);//用Key獲取Value對象
Objectremove(Objectkey);//從Map中刪除Key-Value對
booleancontainsKey(Objectkey);//判斷是否包括指定的Key
booleancontainsValue(Objectvalue);//判斷是否包含指定的Value
SetkeySet();〃獲取Key的集合,配合get方法,可以遍歷Map
SetentrySet();//獲取Map.Entry的集合,可以通過Map.Entry直接遍歷每個Key和Value
booleanisEmpty();//判斷是否為空
intsize();//判斷Map中的元素個數(shù)
voidclear();//清空Map
3.4泛型
3.4.1泛型簡介
先拿一個例子來說明泛型是什么。
有兩個類如下,要構(gòu)造兩個類的對象,并打印出各自的成員X。
publicclassStringFoo{
privateStringx;
publicStringgetX(){
returnx;
1
publicvoidsetX(Stringx){
this.x=x;
)
publicclassDoubleFoo{
privateDoublex;
publicDoublegetX(){
returnx;
)
publicvoidsetX(Doublex){
this.x=x;
)
}
如果要實現(xiàn)對Integer、Long、Date等類型的操作,還要寫相應(yīng)的類,實在是無聊之極.
因此,對上面的兩個類進(jìn)行重構(gòu),寫成一個類,考慮如下:
上面的類中,成員和方法的邏輯都一樣,就是類型不一樣。Object是所有類的父類,因此可以考慮用Object做為成
員類型,這樣就可以實現(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)行類型聲明,然后將值傳入,取出時要進(jìn)
行強制類型轉(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);
注意,有幾點明顯的改變:
1.對象創(chuàng)建時,明確給出類型,如GenericsFoo<String>。
2.對象通過getX方法取出時,不需要進(jìn)行類型轉(zhuǎn)換。
3.對各個方法的調(diào)用,如果參數(shù)類型與創(chuàng)建時指定的類型不匹配時,編譯器就會報錯。
那么我們?yōu)槭裁匆盒湍兀坑袃蓚€好處:
1.可以在編譯時檢查存儲的數(shù)據(jù)是否正確。我們開發(fā)有一個趨向就是盡早的發(fā)現(xiàn)錯誤,最好就是在編譯階段,泛
型正好符合這一條件。
2.減少了強制轉(zhuǎn)換,Stringstr=(String)strList.get(O);這樣的操作屬于一種向下轉(zhuǎn)型,是比較危險的操作,當(dāng)List
內(nèi)存儲的對象不適String時就會拋出異常.
JDK1.5中,java.util包中的各種數(shù)據(jù)類型工具類,都支持泛型,在編程中被廣泛使用,需要好好掌握。
泛型最常見的應(yīng)用是應(yīng)用在類、接口和方法上,下面分別介紹。
3.4.2泛型應(yīng)用在接口上:
publicinterfaceValuePair<A,B>{
publicAgetA();
publicBgetB();
publicStringtoString();
這里A和B都是代表類型。尖角號<>中,可以使用一個類型,也可以使用多個類型。
3.4.3泛型應(yīng)用在類上:
publicclassValuePairlmpl<A,B>{
publicfinalAfirst;
publicfinalBsecond;
publicValuePairlmpl(AazBb){first=a;second=b;}
publicAgetA(){returnfirst;}
publicBgetB(){returnsecond;}
publicStringtoString(){
return+first+","+second+
}
)
如果這個類實現(xiàn)泛型接口,則相應(yīng)的寫法為:
publicclassValuePairlmpl<A,B>implementsValuePair<A,B>{
}
3.4.4泛型應(yīng)用在方法上:
泛型也可以應(yīng)用在單獨的方法上,示例如下:
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)用在接口、類、方法上,是一種通用的做法,對泛型可以傳入的類型沒有任何限制。
但有些場景下,我們希望對可用的類型進(jìn)行限制,比如希望傳入的類型必須從某個類繼承(也就是說,必須是某
個類的子類、孫類等),這種情況下就用到了泛型限制的語法。
extends:限制泛型類型必須為某個類的后代,包括本類型。
語法:<TextendsparentClass>
這里,T為泛型類型,extends關(guān)鍵字限制泛型類型必須是parentclass的后代。parentclass指定父類的類型,也
可以是接口。
在Java語言中,對類只能單繼承,對接口可以多繼承,如果要限制指定類型必須從某個類繼承,并且實現(xiàn)了多個
接口,則語法為:
<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));
)
)
運行,輸出結(jié)果為22.
接著,我們再深入探討一下。把上面的例子該寫如下:
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)造此類實例的時候T是確定的一個類型,這個類型是BaseClass的后代。
但是BaseClass的后代還又很多,如SubClass3,SubClass4,如果針對每一種都要寫出具體的子類類型,那也太麻
煩了,干脆還不如用Object通用一下。能不能象普通類那樣,用父類的類型引入各種子類的實例,這樣不就簡單
了很多?答案是肯定的,泛型針對這種情況提供了更好的解決方案,那就是“通配符泛型”,下面詳細(xì)講解。
3.4.6通配符泛型
Java的泛型類型如同java.Iang.String,java.io.File一樣,屬于普通的Java類型。比方說,下面兩個變量的類型就
是互不相同的:
Box<Object>boxObj=newBox<Object>();
Box<String>boxStr=newBox<String>();
雖然String是Object的子類,但是Box<String>和Box<Object>之間并沒有什么關(guān)系----Box<String>不是
Box<Object>的子類或者子類型,因此,以下賦值語句是非法的:
boxObj=boxStr;//無法通過編譯
因此,我們希望使用泛型時,能象普通類那樣,用父類的類型引入各種子類的實例,從而簡化程序的開發(fā)。Java的
泛型中,提供?通配符來滿足這個要求。
代碼示例如下:
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ù)可以接受類型可能對于程序員設(shè)計的意圖而言太廣泛了一點。
因為我們可能只是希望print可以接受一個List,但這個List中的元素必須是Number的后代。因此,我們要對通配
符有所限制,這時可以使用邊界通配符(boundedwildcard)形式來滿足這個要求。我們將print方法再修改一下:
publicvoidprint(List<?extendsNumber>1st){
for(inti=0;i<lst.size();i++){
System.ouLprintln(lst.get(i));
)
)
it#,List<lnteger>.List<Short>等等類型的變量就可以傳給print方法,而儲存其他類型元素的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");//報錯!!!
Stringst
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 汽貿(mào)消防培訓(xùn)課件
- 2025屆江蘇省淮安、宿遷等化學(xué)高二下期末教學(xué)質(zhì)量檢測模擬試題含解析
- 全國愛耳日小學(xué)主題教育
- 校園安全法律法規(guī)
- 汕尾城市介紹
- 永遠(yuǎn)的一課的有關(guān)課件
- 中班健康《我出門了》教案
- 氯丙嗪說課課件
- 植物的旅行課件
- DB4201T 570-2018“武漢名品”評價通 用要求
- 從管控到賦能:我國文藝演出市場發(fā)展進(jìn)程中政府職能轉(zhuǎn)變探究
- 安全標(biāo)準(zhǔn)化考試試題及答案
- 車輛進(jìn)廠出廠管理制度
- 商協(xié)會公章管理制度
- 企業(yè)檔案利用管理制度
- 安全生產(chǎn)月題庫-2025年安全生產(chǎn)月安全知識競賽題庫(附題目答案)
- 口腔正畸模型測量分析
- 2025年中小學(xué)美術(shù)教師招聘考試美術(shù)專業(yè)知識必考題庫及答案(共170題)
- 四川省德陽市2023-2024學(xué)年高二下學(xué)期7月期末考試英語試題(含答案)
- 【MOOC】人工智能導(dǎo)論-西安電子科技大學(xué) 中國大學(xué)慕課MOOC答案
- 生物吸附課件
評論
0/150
提交評論