Java學(xué)習(xí)記錄:對(duì)象與類(二)
類路徑:
類存儲(chǔ)在文件系統(tǒng)的子目錄中。類的路徑必須與包名匹配。
另外,類文件也可以存儲(chǔ)在JAR(java歸檔)文件中。在一個(gè)JAR文件中,可以包含多個(gè)壓縮格式的類文件和子目錄,這樣既可以節(jié)省空間又可以改善性能。在程序中用到第三方的庫(kù)時(shí),你通常會(huì)得到一個(gè)或多個(gè)需要包含的JAR文件。(第11章會(huì)講JAR文件)
【提示:JAR文件使用ZIP格式組織文件和子目錄??梢允褂萌魏蝂IP工具查看JAR文件?!?/p>
為了使類能夠被多個(gè)程序共享,需要做到下面幾點(diǎn):
1.?把類文件放到一個(gè)目錄中,例如/home/user/classdir。需要注意,這個(gè)目錄是包樹(shù)狀結(jié)構(gòu)的基目錄。如果希望增加com.horstmann.corejava.Employee類,那么Employee.class類文件就必須位于子目錄/home/user/classdir/com/horstmann/corejava中。
2.?將JAR文件放在一個(gè)目錄中,例如/home/user/archives。
3.?設(shè)置類路徑(class path)。類路徑是所有包含類文件的路徑的集合。
在UNIX環(huán)境中,類路徑中的各項(xiàng)之間用冒號(hào)(:)分割:
/home/user/classdir:.:/home/user/archives/archive.jar
而在Windows環(huán)境中,則以分號(hào)(;)分割:
C:\classdir;.;C:\archives\archive.jar
不論是UNIX還是Windows,都用句點(diǎn)(.)表示當(dāng)前目錄。
類路徑包括:
·基目錄/home/user/classdir或C:\classdir;
·當(dāng)前目錄(.)
·JAR文件/home/user/archives/archive.jar或者C:\archives\archive.jar.
從java6開(kāi)始,可以為JAR文件目錄指定一個(gè)通配符,如下:
/home/user/classdir:.:/home/user/archives/’*’
或者
C:\classdir;.;C:\archives\*
在UNIX中,*必須轉(zhuǎn)義以防止shell擴(kuò)展。
archives目錄中的所有JAR文件(但不包括.class文件)都包含在這個(gè)類路徑中。
由于總是會(huì)搜索JavaAPI的類,所以不必顯式地包含在類路徑中。
【警告:javac編譯器總是在當(dāng)前目錄中查找文件,但只有當(dāng)類路徑中包含“.”目錄時(shí),java虛擬機(jī)才會(huì)查看當(dāng)前目錄。如果你沒(méi)有設(shè)置類路徑,那么沒(méi)有什么問(wèn)題,因?yàn)槟J(rèn)的類路徑會(huì)包含”.“目錄。但是如果你設(shè)置了類路徑卻忘記包含”.“目錄,那么盡管你的程序可以沒(méi)有錯(cuò)誤地通過(guò)編譯,但不能運(yùn)行?!?/p>
類路徑所列出地目錄和歸檔文件是搜尋類地起始點(diǎn)。下面看一個(gè)類路徑示例:
/home/user/classdir:.:/home/user/archives/archive.jar
假定虛擬機(jī)要搜尋com.horstmann.corejava.Employee類的類文件。它首先要查看JavaAPI類,顯然,在那里找不到相應(yīng)的類文件,所以轉(zhuǎn)而查看類路徑。它會(huì)查找以下文件:
·/home/user/classdir/com/horstmann/corejava/Employee.class
·com/horstmann/corejava/Employee.class(從當(dāng)前目錄開(kāi)始)
·com/horstmann/corejava/Employee.class(/home/user/archives/archive.jar中)
編譯器查找文件要比虛擬機(jī)復(fù)雜得多。如果引用了一個(gè)類,而沒(méi)有指定這個(gè)類的包,那么編譯器將首先查找包含這個(gè)類的包。它會(huì)查看所有的import指令,確定其中是否包含這個(gè)類。例如,假定源文件包含指令:
Import java.util.*;
Import com.horstmann.corejava.*;
并且源代碼引用了Employee類。編譯器將嘗試查找java.lang.Employee(因?yàn)榭偸菚?huì)默認(rèn)導(dǎo)入java.lang包)、java.util.Employee、com.horstmann.corejava.Employee和當(dāng)前包中的Employee。它會(huì)在類路徑所有位置中搜索以上各個(gè)類。如果找到一個(gè)以上的類,就會(huì)產(chǎn)生編譯時(shí)錯(cuò)誤(因?yàn)橥耆薅惷仨毷俏ㄒ坏模詉mport語(yǔ)句的次序并不重要)。
編譯器的任務(wù)不止這些,它還要查看源文件是否比類文件新。如果是這樣的話,那么源文件就會(huì)自動(dòng)地重新編譯。在前面已經(jīng)知道,只可以導(dǎo)入其他包中的公共類。一個(gè)源文件只能包含一個(gè)公共類,并且文件名與公共類名必須匹配。因此,編譯器很容易找到公共類的源文件。不過(guò),還可以從當(dāng)前包中導(dǎo)入非公共類。這些類有可能在與類名不同的源文件中定義。如果從當(dāng)前包中導(dǎo)入一個(gè)類,那么編譯器就要搜索當(dāng)前包中的所有源文件,查看哪個(gè)源文件定義了這個(gè)類。
?
設(shè)置類路徑
最好使用-classpath(或-cp,或者Java中的--class-path)選項(xiàng)指定類路徑:
java -classpath /home/user/classdir:.:/home/user/archive.jar MyProg
或者
java -classpath c:\classdir;.;c:\archives\archive.jar MyProg
整個(gè)指令必須寫(xiě)在一行中。將這樣一個(gè)很長(zhǎng)的命令行放在一個(gè)shell腳本或一個(gè)批處理文件中是個(gè)不錯(cuò)的主意。
利用-classpath選項(xiàng)設(shè)置類路徑是首選的方法,另一種方法是通過(guò)設(shè)置CLASSPATH環(huán)境變量來(lái)指定類路徑。具體細(xì)節(jié)依賴于所使用的shell。在Bourne Again shell(bash)中,命令如下:
export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar
在Windows shell中,命令如下:
set CLASSPATH=c:\classdir;.;c:\archives\archive.jar
直到退出shell為止,類路徑設(shè)置均有效。
【警告:有人建議永久地設(shè)置CLASSPATH環(huán)境變量。一般來(lái)說(shuō),這是一個(gè)糟糕地想法。人們有可能會(huì)忘記全局設(shè)置,因此,當(dāng)他們的類沒(méi)有正確地加載時(shí),就會(huì)感到很奇怪。一個(gè)頗受詬病的示例是Windows中Apple QuickTime安裝程序。很多年來(lái),它都將CLASSPATH全局設(shè)置為指向它需要的一個(gè)JAR文件,而沒(méi)有在類路徑中包含當(dāng)前目錄。因此,當(dāng)程序編譯后卻不能運(yùn)行時(shí),無(wú)數(shù)JAVA程序員不得不花費(fèi)很多精力去解決這個(gè)問(wèn)題?!?/p>
【警告:過(guò)去,有人建議完全繞過(guò)類路徑,將所有的JAR文件都放在jre/lie/ext目錄中。這種機(jī)制在Java9中已經(jīng)過(guò)時(shí),不過(guò)不管怎樣這都是一個(gè)不好的建議。從擴(kuò)展目錄加載一些已經(jīng)遺忘很久的類時(shí),這會(huì)讓人非常困惑。】
【注釋:在Java9中,還可以從模塊路徑加載類。本書(shū)卷II的第9章將討論模塊和模塊路徑?!?/p>
?
JAR文件:
在將應(yīng)用程序打包時(shí),你希望只向用戶提供一個(gè)單獨(dú)的文件,而不是一個(gè)包含大量類文件的目錄結(jié)構(gòu),Java歸檔(JAR)文件就是為此目的而設(shè)計(jì)的。JAR文件既可以包含類文件,也可以包含諸如圖像和聲音等其他類型的文件。此處,JAR文件是壓縮的,它使用了我們熟悉的ZIP壓縮格式。
創(chuàng)建JAR文件:
可以使用jar工具制作JAR文件(在默認(rèn)的JDK安裝中,這個(gè)工具位于jdk/bin目錄下)。創(chuàng)建一個(gè)新JAR文件最常用的命令使用以下語(yǔ)法:
jar cvf jarFileName file1file2...
例如:
jar cvf CalculatorClasses.jar*.class icon.gif
通常,jar命令的格式如下:
jar options file1file2...
可以將應(yīng)用程序和代碼庫(kù)打包在JAR文件中。例如,如果想在一個(gè)Java程序中發(fā)送郵件,可以使用打包在文件javax.mail.jar中的一個(gè)庫(kù)。
?
以下是jar程序選項(xiàng):
用法: jar {ctxui}[vfmn0PMe] [jar-file] [manifest-file] [entry-point] [-C dir] files ...
選項(xiàng):
????-c ?創(chuàng)建新檔案
????-t ?列出檔案目錄
????-x ?從檔案中提取指定的 (或所有) 文件
????-u ?更新現(xiàn)有檔案
????-v ?在標(biāo)準(zhǔn)輸出中生成詳細(xì)輸出,詳細(xì)輸出告訴您每個(gè)文件添加到 JAR 文件時(shí)的名稱。
????-f ?指定檔案文件名
????-m ?包含指定清單文件中的清單信息。
????-n ?創(chuàng)建新檔案后執(zhí)行 Pack200 規(guī)范化
????-e ?為捆綁到可執(zhí)行 jar 文件的獨(dú)立應(yīng)用程序指定應(yīng)用程序入口點(diǎn)
????-0 ?(數(shù)字零)選項(xiàng)表示僅存儲(chǔ)而不使用 ZIP 壓縮 JAR 文件
????-P ?保留文件名中的前導(dǎo) '/' (絕對(duì)路徑) 和 ".." (父目錄) 組件
????-M ?指示不應(yīng)生成默認(rèn)清單文件。Jar 工具默認(rèn)會(huì)自動(dòng)將一個(gè)清單文件添加到 Jar 歸檔文件中,其路徑名為 META-INF/ manifest
????-i ?為指定的 jar 文件生成索引信息
????-C ?更改為指定的目錄并包含以下文件
如果任何文件為目錄, 則對(duì)其進(jìn)行遞歸處理。
清單文件名, 檔案文件名和入口點(diǎn)名稱的指定順序
與 'm', 'f' 和 'e' 標(biāo)記的指定順序相同。
示例 1: 將兩個(gè)類文件歸檔到一個(gè)名為 classes.jar 的檔案中:
???????jar cvf classes.jar Foo.class Bar.class
示例 2: 使用現(xiàn)有的清單文件 'mymanifest' 并
???????????將 foo/ 目錄中的所有文件歸檔到 'classes.jar' 中:
???????jar cvfm classes.jar mymanifest -C foo/ .
關(guān)于jar的常見(jiàn)用法請(qǐng)?jiān)L問(wèn):jar工具詳解 - 簡(jiǎn)書(shū) (jianshu.com)
?
清單文件:
除了類文件、圖像和其他資源外,每個(gè)JAR文件還包含一個(gè)清單文件(meanifest),用于描述歸檔文件的特殊特性。
清單文件被命名為MANIFEST.MF,它位于JAR文件的一個(gè)特殊的META.INF子目錄中。合法的最小清單文件極其簡(jiǎn)單:
Manifest-Version: 1.0
簡(jiǎn)單的清單文件可能包含更多條目。這些清單條目被分組為多個(gè)節(jié)。第一節(jié)被稱為主節(jié)(main section)。它作用于整個(gè)JAR文件。隨后的條目可以指定命名實(shí)體的屬性,如單個(gè)文件、包或者URL。它們都必須以一個(gè)Name條目開(kāi)始。節(jié)與節(jié)之間用空行分開(kāi)。例如:
Manifest-Version: 1.0
lines describing this archive
Name: Woozle.class
lines describing this file
Name: com/mycompany/mypkg/
lines describing this package
要想編輯清單文件,需要將希望添加到清單文件中的行放到文本文件中,然后運(yùn)行。
jar cfm jarFileName manifestFileName...
例如,要?jiǎng)?chuàng)建一個(gè)包含清單文件的JAR文件,應(yīng)該運(yùn)行:
java cfm MyArchive.jar manifest.mf com/mycompany/mypkg/*.class
要想更新一個(gè)已有的JAR文件的清單,則需要將增加的部分放置到一個(gè)文本文件中,然后執(zhí)行以下命令:
java ufm MyArchive.jar manifest-additions.mf
【注釋:請(qǐng)參見(jiàn)http://docs.oracle.com/javase/10/docs/specs/jar/jar.html獲得有關(guān)JAR文件和清單文件格式的更多信息?!?/p>
?
可執(zhí)行JAR文件:
可以使用jar命令中的e選項(xiàng)指定程序的入口點(diǎn),即通常調(diào)用java執(zhí)行程序時(shí)指定的類:
java cvfe Myprogram.jar com.mycompany.mypkg.MainAppClass files to add
或者,可以在清單文件中指定程序的主類,包括以下形式的語(yǔ)句:
Main-Class: com.mycompany.mypkg.MainAppClass
不要為主類名加擴(kuò)展名.class。
【警告:清單文件的最后一行必須以換行符結(jié)束。否則,將無(wú)法正確地讀取清單文件。常見(jiàn)的一個(gè)錯(cuò)誤時(shí)創(chuàng)建了一個(gè)只包含Main-Class行而沒(méi)有行結(jié)束符的文本文件。】
不論使用哪一種方法,用戶都可以簡(jiǎn)單地通過(guò)下面的命令來(lái)啟動(dòng)程序:
jar -jar Myprogram.jar
取決于操作系統(tǒng)的配置,用戶甚至可以通過(guò)雙擊JAR文件圖標(biāo)來(lái)啟動(dòng)應(yīng)用程序。下面是各種操作系統(tǒng)的操作方式:
·在Windows平臺(tái)中,Java運(yùn)行時(shí)安裝程序?qū)椤?jar”擴(kuò)展名創(chuàng)建一個(gè)文件關(guān)聯(lián),會(huì)用javaw -jar命令啟動(dòng)文件(與java命令不同,javaw命令不打開(kāi)shell窗口)。
·在Mac OS X平臺(tái)中,操作系統(tǒng)能夠識(shí)別“.jar”擴(kuò)展名文件。雙擊JAR文件時(shí)就會(huì)執(zhí)行Java程序。
不過(guò),人們對(duì)JAR文件中的Java程序與原生應(yīng)用還是感覺(jué)不同。在Windows平臺(tái)中,可以使用第三方的包裝器工具將JAR文件轉(zhuǎn)換成Windows可執(zhí)行文件。包裝器是一個(gè)Windows程序,有大家熟悉的擴(kuò)展名.exe,它可以查找和加載Java虛擬機(jī)(JVM),或者在沒(méi)有找到JVM時(shí)會(huì)告訴用戶應(yīng)該做些什么。有許多商業(yè)的和開(kāi)源的產(chǎn)品,例如,Launch4J(http://launch4j.sourceforge.net)和IzPack(http://izpack.org)。
?
多版本JAR文件:
隨著模塊和包強(qiáng)封裝的引入,之前可以訪問(wèn)的一些內(nèi)部API不再可用。這可能要求庫(kù)提供商為不同Java版本發(fā)布不同的代碼。為此,java9引入了多版本JAR(multi-release JAR)。
為了保證向后兼容,特定于版本的類文件放在META-INF/versions目錄中:
Application.class
BuildingBlocks.class
Util.class
META-INF
->MANIFEST.MF(with line Multi-Release: true)、
??Versions
->9->Application.class
???->BuildingBlocks.class
->10->BuildingBlocks.class
假設(shè)Application類使用了CssParser類,那么遺留版本的Application.class文件可以使用com.sun.javafx.css.CssParer,而Java9版本可以使用javafx.css.Cssparser。
Java8完全不知道META-INF/versions目錄,它只會(huì)加載遺留的類。Java9讀取這個(gè)JAR文件時(shí),則會(huì)使用新版本。
要增加不同的版本的類文件,可以使用--release標(biāo)志:
Jar uf MyProgram.jar --release 9 Application.class
要從頭構(gòu)建一個(gè)多版本JAR文件,可以使用-C選項(xiàng),對(duì)應(yīng)每個(gè)版本要切換到一個(gè)不同的類文件目錄:
Jar cf Myprogram.jar -C bin/8 . --release 9 -C bin/9 Application.class
面向不同版本編譯時(shí),要使用--release標(biāo)志和-d標(biāo)志來(lái)指定輸出目錄:
Javac -d bin/8 --release 8...
在java9中,-d選項(xiàng)會(huì)創(chuàng)建這個(gè)目錄(如果原先該目錄不存在)。
--release標(biāo)志也是Java9新增的。在較早的版本中,需要使用-source、-target和-bootclasspath標(biāo)志。JDK現(xiàn)在為之前的兩個(gè)API版本提供了符號(hào)文件。在Java9中,編譯時(shí)可以將--release設(shè)置為9、8或7.
多版本JAR并不適用于不同版本的程序或庫(kù)。對(duì)于不同的版本,所有類的公共API都應(yīng)當(dāng)是一樣的。多版本JAR的唯一作用是使你的某個(gè)特定版本的程序或庫(kù)能夠使用多個(gè)不同的JDK版本。如果你新增了功能或者改變了一個(gè)API,就應(yīng)當(dāng)提供一個(gè)新版本的JAR。
【注釋:javap之類的工具并沒(méi)有改造為可以處理多版本JAR文件。如果調(diào)用
javap -classpath Myprogram.jar Application.class
你會(huì)得到類的基本版本(畢竟它與更新的版本應(yīng)該有相同的公共API)。如果必須查看更新的版本,則可以調(diào)用:
javap -classpath Myprogram.jar\!/META-INF/versions/9/Application.class
?
關(guān)于命令行選項(xiàng)的說(shuō)明:
Java開(kāi)發(fā)包(JDK)的命令行選項(xiàng)一直以來(lái)都使用單個(gè)短橫線加多字母選項(xiàng)名的形式,如:
java -jar...
javac -Xlint:unchecked -classpath...
但jar命令是個(gè)例外,這個(gè)命令遵循經(jīng)典的tar命令選項(xiàng)格式,而沒(méi)有短橫線:
jar cvf...
從Java9開(kāi)始,Java工具開(kāi)始轉(zhuǎn)向一種更常用的選項(xiàng)格式,多字母選項(xiàng)名前面加兩個(gè)短橫線,另外對(duì)于常用的選項(xiàng)可以使用單字母快捷方式。例如,調(diào)用Linux ls命令時(shí)可以提供一個(gè)“human-readable”選項(xiàng):
ls --human-readble
或者
ls -h
在Java9中,可以使用--version而不是version,另外可以使用--class-path而不是-classpath。在本書(shū)卷II的第9章中可以看到,--module-path選項(xiàng)有一個(gè)快捷方式-p。
詳細(xì)內(nèi)容可以參見(jiàn)JEP293增強(qiáng)請(qǐng)求(http://openjdk.java.net/jeps/293)。在所有清潔工作中,作者還提出要標(biāo)準(zhǔn)化選項(xiàng)參數(shù)。帶--和多字母選項(xiàng)的參數(shù)用空格或者一個(gè)等號(hào)(=)分隔:
Java --class-path /home/user/classdir...
或者
Javac --class-path=/home/user/classdir...
單字母選項(xiàng)的參數(shù)可以用空格分隔,或者直接跟在選項(xiàng)后面:
Javac -p moduledir...
或者
Javac -pmoduledir...
【警告:后一種方式現(xiàn)在不能使用,而且一般來(lái)講這也不是一個(gè)好主意。如果模塊目錄恰好是arameters或rocessor,這就很容易與遺留的選項(xiàng)(parameters或processor)發(fā)生沖突,這又何必呢?】
無(wú)參數(shù)的單字母選項(xiàng)可以組合在一起:
Jar -cvf Myprogram.jar -e mypackage.MyProgram */*.class
【警告:目前不能使用這種方式,這肯定會(huì)帶來(lái)混淆。假設(shè)javac有一個(gè)-c選項(xiàng),那么javac -cp是指java -c -p還是-cp?】
這就會(huì)帶來(lái)一些混亂,希望過(guò)段時(shí)間能夠解決這個(gè)問(wèn)題,盡管我們想要遠(yuǎn)離這些古老的jar選項(xiàng),但最好還是等到塵埃落定為妙。不過(guò),如果你想做到最現(xiàn)代化,那么可以安全地使用jar命令的長(zhǎng)選項(xiàng):
jar --create --verbose ==file jarFileName file1file2...
對(duì)于單字母選項(xiàng),如果不組合,也是可以使用的:
jar -c -v -f jarFileName file1file2...
?
文檔注釋:
JDK包含一個(gè)很有用的工具,叫做javadoc,它可以由源文件生成一個(gè)HTML文檔。
如果在源代碼中添加以特殊定界符/**開(kāi)始的注釋,那么你也可以很容易生成一個(gè)看上去具有專業(yè)水準(zhǔn)的文檔。這是一種很好的方法,因?yàn)檫@樣可以將代碼與注釋放在一個(gè)地方。應(yīng)該知道,如果將文檔存放在一個(gè)單獨(dú)的文件中,隨著時(shí)間的推移,代碼和注釋很可能出現(xiàn)不一致。不過(guò),如果文檔注釋與源代碼在同一個(gè)文件中,就可以很容易地同時(shí)修改源代碼和注釋,然后重新運(yùn)行javadoc。
?
注釋的插入:
Javadoc實(shí)用工具從下面幾項(xiàng)中抽取信息:
·模塊;
·包;
·公共類與接口;
·公共的和受保護(hù)的字段;
·公共的和受保護(hù)的構(gòu)造器及方法。
第5章中將介紹受保護(hù)特性,第6章中將介紹接口,模塊在卷II的第9章介紹。
可以(而且應(yīng)該)為以上各個(gè)特性編寫(xiě)注釋。各個(gè)注釋放置在所描述特性的前面。注釋以/**開(kāi)始,并以*/結(jié)束。
每個(gè)/**。。。*/文檔注釋包含標(biāo)記以及之后緊跟著的自由格式文本(free-form text)。標(biāo)記以@開(kāi)始,如@since或@param。
自由格式文本的第一個(gè)句子應(yīng)該是一個(gè)概要陳述。Javadoc工具自動(dòng)地將這些句子抽取出來(lái)生成概要頁(yè)。
在自由格式文本中,可以使用HTML修飾符,例如,用于強(qiáng)調(diào)的<em>.....</em>、用于著重強(qiáng)調(diào)的<strong>.....</strong>、用于項(xiàng)目符號(hào)列表的<ul>/<li>以及用于包含圖像的<img..../>等。要鍵入等寬代碼,需要使用{@code.....}而不是<code>......</code>——這樣一來(lái),就不用操心對(duì)代碼的<字符轉(zhuǎn)義了。
【注釋:如果文檔中有到其他文件的鏈接,如圖像文件(例如,圖表或用戶界面組件的圖像),就應(yīng)該將這些文件放到包含源文件的目錄下的一個(gè)子目錄doc-files中國(guó)。Javadoc工具將從源目錄將doc-files目錄及其內(nèi)容復(fù)制到文檔目錄中。在鏈接中需要使用doc-files目錄,例如<imgsrc=”doc-files/uml.png”?alt= “UML dagram”/>
?
類注釋:
類注釋必須放在import語(yǔ)句之后,class定義之前。
下面是一個(gè)類注釋的例子:
/**
?* A{@code Card}object represents a playing card, such
?* as "Queen of Hearts". A card has a suit (Diamond, Heard,
?* Spade or Club) and a value (1 = Ace, 2. . . 10, 11 = Jack,
?* 12 = Queen, 13 = King)
?*/
public class Card {
??????. . .
}
【注釋:沒(méi)有必要在每一行的開(kāi)始都添加*,例如,以下注釋同樣是合法的:
**
??A{@code Card}object represents a playing card, such
??as "Queen of Hearts". A card has a suit (Diamond, Heard,
??Spade or Club) and a value (1 = Ace, 2. . . 10, 11 = Jack,
??12 = Queen, 13 = King)
?*/
不過(guò),大部分IDE會(huì)自動(dòng)提供星號(hào),而且換行改變時(shí),還會(huì)重新放置星號(hào)。】
?
方法注釋:
每個(gè)方法注釋必須放在所描述的方法之前。除了通用標(biāo)記之外,還可以使用下面的標(biāo)記:
·@param variable descripition
這個(gè)標(biāo)記將給當(dāng)前方法的“parameters”(參數(shù))部分添加一個(gè)條目。這個(gè)描述可以占據(jù)多行,并且可以使用HTML標(biāo)記。一個(gè)方法的所有@param標(biāo)記必須放在一起。
·@return description
這個(gè)標(biāo)記將給當(dāng)前方法添加“returns”(返回)部分。這個(gè)描述可以跨多行,并且可以使用HTML標(biāo)記。
·@throws class description
這個(gè)標(biāo)記將添加一個(gè)注釋,表示這個(gè)方法有可能拋出異常。有關(guān)異常的詳細(xì)內(nèi)容將在第7章種討論。
下面是一個(gè)方法注釋的示例:
/**
?* Raises the salary of an employee.
?* @param byPercent the percentage by which to raise the salary (e.g., 10 means 10%)
?* @return the amount of the raise
?*/
public double raiseSalary(double byPercent){
????double raise = this.salary * byPercent / 100;
????this.salary += raise;
????return raise;
}
?
字段注釋:
只需要對(duì)公共字段(通常指的是靜態(tài)常量)增加文檔注釋。例如,
/**
?* The "Hearts" card suit
?*/
public static final int HEARTS = 1;
?
通用注釋:
標(biāo)記@since text會(huì)建立一個(gè)“since”(始于)條目。Text(文本)可以是對(duì)引入這個(gè)特性的版本描述。例如,@since 1.7.1
類文檔注釋中可以使用下面的標(biāo)記:
·@author name
這個(gè)標(biāo)記將建立一個(gè)“author”(作者)條目??梢杂卸鄠€(gè)@author標(biāo)記,每個(gè)@author標(biāo)記對(duì)應(yīng)一個(gè)作者。并不是非得使用這個(gè)標(biāo)記,你的版本控制系統(tǒng)能夠更好地跟蹤作者。
·@version text
這個(gè)標(biāo)記將建立一個(gè)“version”(版本)條目。這里的text可以是對(duì)當(dāng)前版本的任何描述。
通過(guò)@see和@link標(biāo)記,可以使用超鏈接,鏈接到j(luò)avadoc文檔的相關(guān)部分或外部文檔。
標(biāo)記@see reference將在“see also”(參見(jiàn))部分增加一個(gè)超鏈接。它可以用于類中,也可以用于方法中。這里的reference(引用)可以有以下選擇:
1.package.class#feature label
2.<a href=". . .">label</a>
3."test"
第一種情況是最有用的。只要提供類、方法或變量的名字,javadoc就在文檔中插入一個(gè)超鏈接。例如,
@see com.horstmann.corejava.Employee#raiseSalary(double)
會(huì)建立一個(gè)超鏈接,鏈接到com.horstmann.corejava.Employee類的raiseSalary(double)方法??梢允÷园?,甚至把包名和類名都省去,這樣一來(lái),這會(huì)位于當(dāng)前包或當(dāng)前類。
需要注意,一定要使用井號(hào)(#),而不要使用句號(hào)(.)分隔類名與方法名(或類名與變量名)。Java編譯器自身可以熟練地確定句點(diǎn)在分割包、子包、類、內(nèi)部類以及方法和變量時(shí)的不同含義。但是javadoc工具就沒(méi)有這么聰明了,因此必須對(duì)它提供幫助。
如果@see標(biāo)記后面有一個(gè)<字符,就需要指定一個(gè)超鏈接。可以超鏈接到任何URL。例如:
@see <a href="www.horstmann.com/corejava.html">The Core Java home page</a>
在上述各種情況下,都可以指定一個(gè)可選的標(biāo)簽(label),這回顯示為鏈接錨(link anchor)。如果省略了標(biāo)簽,則用戶看到的錨就是目標(biāo)代碼名或URL。
如果@see標(biāo)記后面有一個(gè)雙引號(hào)(“)字符,文本就會(huì)顯示在”see also”部分。例如,
@see "Core Java 2 volume 2"
可以為一個(gè)特性添加多個(gè)@see標(biāo)記,但必須將它們放在一起。
如果愿意,可以在任何文檔注釋中放置指向其他類或方法的超鏈接。可以在注釋中的任何位置插入一個(gè)形式如下的特殊標(biāo)記:
{@link package.class#feature label}
這里的特性描述規(guī)則與@see標(biāo)記的規(guī)則相同。
最后,在Java9中,還可以使用{@index entry}標(biāo)記為搜索框增加一個(gè)條目。
?
包注釋:
可以直接將類、方法和變量的注釋放置在Java源文件中,只要用/**......*/文檔注釋界定就可以了。但是,要想產(chǎn)生包注釋,就需要在每一個(gè)包目錄中添加一個(gè)單獨(dú)的文件??梢杂腥缦聝蓚€(gè)選擇:
1.?提供一個(gè)名為package-info.java的Java文件。這個(gè)文件必須包含一個(gè)初始的Javadoc注釋,以/**和*/界定,后面是一個(gè)package語(yǔ)句。它不能包含更多的代碼或注釋。
2.?提供一個(gè)名為package.html的HTML文件,抽取標(biāo)記<body>. . .</body>之間的所有文本。
?
注釋提取:
在這里,假設(shè)你希望HTML文件將放在名為docDirectory的目錄下。執(zhí)行以下步驟:
1.?切換到源文件目錄,其中包含想要生成文檔的源文件。如果有嵌套的包要生成文檔,例如com.horstmann.corejava,就必須切換到包含子目錄com的目錄(如果提供overview.html文件的話,這就是這個(gè)文件所在的目錄)。
2.?如果是一個(gè)包,應(yīng)該運(yùn)行命令:
javadoc -d docDirectory nameOfPakage
或者,如果要為多個(gè)包生成文檔,運(yùn)行:
javadoc -d docDirectory nameOfPakage1 nameOfpackage2. . .
如果你的文件在無(wú)名包中,則應(yīng)該運(yùn)行:
javadoc -d docDirectory *.java
如果省略了-d docDirectory選項(xiàng),HTML文件就會(huì)提取到到當(dāng)前目錄下。這樣可能很混亂,因此我不提倡這種做法。
可以使用很多命令行選項(xiàng)對(duì)javadoc程序進(jìn)行微調(diào)。例如,可以使用-auther和-version選項(xiàng)在文檔包含@author和@version標(biāo)記(默認(rèn)情況下,這些標(biāo)記會(huì)被省略)。另一個(gè)很有用的選項(xiàng)是-link,用來(lái)為標(biāo)準(zhǔn)類添加超鏈接。例如,如果使用命令:
javadoc -link http://docs.oracle.com/javase/9/docs/api *.java
那么,所有的標(biāo)準(zhǔn)類庫(kù)的類都會(huì)自動(dòng)地鏈接到Oracle網(wǎng)站的文檔。
如果使用-linksource選項(xiàng),那么每個(gè)源文件就會(huì)轉(zhuǎn)換為HTML(不對(duì)代碼著色,但包含行號(hào)),并且每個(gè)類和方法名將變?yōu)橹赶蛟创a的超鏈接。
還可以為所有源文件提供一個(gè)概要注釋。把它放在一個(gè)類似overview.html的文件中,運(yùn)行javadoc工具,并提供命令行選項(xiàng)-overview filename。將抽取標(biāo)價(jià)<body>......</body>之間的所有文本。當(dāng)用戶從導(dǎo)航欄中選擇“Overview”時(shí),就會(huì)顯示這些內(nèi)容。
有關(guān)其他的選項(xiàng),請(qǐng)查閱javadoc工具的聯(lián)機(jī)文檔http://docs.oracle.com/javase/9/javadoc/javadoc.html。
?
類設(shè)計(jì)技巧:
我們不會(huì)面面俱到,也不希望過(guò)于沉悶,所以在這一章結(jié)束之前再簡(jiǎn)單地介紹幾點(diǎn)技巧。應(yīng)用這些技巧可以使你設(shè)計(jì)的類更能得到專業(yè)OOP圈子的認(rèn)可。
1.一定要保證數(shù)據(jù)私有。
這是最重要的;絕對(duì)不要破壞封裝性。有時(shí)候,可能需要編寫(xiě)一個(gè)訪問(wèn)器方法或更改器方法,但最好還是保持實(shí)例字段的私有性。很多慘痛的教訓(xùn)告訴我們,數(shù)據(jù)的表示形式很可能會(huì)改變,但它們的使用方式卻不會(huì)經(jīng)常變化。當(dāng)數(shù)據(jù)保持私有時(shí),表示形式的變化不會(huì)對(duì)類的使用者產(chǎn)生影響,而且也更容易檢測(cè)bug。
2.一定要初始化數(shù)據(jù)。
Java不會(huì)為你初始化局部變量,但是會(huì)對(duì)對(duì)象的實(shí)例字段進(jìn)行初始化。最好不要依賴于系統(tǒng)的默認(rèn)值,而是應(yīng)該顯式地初始化所有變量,可以提供默認(rèn)值,也可以在所有構(gòu)造器中設(shè)置默認(rèn)值。
3.不要在類中使用過(guò)多的基礎(chǔ)類型。
其想法是要用其他類,而不是使用多個(gè)相關(guān)的基本類型。這樣會(huì)使類更易于理解,也更易于修改。例如,可以用一個(gè)名為Address的新類替換一個(gè)Customer類中的以下實(shí)例字段:
private String street;
private String city;
private String state;
private int zip;
這樣一來(lái),可以很容易地處理地址的變化,例如,可能需要處理國(guó)際地址。
4.不是所有的字段都需要單獨(dú)的字段訪問(wèn)器和更改器。
你可能需要獲得或設(shè)置員工的工資。而一旦構(gòu)造了員工對(duì)象,肯定不需要更改雇傭日期。另外,在對(duì)象中,常常包含一些不希望別人獲得或設(shè)置的實(shí)例字段,例如,Address類中的州縮寫(xiě)數(shù)組。
5.分解有過(guò)多職責(zé)的類。
這樣說(shuō)似乎有點(diǎn)含糊,究竟多少算是“過(guò)多”?每個(gè)人的看法都不同。但是,如果明顯地可以將一個(gè)復(fù)雜的類分解成兩個(gè)概念上更為簡(jiǎn)單的類,就應(yīng)該進(jìn)行分解。(但另一方面,也不要走極端。如果設(shè)計(jì)10個(gè)類,每個(gè)類只有一個(gè)方法,顯然就有些矯枉過(guò)正了。)
下面是一個(gè)反面的設(shè)計(jì)示例。
public class CardDeck // bad design
{
????private int[] value;
????private int[] suit;
????
????public CardDeck(){....}
????public void shuffle{.....}
????public int getTopValue(){.....}
????public int getTopSuit(){.....}
????public void draw(){......}
}
實(shí)際上,這個(gè)類實(shí)現(xiàn)了兩個(gè)獨(dú)立的概念:一副牌(包含shuffle方法和draw方法)和一張牌(包含查看面值和花色的方法)。最好引入一個(gè)表示一張牌的Card類?,F(xiàn)在有兩個(gè)類,每個(gè)類分別完成自己的職責(zé):
public class CardDeck // bad design
{
????private Card[] cards;
????public CardDeck(){....}
????public void shuffle{.....}
????public int getTopValue(){.....}
????public int getTopSuit(){.....}
????public void draw(){......}
}
public class Card
{
????private int value;
????private int suit;
????
????public Card(int aValue, int aSuit){.......}
????public int getValue(){.......}
????public int getSuit(){......}
}
6.類名和方法名要能夠體現(xiàn)它們的職責(zé)。
變量應(yīng)該有一個(gè)能夠反映其含義的名字,類似地,類也應(yīng)該如此(在標(biāo)準(zhǔn)類庫(kù)中,確實(shí)存在著一些含義不明確的例子,如Date類實(shí)際上是一個(gè)描述時(shí)間的類)。
對(duì)此有一個(gè)很好的慣例:類名應(yīng)當(dāng)是一個(gè)名詞(Order),或者是前面有形容詞修飾的名詞(RushOrder),或者是有動(dòng)名詞(有“-ing”后綴)修飾的名詞(例如,BillingAddress)。對(duì)于方法來(lái)說(shuō),要遵循標(biāo)準(zhǔn)慣例:訪問(wèn)器方法用小寫(xiě)get開(kāi)頭(getSalary),更改器方法用小寫(xiě)的set開(kāi)頭(setSalary)。
7.?優(yōu)先使用不可變的類。
LocalDate類以及java.time包中的其他類是不可變的——沒(méi)有方法能夠修改對(duì)象的狀態(tài)。類似plusDays的方法并不會(huì)更改對(duì)象,而是會(huì)返回狀態(tài)已修改的新對(duì)象。
更改對(duì)象的問(wèn)題在于,如果多個(gè)線程試圖同時(shí)更新一個(gè)對(duì)象,就會(huì)發(fā)生并發(fā)更改,其結(jié)果是不可預(yù)料的。如果類是不可變的,就可以安全地在多個(gè)線程間共享其對(duì)象。
因此,要盡可能讓類是不可能變的,這是一個(gè)很好的想法。對(duì)于表示值的類,如一個(gè)字符串或一個(gè)時(shí)間點(diǎn),這尤其容易。計(jì)算會(huì)生成新值,而不是更新原來(lái)的值。
當(dāng)然,并不是所有類都應(yīng)當(dāng)是不可變的。如果員工加薪時(shí)讓raiseSalary方法返回一個(gè)新的Employee對(duì)象,這會(huì)很奇怪。
本章介紹了有關(guān)對(duì)象和類的基礎(chǔ)知識(shí),這使得Java可以作為一種“基于對(duì)象”的語(yǔ)言。要真正做到面向?qū)ο?,程序設(shè)計(jì)語(yǔ)言還必須支持繼承和多態(tài)。Java提供了對(duì)這些特性的支持,具體內(nèi)容將在下一章中介紹。
【學(xué)習(xí)參考書(shū)籍:《Java核心技術(shù)卷I》】?