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

下載本文檔

版權(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)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論