[Java核心技術(shù)1] 第4章 對(duì)象和類
面向?qū)ο蟪绦蛟O(shè)計(jì)(OOP)與面向過(guò)程的程序設(shè)計(jì)在思維方式上有很大的差別,OOP將數(shù)據(jù)放在第一位,然后再考慮操作數(shù)據(jù)的算法。OOP中不必關(guān)注具體的實(shí)現(xiàn),只要能滿足需求即可。
4.1 面向?qū)ο蟮某绦蚋攀?/h2>
4.1.1 類
類是什么?一類事物的抽象描述?
類(class)是構(gòu)造對(duì)象的模板或藍(lán)圖,由類構(gòu)造(construct) 對(duì)象的過(guò)程稱為創(chuàng)建類的實(shí)例 (instance )。
封裝( encapsulation)關(guān)鍵在于絕對(duì)不能讓類中的方法直接地訪問(wèn)其他類的實(shí)例域。
繼承(inheritance)通過(guò)擴(kuò)展一個(gè)類來(lái)建立另外一個(gè)類的過(guò)程稱。
4.1.2 對(duì)象
對(duì)象中的數(shù)據(jù)稱為實(shí)例域( instance field ),操縱數(shù)據(jù)的過(guò)程稱為方法( method ) 對(duì)于每個(gè)特定的類實(shí)例(對(duì)象)都有一組特定的實(shí)例域值。這些值的集合就是這個(gè)對(duì)象的當(dāng)前狀態(tài)( state )。
對(duì)象的主要特性:
- 對(duì)象的行為(behavior)——用可調(diào)用的方法定義
- 對(duì)象的狀態(tài)(state)——對(duì)象的狀態(tài)通過(guò)調(diào)用方法實(shí)現(xiàn)(否則,封裝性被破壞)
- 對(duì)象標(biāo)識(shí)(identity)——對(duì)象的標(biāo)識(shí)是不同的,狀態(tài)也存在差異
4.1.3 標(biāo)識(shí)類
OOP首先從設(shè)計(jì)類開(kāi)始,然往類中添加方法。
設(shè)計(jì)一個(gè)類時(shí),名詞可能成為類,如:類Item,Order等。
動(dòng)詞可能成為方法,如add是Order類的一個(gè)方法,而Item對(duì)象是一個(gè)參數(shù)。
4.1.4 類之間的關(guān)系
類之間常見(jiàn)的關(guān)系有:
- 依賴(Dependency) 如果一個(gè)類的方法操縱另一個(gè)類的對(duì)象,我們就說(shuō)一個(gè)類依賴于另一個(gè)類。一般而言,依賴關(guān)系在Java語(yǔ)言中體現(xiàn)為局域變量、方法的形參,或者對(duì)靜態(tài)方法的調(diào)用。
Try to minimize the number of classes that depend on each other. The point is, if a class A is unaware of the existence of a class B, it is also unconcerned about any changes to B. (And this means that changes to B do not introduce bugs into A.) In software engineering terminology, you want to minimize the coupling between classes.
應(yīng)該盡可能地將相互依賴的類減至最少。如果類 A 不知道 B 的存在, 它就不會(huì)關(guān)心 B的任何改變(這意味著 B 的改變不會(huì)導(dǎo)致 A 產(chǎn)生任何 bug )。用軟件工程的術(shù)語(yǔ)來(lái)說(shuō),就是讓類之間的耦合度最小。
- 聚合(Aggregation) 聚合關(guān)系意味著類 A 的對(duì)象包含類 B 的對(duì)象。例如, 一個(gè)Order 對(duì)象包含一些 Item 對(duì)象。聚合關(guān)系通過(guò)實(shí)例變量實(shí)現(xiàn)的。但兩個(gè)類是處在不平等層次上的,一個(gè)代表整體,另一個(gè)代表部分。
- 繼承(Inheritance) 類A擴(kuò)展類B,類A包含從類B繼承的方法,并且還會(huì)有一些其他的功能。類A中包含一些優(yōu)先處理的特殊方法。
4.2預(yù)定義類
Java中并不是所有類都具有面向?qū)ο蟮奶卣鳎热鏜ath類。Math類只封裝了功能,如Math.random方法,由于沒(méi)有數(shù)據(jù),因此不必?fù)?dān)心生成對(duì)象以及初始化實(shí)例域。
4.2.1 對(duì)象和對(duì)象變量
Java中使用構(gòu)造器(constructor)構(gòu)造新實(shí)例。構(gòu)造器與類名相同,在構(gòu)造器前面加上new操作符構(gòu)造一個(gè)新類。
new Date();//構(gòu)造一個(gè)新對(duì)象,初始化為當(dāng)前時(shí)間
System.out.println(new Date());//也可以將對(duì)象傳遞個(gè)一個(gè)方法
String s=new Date().toString();//也可以將一個(gè)方法應(yīng)用于剛剛創(chuàng)建的對(duì)象
Date birthdayday=new Date();//通常,構(gòu)造的對(duì)象存放在一個(gè)變量中,會(huì)被多次使用。
對(duì)象與對(duì)象變量間的區(qū)別:
Date deadline;//定義一個(gè)對(duì)象變量deadline
s=deadline.toString();
//報(bào)錯(cuò),因?yàn)閐eadline不是一個(gè)對(duì)象,也沒(méi)有引用對(duì)象,不能將任何Date方法應(yīng)用這個(gè)變量
初始化變量deadline有兩種方法
deadline=new Date(); //構(gòu)造新的對(duì)象
deadline=birthday;//引用已經(jīng)存在的對(duì)象
It is important to realize that an object variable doesn’t actually contain an object. It only refers to an object.
In Java, the value of any object variable is a reference to an object that is stored elsewhere. The return value of the new operator is also a reference.一定要認(rèn)識(shí)到 : 一個(gè)對(duì)象變量并沒(méi)有實(shí)際包含一個(gè)對(duì)象, 而僅僅引用一個(gè)對(duì)象 。
在Java中,任何對(duì)象變量的值都是對(duì)存儲(chǔ)在另一個(gè)地方的對(duì)象的引用。new操作符返回的也是一個(gè)引用
局部變量不會(huì)自動(dòng)初始化null。必須調(diào)用new 或者設(shè)置為null,表明這個(gè)對(duì)象變量目前沒(méi)有引用任何對(duì)象。
4.2.2 Java類庫(kù)中的LocalDate類
在Java中,Date類用來(lái)表示時(shí)間點(diǎn),LocalDate類是表示日歷。將時(shí)間和日歷分開(kāi)是一種很好的面向?qū)ο笤O(shè)計(jì),使用不同的類表示不同的概念。
不要使用構(gòu)造器來(lái)構(gòu)造 LocalDate 類的對(duì)象。實(shí)際上,應(yīng)當(dāng)使用靜態(tài)工廠方法 (factorymethod) 。
LocalDate.now();//構(gòu)造一個(gè)新的LocalDate對(duì)象
LocalDate.of(1998.2.16);//構(gòu)造一個(gè)特定日期的對(duì)象
LocalDate newYearsEve=LocalDate.of(1999,12.31);//構(gòu)造的對(duì)象存在對(duì)象變量中
4.2.3 更改器方法和訪問(wèn)器方法
更該器方法,是一種用來(lái)控制變量變化的方法。它們也被稱為setter方法。返回私有成員變量的值。
GregorianCalendar.add方法是一個(gè)更改器方法,調(diào)用這個(gè)方法后someDay會(huì)改變。
GregorianCalendar someDay=new GregorianCalendar(1999.11.31);
//這個(gè)類的月是從0到11
someDay.add(alendar.DAY_OF_MONTH,1000);
訪問(wèn)器方法,只訪問(wèn)對(duì)象而不修改對(duì)象。
LocalDate.getYear和GregorianCalendar.get是訪問(wèn)器方法。
int year=someDay.get(Calendar.YEAR);
因?yàn)?LocalDate 被設(shè)計(jì)為不可變對(duì)象,這樣的話每個(gè)修改當(dāng)前 LocalDate 對(duì)象的方法(比如 plusDays),都會(huì)返回一個(gè)新的 LocalDate 對(duì)象,而原有的 LocalDate 對(duì)象不會(huì)發(fā)生改變。
LocalDate aThousandDays=newYearsEve.plusDays(1000);
//plusDays方法會(huì)生成一個(gè)新的對(duì)象,plusDays方法沒(méi)有更改調(diào)用這個(gè)方法的對(duì)象。
4.3 用戶自定義
設(shè)計(jì)復(fù)雜應(yīng)用程序需要各種主力類(workhorse class),這些類通常沒(méi)有main方法。
一個(gè)完整的應(yīng)用程序,需要將若干類組合起來(lái),其中只有一個(gè)main方法。
4.3.1 Employee類
在Java中定義類,定義一個(gè)Employee類
class ClassName{
field1
field2
...
constructor1
constructor2
...
method1
method2
}
4.3.2 多個(gè)源文件的使用
在一個(gè)源文件中,只能有一個(gè)公有類,但可以有任意數(shù)量的非公有類。編譯源代碼時(shí),每個(gè)類都會(huì)生稱一個(gè)對(duì)應(yīng)的class文件。
如果將每一個(gè)類存在一個(gè)單獨(dú)的源文件中。當(dāng)編譯器發(fā)現(xiàn)A類使用了B類時(shí),會(huì)查找B.class文件,如果沒(méi)找到,就會(huì)搜索B.java并編譯;如果B.java比B.class新,也會(huì)重新編譯。
4.3.3 解剖Employee類
類通常包含一個(gè)構(gòu)造器(或者多個(gè))和若干方法,方法標(biāo)記為public,任何類都可以調(diào)用public修飾的方法。
實(shí)例域由private修飾,類自身的方法能夠訪問(wèn)這些實(shí)例域,其他類的方法不能。
類通常包含類類型的字段(如String)。
4.3.4 從構(gòu)造器開(kāi)始
構(gòu)造器與類名相同。構(gòu)造器總是伴隨著new操作符的執(zhí)行被調(diào)用,以便將實(shí)例域初始化為所希望的狀態(tài)。
- 構(gòu)造器與類名相同
- 每個(gè)類可以有一個(gè)以上的構(gòu)造器
- 構(gòu)造器可以有0個(gè),1個(gè)或多個(gè)
- 構(gòu)造器沒(méi)有返回值
- 構(gòu)造器總是伴隨著new操作一起調(diào)用
注意在構(gòu)造器中不要定義與實(shí)例域同名的變量,因?yàn)檫@些變量只能在構(gòu)造器內(nèi)部訪問(wèn)。
4.3.5 隱式參數(shù)和顯示參數(shù)
在每一個(gè)方法中,出現(xiàn)在方法前的類對(duì)象稱為隱式(implicit)參數(shù),方法括號(hào)中的數(shù)值是顯示(explicit)參數(shù)。
關(guān)鍵字this表示隱式參數(shù)。
4.3.6 封裝的優(yōu)點(diǎn)
getXX方法是典型的域訪問(wèn)器,它們只返回實(shí)例值域。
如果域不是是public的,修改這個(gè)域值的搗亂者可能在任何地方。
在有些時(shí)候, 需要獲得或設(shè)置實(shí)例域的值,應(yīng)當(dāng)提供下面的內(nèi)容
- 一個(gè)私有的數(shù)據(jù)域
- 一個(gè)公有的域訪問(wèn)器方法
- 有個(gè)公有的域修改器方法
這么做的好處是,可以改變內(nèi)部的實(shí)現(xiàn),而不影響其他代碼;二是更改器可以執(zhí)行錯(cuò)誤檢測(cè),而賦值是不會(huì)進(jìn)行這種處理的。
請(qǐng)注意不要編寫返回可變對(duì)象引用的訪問(wèn)器方法
4.3.7 基于類的訪問(wèn)權(quán)限
一個(gè)方法可以訪問(wèn)所屬類的對(duì)象的私有數(shù)據(jù)。
典型的調(diào)用方式
a.equals(b)...
方法可以訪問(wèn)所屬類的私有特性(feature),而不僅限于訪問(wèn)調(diào)用對(duì)象(隱式參數(shù))的私有特性。
4.3.8 私有方法
有時(shí),希望一個(gè)計(jì)算代碼可以劃分為若干獨(dú)立的輔助方法。它們與實(shí)現(xiàn)機(jī)制非常緊密,或者有特別的協(xié)議或調(diào)用次序。這種方法最好設(shè)計(jì)為private。
對(duì)于私有方法,可以進(jìn)行改變;如果改用其他的方法實(shí)現(xiàn)相應(yīng)的操作,可以將其刪除。如果方法是公有的, 就不能將其刪去,因?yàn)槠渌拇a很可能依賴它。
4.3.9 final實(shí)例域
定義為final的實(shí)例域,構(gòu)建對(duì)象的時(shí)候必須初始化。并且之后不能再修改。
final大多應(yīng)用于基本(primitive)類型域,或不可變(immutable)類的域
不可變類中沒(méi)有改變對(duì)象的方法,如String,每次對(duì)于String對(duì)象的修改都將產(chǎn)生一個(gè)新的String對(duì)象,而原來(lái)的對(duì)象保持不變 。
StringBuilder是可變類,因?yàn)槊看螌?duì)于它的對(duì)象的修改都作用于該對(duì)象本身,并沒(méi)有產(chǎn)生新的對(duì)象。
final修飾的可變的類,表示存儲(chǔ)在變量中的對(duì)象引用不會(huì)再指向其他的類,不過(guò)這個(gè)可變對(duì)象可以更改。
private final StringBuilder evaluations= new StringBuilder();
public void giveGoldStar()
{
evaluations.append(LocalDate.now() + ": Gold star!\n");
}
4.4 靜態(tài)域與靜態(tài)方法
main方法都被標(biāo)記為static,這個(gè)符號(hào)有什么含義呢?
4.4.1 靜態(tài)域
如果將域定義為static,這個(gè)域?qū)儆陬?,不屬于類?duì)象。這樣的域只存在類,而對(duì)于實(shí)例域卻在每個(gè)對(duì)象中都有拷貝。
class Employee
{
private static int nextId = 1;
private int id;
. . .
}
public void setId()
{
id = nextId;
nextId++;
}
. . .
//harry 的 id 域被設(shè)置為靜態(tài)域 nextld 當(dāng)前的值,并且靜態(tài)域 nextld 的值加 1:
harry.id = Employee.nextId;
Employee.nextId++;
類的所以實(shí)例共享一個(gè)nextId,每個(gè)對(duì)象都有自己的id。
4.4.2 靜態(tài)常量
靜態(tài)常量使用比靜態(tài)變量多。如在Math類中的PI
public class Math
{
. . .
public static final double PI = 3.14159265358979323846;
. . .
}
雖然類中的域最好不要設(shè)計(jì)成public,但是公有常量(final域)可以。
4.4.3 靜態(tài)方法
靜態(tài)方法同樣屬于類且不屬于類對(duì)象,或者說(shuō)靜態(tài)方法沒(méi)有隱式參數(shù)(this)。靜態(tài)方法不能訪問(wèn)實(shí)例域,因?yàn)樗荒懿僮鲗?duì)象:不過(guò)它可以訪問(wèn)靜態(tài)域。
使用靜態(tài)方法時(shí),建議使用類名而不是對(duì)象名調(diào)用靜態(tài)方法。
使用靜態(tài)方法的情況:
- 方法不訪問(wèn)對(duì)象狀態(tài),其所需參數(shù)通過(guò)顯示參數(shù)提供(例如:Math.pow)。
- 方法只訪問(wèn)靜態(tài)域。
4.4.4 工廠方法
靜態(tài)方法的另一用途,如LocalDate和NumberFormat類使用的靜態(tài)工廠方法(factory method)來(lái)構(gòu)造對(duì)象。
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
NumberFormat percentFormatter = NumberFormat.getPercentInstance();
double x = 0.1;
System.out.println(currencyFormatter.format(x)); // prints $0.10
System.out.println(percentFormatter.format(x)); // prints 10%
為什么用工廠方法而不用構(gòu)造器?
- 無(wú)法重命名構(gòu)造器。而工廠方法可以采用不同的名字。
- 構(gòu)造器無(wú)法改變所構(gòu)造的對(duì)象類型。而NumberFormat的工廠方法可以返回一個(gè)DecimalFormat類對(duì)象(NumberFormat的子類)
4.4.5 main方法
main方法也是靜態(tài)方法,不對(duì)任何對(duì)象進(jìn)行操作。而且在啟動(dòng)程序時(shí)還沒(méi)有任何對(duì)象,只有靜態(tài)的main方法將執(zhí)行并創(chuàng)建對(duì)象。
在類中添加一個(gè)main方法,可進(jìn)行單元測(cè)試。
4.5 方法參數(shù)
Java采用的是按值調(diào)用,即方法得到的是參數(shù)值的一個(gè)拷貝,方法不能修改傳遞給它的參數(shù)變量的內(nèi)容:
The term call by value means that the method gets just the value that the caller provides. In contrast, call by reference means that the method gets the location of the variable that the caller provides. Thus, a method can modify the value stored in a variable that is passed by reference but not in one that is passed by value.
按值調(diào)用意味著該方法只獲取調(diào)用者提供的值。相反,引用的調(diào)用意味著該方法獲取調(diào)用者提供的變量的位置。因此,一個(gè)方法可以修改被引用而不是通過(guò)值傳遞的變量中存儲(chǔ)的值。
public static void tripleValue(double x) // doesn't work
{
x = 3 * x;
}
double percent = 10;
tripleValue(percent);
//執(zhí)行后percent的值還是10;
方法參數(shù)有兩種類型:基本數(shù)據(jù)類型、對(duì)象引用。
雖然不能修改基本數(shù)據(jù)類型,但是對(duì)象引用作參數(shù)就不同了:
public static void tripleSalary(Employee x) // works
{
x.raiseSalary(200);
}
harry = new Employee(. . .);
tripleSalary(harry);
/*
1 )X 被初始化為 harry 值的拷貝,這里是一個(gè)對(duì)象的引用。
2 ) raiseSalary 方法應(yīng)用于這個(gè)對(duì)象引用。x 和 harry 同時(shí)引用的那個(gè) Employee 對(duì)象的薪金提高了 200%。
3 ) 方法結(jié)束后,參數(shù)變量 x 不再使用。當(dāng)然,對(duì)象變量 harry 繼續(xù)引用那個(gè)薪金增至3倍的雇員對(duì)象
*/
看起來(lái)Java對(duì)對(duì)象采用的是引用調(diào)用,但實(shí)際上并不是:
public static void swap(Employee x, Employee y) // doesn't work
{
Employee temp = x;
x = y;
y = temp;
}
Employee a = new Employee("Alice", . . .);
Employee b = new Employee("Bob", . . .);
swap(a, b);
//交換的是x和y兩個(gè)對(duì)象的引用拷貝,a和b的引用仍然沒(méi)有變
Java 對(duì)對(duì)象采用的不是引用調(diào)用,實(shí)際上,對(duì)象引用是按值傳遞的。
Java中方法參數(shù)的使用情況:
- 方法不能修改基本數(shù)據(jù)類型的參數(shù)。
- 方法可以改變對(duì)象參數(shù)的狀態(tài)。
- 方法不能讓對(duì)象參數(shù)引用新的變量。
4.6 對(duì)象構(gòu)造
Java提供了多種編寫構(gòu)造器的機(jī)制。
4.6.1 重載
有些類可以有多個(gè)構(gòu)造器
有相同的名字,不同的參數(shù),這種特征叫做重載(overloading)。
編譯器通過(guò)各方法的參數(shù)類型和返回類型匹配相應(yīng)的方法,如果找不到匹配的參數(shù),則產(chǎn)生編譯錯(cuò)誤。(這個(gè)過(guò)程被稱為重載解析(overloading resolution)。)
Java允許重載任何方法,而不只是構(gòu)造器方法。
方法的方法名和參數(shù)類型叫做方法的簽名(signature)。返回類型不是方法簽名的一部分。也就是說(shuō),不能有兩個(gè)方法簽名相同,返回類型不同的方法。
4.6.2 默認(rèn)域的初始化
如果構(gòu)造器沒(méi)有顯示的給域賦值,則默認(rèn)為:數(shù)值為 0、布爾值為 false、 對(duì)象引用為 null。
This is an important difference between fields and local variables.You must always explicitly initialize local variables in a method. But in a class, if you don’t initialize a field, it is automatically initialized to a default (0, false, or null).
這是域與局部變量的主要不同點(diǎn)。 必須明確地初始化方法中的局部變量。但是,如果沒(méi)有初始化類中的域,會(huì)被自動(dòng)初始化為默認(rèn)值( 0、false 或 null )。
如果不明確的對(duì)域進(jìn)行初始化,會(huì)影響代碼的可讀性,這并不是一種良好的編程習(xí)慣。
4.6.3 無(wú)參數(shù)的構(gòu)造器
當(dāng)類中沒(méi)有任何構(gòu)造器時(shí),系統(tǒng)才會(huì)提供一個(gè)默認(rèn)的無(wú)參數(shù)的構(gòu)造器,并將所有實(shí)例域設(shè)置為默認(rèn)值。
如果類中提供了至少一個(gè)構(gòu)造器,但是沒(méi)有無(wú)參數(shù)的構(gòu)造器,則構(gòu)造對(duì)象不提供參數(shù)會(huì)被視為不合法。
4.6.4 顯示域的初始化
在類定義時(shí)可以直接賦值給任何域。
在構(gòu)造器之前,先執(zhí)行賦值操作。如果類的所有構(gòu)造器都需要將特定的實(shí)例字段設(shè)置為相同的值,那么這種語(yǔ)法特別有用。
也可以調(diào)用方法對(duì)域進(jìn)行初始化。
class Employee{
private static int nextId;
private int id = assignId();
. . .
private static int assignId(){
int r = nextId;
nextId++;
return r;
}
. . .
}
4.6.5 參數(shù)名
編寫構(gòu)造器時(shí),參數(shù)命名也是有講究的。
public Employee(String n,double s){
name=n;
salary=s;
}
//這種的缺陷是要閱讀源碼才能理解n和s的含義
可以在參數(shù)前加上"a"
public Employee(String aName,double aSalary){
name=aName;
salary=aSalary;
}
//一目了然
參數(shù)變量使用相同的名字將實(shí)例域屏蔽起來(lái)。采用this訪問(wèn)實(shí)例域。
public Employee(String naie, double salary)
{
this.name = name;
this.salary = salary;
}
//this指隱式參數(shù),即所構(gòu)造的對(duì)象
4.6.6 調(diào)用另一個(gè)構(gòu)造器
如果在構(gòu)造器的第一個(gè)語(yǔ)句是this(...),這個(gè)構(gòu)造器將調(diào)用同類中的另一個(gè)構(gòu)造器。
public Employee(double s)
{
// calls Employee(String, double)
this("Employee #" + nextld, s);
nextld++;
}
使用this,對(duì)公共的構(gòu)造器代碼只編寫一次
4.6.7 初始化塊
前面已經(jīng)講了初始化數(shù)據(jù)域的兩種方法:
- 在構(gòu)造器中設(shè)置
- 在聲明時(shí)賦值
還有第三種機(jī)制,初始化代碼塊(initialization block)。只要構(gòu)造類的對(duì)象,這些塊就會(huì)被執(zhí)行。首先運(yùn)行初始化代碼塊,然后才運(yùn)行構(gòu)造器的主體部分。
由于初始化數(shù)據(jù)域有多種途徑,下面是調(diào)用構(gòu)造器的具體處理步驟:
- 所有數(shù)據(jù)域被初始化為默認(rèn)值。
- 按照在類中聲明的次序,依次執(zhí)行所有域初始化語(yǔ)句和初始化塊。
- 如果構(gòu)造器第一行調(diào)用了第二個(gè)構(gòu)造器,則執(zhí)行第二個(gè)構(gòu)造器主體。
- 執(zhí)行這個(gè)構(gòu)造器的主體。
如果對(duì)類的靜態(tài)域進(jìn)行初始化的代碼比較復(fù)雜,那么可以使用靜態(tài)的初始化塊。將代碼放入一個(gè)塊中,并標(biāo)記關(guān)鍵字static。靜態(tài)代碼塊按照類定義的順序執(zhí)行。
4.6.8 對(duì)象析構(gòu)和finalize方法
在析構(gòu)器中,最常見(jiàn)的操作是回收分配給對(duì)象的存儲(chǔ)空間。由于 Java 有自動(dòng)的垃圾回收器,不需要人工回收內(nèi)存, 所以 Java 不支持析構(gòu)器。
如果對(duì)象使用了內(nèi)存之外的資源,如文件或系統(tǒng)資源的另一個(gè)對(duì)象的句柄,這時(shí)回收不需要的資源就十分重要。
可以為類添加一個(gè)finalize方法,此方法在對(duì)象被清除前調(diào)用。而在實(shí)際應(yīng)用中,不要依賴于使用 finalize 方法回收任何短缺的資源, 因?yàn)楹茈y知道這個(gè)方法什么時(shí)候才能夠調(diào)用。
如果某個(gè)資源在使用完后需要立即關(guān)閉,可以應(yīng)用close方法來(lái)完成相應(yīng)的清理操作。
4.7 包
Java使用包(package)將類組織起來(lái)。使用包類確保類名的唯一性。
4.7.1 類的導(dǎo)入
使用import語(yǔ)句導(dǎo)入包,就可以使用包中的類。
使用(*)導(dǎo)入包比較簡(jiǎn)單,不過(guò)如果能夠明確指出導(dǎo)入的類,就能顯而易見(jiàn)的知道加載了哪些類。
如果包下的類命名沖突,如java.util和java.sql都有Date類,使用時(shí)需要在類名前加上完整的包名。
import java.util.*;
import java.sql.*;
import java.util.Date;
java.util.Date deadline = new java.util.Date();
java.sql.Date today = new java.sql.Date(...);
4.7.2 靜態(tài)導(dǎo)入
import不僅可以導(dǎo)入包,還可以導(dǎo)入靜態(tài)方法和靜態(tài)域。
import static java.lang.System.*;
...
out.println("hi,world");
這種編寫形式不利于代碼的清晰度,不過(guò)用起來(lái)一定很不錯(cuò)。
4.7.3 將類放入包中
package語(yǔ)句將一個(gè)類放入包中,并且必須將包的名字放在源文件的開(kāi)頭。
如果開(kāi)頭沒(méi)有package語(yǔ)句,這個(gè)源文件就被放置在默認(rèn)包(default package)。
將包中的文件放到域完整的包名匹配的子目錄中,編譯器也將類文件放到相同的目錄結(jié)構(gòu)中。如果包和目錄不匹配,虛擬機(jī)就找不到類。
4.7.4 包作用域
沒(méi)有public和private修飾的部分,可以被同一個(gè)包中的所有方法訪問(wèn)。但是對(duì)于變量來(lái)說(shuō)不合適,會(huì)破壞封裝性。
視具體情況,包不是一個(gè)封閉的實(shí)體,可以修改。不過(guò)也可以通過(guò)包密封(package sealing)來(lái)防止再添加類。
4.8 類路徑
類儲(chǔ)存在文件系統(tǒng)的子目錄中,類的路徑必須與包名匹配。
為了使類能被多個(gè)程序共享,需要:
- 把類放到一個(gè)目錄中,這個(gè)目錄是包樹(shù)狀結(jié)構(gòu)的基目錄,例如c:\classdir。
- 將jar文件放到一個(gè)目錄中,例如c:\archives。
- 設(shè)置類路徑(class path)
- 類路徑包括
- 基目錄 c:\classdir;
- 當(dāng)前目錄(.);
- Jar文件 c:\archives\archive.jar
Windows 環(huán)境中,則以分號(hào)(;)分隔不同的項(xiàng)目
c:\classdir;.;c:\archives\archive.jar
4.8.1 設(shè)置類路徑
最好采用-classpath指定類路徑:
java -classpath c:\classdir;.;c:\archives\archive.jar MyProg
利用-classpath選項(xiàng)設(shè)置類路徑是首選的方法, 也可以通過(guò)設(shè)置 CLASSPATH 環(huán)境變量 完成這個(gè)操作
4.9 文檔注釋
運(yùn)行javadoc可以生成HTML文檔。
以專用的定界符 /**開(kāi)始的注釋,可以很容易地生成一個(gè)文檔,并且修改時(shí),重新javadoc可以同步。
4.9.1 注釋的插入
javadoc從下面幾個(gè)特性中抽取信息:
- 包
- 共有類和接口
- 共有的和受保護(hù)的構(gòu)造器及方法
- 共有的和受保護(hù)的域
每個(gè) /** . . . */ 文檔注釋在標(biāo)記之后緊跟著自由格式文本(free-form text)。標(biāo)記由@開(kāi)始, 如@author 或@param。
4.9.2 類注釋
類注釋放在import語(yǔ)句之后,類定義之前。
4.9.3 方法注釋
每一個(gè)方法注釋必須放所在方法之前,可以使用下面標(biāo)記:
- @param 變量描述:為當(dāng)前方法的參數(shù)添加描述
- @return 描述:可進(jìn)行多行描述
- @throws 類描述:此方法可能拋出異常
/**
* 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 = salary * byPercent / 100;
salary += raise;
return raise;
}
4.9.4 域注釋
只需要對(duì)公有域(通常指的是靜態(tài)常量)建立文檔。
4.9.5 通用注釋
下面的注釋可用在類文檔的注釋中:
- @author 姓名:作者
- @version 文本:對(duì)當(dāng)前版本的描述
- @since 文本:對(duì)引入特性的版本的描述
- @deprecate 文本:此標(biāo)記表示不再使用這個(gè)部分
- @see 引用:超鏈接
4.9.6 包和概述注釋
可以將各種注釋用/**. . . */文檔注釋界定。
但是,要生成單獨(dú)的文件需要:
- 提供一個(gè)以package.html命名的文件。<body>. . . </body>之間的文件會(huì)被抽取出來(lái)。
- 提供一個(gè)以package-info.java的文件。文件中包語(yǔ)句之后,緊跟/**. . . */注釋,不需要其他多余的注釋和代碼。
- 也可以為所以源文件提供一個(gè)概述性的注釋,寫在overview.html文件中,這個(gè)文件位于所有源文件的父目錄中。
4.9.7 注釋的抽取
假設(shè)HTML文件被放在docDirectory下。執(zhí)行步驟如下:
切換到想要生成文檔的源文件目錄。
-
運(yùn)行命令。
//如果是一個(gè)包 javadoc -d docDirectory nameOfPackage //多個(gè)包 javadoc -d docDirectory nameOfPackage1 nameOfPackage2. . . //默認(rèn)包 Javadoc -d docDirectory *.java
可以使用多種形式的命令對(duì)Javadoc程序進(jìn)行調(diào)整。
4.10 類設(shè)計(jì)技巧
類設(shè)計(jì)的一些簡(jiǎn)單的技巧:
- 保證數(shù)據(jù)的私有性。不要破壞封裝性。
- 初始化數(shù)據(jù),Java不會(huì)對(duì)局部變量初始化,可以提供默認(rèn)值。
- 不要在類中使用過(guò)多的基本類型。用類代替。
- 不是所以的域都需要獨(dú)立的域訪問(wèn)器和域更改器。
- 分解那些太多職責(zé)的類
- 類名和方法名要能夠體現(xiàn)它們的職責(zé) ,采用名詞是個(gè)好習(xí)慣。
- 優(yōu)先使用不可變的類。如LocalDate類沒(méi)有方法修改對(duì)象的狀態(tài)。不過(guò)也不是所有類都要是不可變的。
參考資料和鏈接:
請(qǐng)注意不要編寫返回可變對(duì)象引用的訪問(wèn)器方法_
Core Java Volume I--Fundamentals, 10th Edition
Java核心技術(shù) 卷1 基礎(chǔ)知識(shí) 原書(shū)第10版 中文版
