java 静态方法_80后程序员,教你学Java核心技术:用户自定义类+静态域静态方法
用戶自定義類(lèi)
在第3章中,已經(jīng)開(kāi)始編寫(xiě)了一些簡(jiǎn)單的類(lèi)。但是,那些類(lèi)都只有一個(gè)簡(jiǎn)單的main方法?,F(xiàn)在讓我們開(kāi)始學(xué)習(xí)如何設(shè)計(jì)復(fù)雜應(yīng)用程序所需要的各種“主力類(lèi)”(workhorse class)。通常,這些類(lèi)沒(méi)有main方法,而有自定義的實(shí)例域和實(shí)例方法。要想創(chuàng)建一個(gè)完整的程序,應(yīng)該將若干類(lèi)組合在一起,其中一個(gè)類(lèi)有main方法。
一個(gè)Employee類(lèi)
在Java中,最簡(jiǎn)單的類(lèi)定義形式為:
下面看一個(gè)非常簡(jiǎn)單的Employee類(lèi)。在編寫(xiě)薪金管理系統(tǒng)時(shí)可能會(huì)用到。
這里將這個(gè)類(lèi)的實(shí)現(xiàn)細(xì)節(jié)分成以下幾個(gè)部分,并分別在稍后的幾節(jié)中給予介紹。下面先看看例4-2,它展示了一個(gè)使用Employee類(lèi)的程序代碼。
在這個(gè)程序中,構(gòu)造了一個(gè)Employee數(shù)組,并填入了三個(gè)雇員對(duì)象:
接下來(lái),使用雇員類(lèi)的raiseSalary方法將每個(gè)雇員的薪水提高5%:
最后,調(diào)用getName方法、getSalary方法和getHireDay方法打印每個(gè)雇員的信息:
注意,在這個(gè)例子程序中包含兩個(gè)類(lèi):一個(gè)Employee類(lèi);一個(gè)帶有public訪問(wèn)修飾符的EmployeeTest類(lèi)。EmployeeTest類(lèi)包含了main方法,其中使用了前面介紹的指令。
源文件名是EmployeeTest.java,這是因?yàn)槲募仨毰cpublic類(lèi)的名字相匹配。在一個(gè)源文件中,只能有一個(gè)公有類(lèi),但可以有任意數(shù)目的非公有類(lèi)。
接下來(lái),當(dāng)編譯這段源代碼的時(shí)候,編譯器將在目錄下創(chuàng)建兩個(gè)類(lèi)文件:EmployeeTest.class和Employee.class。
將程序中包含main方法的類(lèi)名字提供給字節(jié)碼解釋器,以便啟動(dòng)這個(gè)程序:
java EmployeeTest
字節(jié)碼解釋器開(kāi)始運(yùn)行EmployeeTest類(lèi)的main方法中的代碼。在這段代碼中,先后構(gòu)造了三個(gè)新Employee對(duì)象,并顯示它們的狀態(tài)。
例4-2 EmployeeTest.java
多個(gè)源文件的使用
在例4-2中,一個(gè)源文件包含了兩個(gè)類(lèi)。許多程序員習(xí)慣于將每一個(gè)類(lèi)存入一個(gè)單獨(dú)的源文件中。例如,將Employee類(lèi)存放在文件Employee.java中,將EmployeeTest類(lèi)存放在文件EmployeeTest.java中。
如果喜歡這樣組織文件,將有兩種編譯源程序的方法。一種是使用通配符調(diào)用Java編譯器:
javac Employee*.java
于是,所有與通配符匹配的源文件都將被編譯成類(lèi)文件。或者,僅鍵入下列命令:
javac EmployeeTest.java
可能會(huì)感到驚訝,使用第二種方式,并沒(méi)有顯式地編譯Employee.java。然而,當(dāng)Java編譯器發(fā)現(xiàn)看到EmployeeTest.java中使用了Employee類(lèi)時(shí),就會(huì)查找名為Employee.class的文件。如果沒(méi)有找到這個(gè)文件,就會(huì)自動(dòng)地搜索Employee.java,然后,對(duì)它進(jìn)行編譯。更重要的是:如果Employee.java版本較已有的Employee.class文件版本新,Java編譯器就會(huì)自動(dòng)地重新編譯這個(gè)文件。
解析Employee類(lèi)
下面對(duì)Employee類(lèi)進(jìn)行一下剖析。首先從這個(gè)類(lèi)的方法開(kāi)始。通過(guò)查看源代碼會(huì)發(fā)現(xiàn),這個(gè)類(lèi)有一個(gè)構(gòu)造器和4個(gè)方法:
public Employee(String n, double s, int year, int month, int day)
public String getName( )
public double getSalary( )
public Date getHireDay( )
public void raiseSalary(double byPercent)
這個(gè)類(lèi)的所有方法都被標(biāo)記為public。關(guān)鍵字public意味著任何類(lèi)的任何方法都可以調(diào)用這個(gè)方法。(共有4種訪問(wèn)級(jí)別,我們將在本章和下一章中加以介紹。)
接下來(lái),需要注意在Employee類(lèi)的實(shí)例中有三個(gè)實(shí)例域用來(lái)存放將要操作的數(shù)據(jù)。
private String name;
private double salary;
private Date hireDay;
關(guān)鍵字private確保只有Employee類(lèi)自身的方法能夠訪問(wèn)這些實(shí)例域,而其他類(lèi)的方法不能夠讀寫(xiě)這些域。
最后,請(qǐng)注意,有兩個(gè)實(shí)例域本身就是對(duì)象:name域是String類(lèi)對(duì)象,hireDay域是Date類(lèi)對(duì)象。這種情形十分常見(jiàn):類(lèi)通常包括類(lèi)類(lèi)型的實(shí)例域。
從構(gòu)造器開(kāi)始
下面先看看Employee類(lèi)的構(gòu)造器。
已經(jīng)看到,構(gòu)造器與類(lèi)同名。在構(gòu)造Employee類(lèi)對(duì)象時(shí),構(gòu)造器被運(yùn)行,并用于將實(shí)例域初
始化為所希望的狀態(tài)。
例如,當(dāng)使用下面這條代碼創(chuàng)建Employee類(lèi)實(shí)例時(shí):
new Employee("James Bond", 100000, 1950, 1, 1);
將會(huì)把實(shí)例域設(shè)置為:
name = "James Bond";
salary = 100000;
hireDay = January 1, 1950;
構(gòu)造器與其他的方法有一個(gè)重要的不同。構(gòu)造器總是伴隨著new操作符的執(zhí)行被調(diào)用,而不
能對(duì)一個(gè)已經(jīng)存在的對(duì)象調(diào)用構(gòu)造器來(lái)重新設(shè)置實(shí)例域。例如,
james.Employee("James Bond", 250000, 1950, 1, 1); // ERROR
將產(chǎn)生編譯錯(cuò)誤。
本章稍后,還會(huì)更加詳細(xì)地介紹有關(guān)構(gòu)造器的內(nèi)容。現(xiàn)在只需要記住:
? 構(gòu)造器與類(lèi)同名。
? 每個(gè)類(lèi)可以有一個(gè)以上的構(gòu)造器。
? 構(gòu)造器可以有0個(gè)、1個(gè)或1個(gè)以上的參數(shù)。
? 構(gòu)造器沒(méi)有返回值。
? 構(gòu)造器總是伴隨著new操作符一同使用。
隱式參數(shù)與顯式參數(shù)
方法用于操作對(duì)象以及存取它們的實(shí)例域。例如,方法
將調(diào)用這個(gè)方法的對(duì)象的salary實(shí)例域設(shè)置為新值??纯聪旅孢@個(gè)調(diào)用
它的結(jié)果將number007.salary域的值增加了5%。具體地說(shuō),這個(gè)調(diào)用執(zhí)行了下述指令:
raiseSalary方法有兩個(gè)參數(shù)。第一個(gè)參數(shù)被稱(chēng)為隱式參數(shù),是出現(xiàn)在方法名前的Employee類(lèi)對(duì)象。第二個(gè)參數(shù)位于方法名后面括號(hào)中的數(shù)值,這是一個(gè)顯式參數(shù)。
已經(jīng)看到,顯式參數(shù)是明顯地列在方法聲明中的顯示參數(shù),例如double byPercent。隱式參數(shù)沒(méi)有出現(xiàn)在方法聲明中的參數(shù)。
在每一個(gè)方法中,關(guān)鍵字this表示隱式參數(shù)。如果需要的話,可以編寫(xiě)raiseSalary方法如下:
有些程序員更偏愛(ài)這樣的風(fēng)格,因?yàn)樗鼘?shí)例域與局部變量明顯地區(qū)分開(kāi)來(lái)。
封裝的優(yōu)點(diǎn)
最后,再仔細(xì)地看一下非常簡(jiǎn)單的getName方法、getSalary方法和getHireDay方法。
這些都是典型的訪問(wèn)器方法。由于它們只返回實(shí)例域值,因此又被稱(chēng)為域訪問(wèn)器。
將name、salary和hireDay域標(biāo)記為public,以此來(lái)取代獨(dú)立的訪問(wèn)器方法會(huì)不會(huì)更容易些呢?
關(guān)鍵在于name是一個(gè)只讀域。一旦在構(gòu)造器中設(shè)置完畢,就沒(méi)有任何一個(gè)辦法可以對(duì)它進(jìn)行修改,這樣來(lái)確保name域不會(huì)受到外界的干擾。
雖然salary不是只讀域,但是它只能用raiseSalary方法修改。特別是一旦這個(gè)域值出現(xiàn)了錯(cuò)誤,只要調(diào)試這個(gè)方法就可以了。如果salary域是public的,那么破壞這個(gè)域值的搗亂者可能會(huì)出沒(méi)在任何地方。
在有些時(shí)候,需要獲取或設(shè)置實(shí)例域的值。因此,應(yīng)該提供下面三項(xiàng)內(nèi)容:
? 一個(gè)私有的數(shù)據(jù)域。
? 一個(gè)公有的域訪問(wèn)器方法。
? 一個(gè)公有的域更改器方法。
這樣做要比提供一個(gè)簡(jiǎn)單的公有數(shù)據(jù)域復(fù)雜些,但是卻有著下列明顯的好處:
1)可以改變內(nèi)部實(shí)現(xiàn),除了該類(lèi)的方法之外,不會(huì)影響其他代碼。
例如,如果將存儲(chǔ)名字的域改為:
String firstName;
String lastName;
那么getName方法可以改為返回
firstName + " " + lastName
對(duì)于這點(diǎn)改變,程序的其他部分完全不可見(jiàn)。
當(dāng)然,為了進(jìn)行新舊數(shù)據(jù)表示之間的轉(zhuǎn)換,訪問(wèn)器方法和更改器方法有可能需要做許多工
作。但是,這將為我們帶來(lái)了第二點(diǎn)好處。
2)更改器方法可以執(zhí)行錯(cuò)誤檢查,然而直接對(duì)域進(jìn)行賦值將不會(huì)做這些處理。
例如,setSalary方法可能會(huì)檢查薪金是否小于0。
基于類(lèi)的訪問(wèn)權(quán)限
從前面已經(jīng)知道,方法可以訪問(wèn)所調(diào)用對(duì)象的私有數(shù)據(jù)。一個(gè)方法可以訪問(wèn)所屬類(lèi)的所有對(duì)象的私有數(shù)據(jù),這令很多人感到奇怪!例如,讓我們考察一下用來(lái)比較兩個(gè)雇員的equals方法。
一個(gè)典型的調(diào)用是
if (harry.equals(boss)) . . .
這個(gè)方法訪問(wèn)harry的私有域,這沒(méi)什么奇怪的。然而,它還訪問(wèn)boss的私有域。這是合法的,因?yàn)閎oss是Employee類(lèi)對(duì)象,并且Employee類(lèi)的方法可以訪問(wèn)Employee類(lèi)的任何一個(gè)對(duì)象的私有域。
私有方法
當(dāng)實(shí)現(xiàn)一個(gè)類(lèi)的時(shí)候,由于公有數(shù)據(jù)非常危險(xiǎn),所以我們將所有的數(shù)據(jù)域都設(shè)置為私有的。
然而,方法又應(yīng)該如何設(shè)計(jì)呢?盡管絕大多數(shù)方法都設(shè)計(jì)為公有的,但在特殊情況下,也可能會(huì)設(shè)計(jì)為私有的。在有些時(shí)候,可能希望將一個(gè)計(jì)算代碼劃分成若干個(gè)獨(dú)立輔助的方法。通常,這些輔助方法不應(yīng)該成為公有接口的一部分,這是由于它們往往與當(dāng)前的實(shí)現(xiàn)機(jī)制非常緊密,或者需要一個(gè)特別的協(xié)議以及一個(gè)特別的調(diào)用次序。這樣的方法最好被設(shè)計(jì)為private的。
在Java中,為了實(shí)現(xiàn)一個(gè)私有的方法,只需要將關(guān)鍵字public改為private即可。
對(duì)于私有方法,如果改用其他方法實(shí)現(xiàn)相應(yīng)的操作,則不必保留原有的方法。如果數(shù)據(jù)的表達(dá)方式發(fā)生了變化,那么該方法可能會(huì)因此變得難以實(shí)現(xiàn),或者不再需要。然而,只要方法是私有的,類(lèi)的設(shè)計(jì)者就可以確信:它不會(huì)被外部的其他類(lèi)操作調(diào)用,可以將其刪去。如果方法是公有的,就不能將其刪去,因?yàn)槠渌拇a很可能調(diào)用它。
final實(shí)例域
可以將實(shí)例域定義為final。構(gòu)建對(duì)象時(shí)必須初始化這樣的域。也就是說(shuō),必須確保在每一個(gè)構(gòu)造器執(zhí)行之后,這個(gè)域的值被設(shè)置,并且在后面的操作中,不能夠再對(duì)它進(jìn)行修改。例如,可以將Employee類(lèi)中的name域聲明為final,因?yàn)樵趯?duì)象構(gòu)建之后不會(huì)再被修改,即沒(méi)有setName方法。
final修飾符大都應(yīng)用于基本數(shù)據(jù)(primitive)類(lèi)型域,或不可變(immutable)類(lèi)的域。(如果類(lèi)中的每個(gè)方法都不會(huì)改變其對(duì)象,這種類(lèi)就是不可變的類(lèi)。例如,String類(lèi)就是一個(gè)不可變的類(lèi)。)對(duì)于可變的類(lèi),使用final修飾符可能會(huì)對(duì)讀者造成混亂。例如,
僅僅意味著存儲(chǔ)在hiredate變量中的對(duì)象引用在對(duì)象構(gòu)造之后不能被改變。而并不意味著hiredate對(duì)象是一個(gè)常量。任何方法都可以對(duì)hiredate引用的對(duì)象調(diào)用setTime更改器。
靜態(tài)域與靜態(tài)方法
在前面給出的例子程序中,main方法都被標(biāo)記上static修飾符?,F(xiàn)在,我們來(lái)討論一下這個(gè)修飾符的含義。
靜態(tài)域
如果將域定義為static,那么每個(gè)類(lèi)中只有一個(gè)這樣的域。而每一個(gè)對(duì)象對(duì)于所有的實(shí)例域卻都有自己的一份拷貝。例如,假定需要給每一個(gè)雇員賦予唯一的標(biāo)識(shí)碼。這里給Employee類(lèi)添加一個(gè)實(shí)例域id和一個(gè)靜態(tài)域nextId:
現(xiàn)在,每一個(gè)雇員對(duì)象都有一個(gè)自己的id域,但這個(gè)類(lèi)的所有實(shí)例將共享一個(gè)nextId域。換句話說(shuō),如果有1000個(gè)Employee類(lèi)的對(duì)象,則有1000個(gè)實(shí)例域id。但是,只有一個(gè)靜態(tài)域nextId。即使沒(méi)有一個(gè)雇員對(duì)象,靜態(tài)域nextId也存在。它屬于類(lèi),而不屬于任何獨(dú)立的對(duì)象。
下面來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的方法:
假定為harry設(shè)定雇員標(biāo)識(shí)碼:
那么harry的id域被設(shè)置,并且靜態(tài)域nextId的值加1:
常量
靜態(tài)變量使用得比較少,但靜態(tài)常量卻使用得比較多。例如,在Math類(lèi)中定義了一個(gè)靜態(tài)常量:
在程序中,可以通過(guò)Math.PI來(lái)訪問(wèn)這個(gè)常量。
如果關(guān)鍵字static被省略,PI就成了Math類(lèi)的一個(gè)實(shí)例域。即需要用Math類(lèi)的對(duì)象來(lái)訪問(wèn)PI,并且每一個(gè)Math對(duì)象都有它自己的一份PI拷貝。
已經(jīng)使用多次的另一個(gè)靜態(tài)常量是System.out。它在System類(lèi)中聲明:
前面曾經(jīng)提到過(guò),由于每個(gè)類(lèi)對(duì)象都可以對(duì)公有域進(jìn)行修改,所以將域設(shè)計(jì)為public并不是一種好的想法。然而,公有常量(即final域)卻沒(méi)問(wèn)題。因?yàn)閛ut被聲明為final,所以,不允許再將其他打印流賦給它:
System.out = new PrintStream(. . .); // ERROR--out is final
靜態(tài)方法
靜態(tài)方法是不能向?qū)ο髮?shí)施操作的方法。例如,Math類(lèi)的pow方法就是一個(gè)靜態(tài)方法。表達(dá)式Math.pow(x, a)
計(jì)算冪xa 。它在運(yùn)算的時(shí)候,不使用任何Math對(duì)象。換句話說(shuō),沒(méi)有隱式的參數(shù)。
可以認(rèn)為靜態(tài)方法是沒(méi)有this參數(shù)的方法。(在一個(gè)非靜態(tài)的方法中,this參數(shù)表示該方法的隱式參數(shù)。)
因?yàn)殪o態(tài)方法不能操作對(duì)象,所以不能在靜態(tài)方法中訪問(wèn)實(shí)例域。但是,靜態(tài)方法可以訪問(wèn)自身類(lèi)中的靜態(tài)域。下面是這種靜態(tài)方法的一個(gè)例子:
public static int getNextId( ){return nextId; // returns static field}可以通過(guò)類(lèi)名調(diào)用這個(gè)方法:
int n = Employee.getNextId( );
這個(gè)方法可以省略關(guān)鍵字static嗎?答案是肯定的。但是,需要通過(guò)Employee類(lèi)對(duì)象的引用調(diào)用這個(gè)方法。
Factory方法
靜態(tài)方法還有一種常見(jiàn)的用途。NumberFormat類(lèi)使用Factory方法產(chǎn)生不同風(fēng)格的格式對(duì)象。
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance( );NumberFormat percentFormatter = NumberFormat.getPercentInstance( );double x = 0.1;System.out.println(currencyFormatter.format(x)); // prints $0.10System.out.println(percentFormatter.format(x)); // prints 10%為什么NumberFormat類(lèi)不利用構(gòu)造器來(lái)完成這些操作呢?這主要有兩個(gè)原因:
? 無(wú)法命名構(gòu)造器。構(gòu)造器的名字必須與類(lèi)名相同。但是,這里希望將得到的貨幣實(shí)例和百分比實(shí)例采用不用的名字。
? 當(dāng)使用構(gòu)造器時(shí),無(wú)法改變所構(gòu)造的對(duì)象類(lèi)型。而Factory方法將返回一個(gè)DecimalFormat類(lèi)對(duì)象,這是NumberFormat的子類(lèi)。(有關(guān)繼承的詳細(xì)內(nèi)容請(qǐng)參閱第5章。)
main方法
需要注意,不必使用對(duì)象調(diào)用靜態(tài)方法。例如,不需要構(gòu)造Math類(lèi)對(duì)象就可以調(diào)用 Math.pow。
同理,main方法也是一個(gè)靜態(tài)方法。
main方法不對(duì)任何對(duì)象進(jìn)行操作。事實(shí)上,在啟動(dòng)程序的時(shí)候還沒(méi)有任何一個(gè)對(duì)象。靜態(tài)的main方法將執(zhí)行并創(chuàng)建程序所需要的對(duì)象。
例4-3中的程序包含了Employee類(lèi)的一個(gè)簡(jiǎn)單版本,其中包含一個(gè)靜態(tài)域nextId和一個(gè)靜態(tài)方法getNextId。這里用三個(gè)Employee對(duì)象填充數(shù)組,然后打印雇員信息。最后,打印出下一個(gè)可用的員工標(biāo)識(shí)碼來(lái)作為對(duì)靜態(tài)方法使用的演示。
需要注意,Employee類(lèi)也有一個(gè)靜態(tài)的main方法用于單元測(cè)試。試試運(yùn)行java Employee和java StaticTest來(lái)執(zhí)行兩個(gè)main方法。
例4-3 StaticTest.java
覺(jué)得文章不錯(cuò)的話,可以轉(zhuǎn)發(fā)此文關(guān)注小編,之后持續(xù)更新干貨文章~~~
總結(jié)
以上是生活随笔為你收集整理的java 静态方法_80后程序员,教你学Java核心技术:用户自定义类+静态域静态方法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: mysql分页概念_MySQL学习笔记之
- 下一篇: linux显示mem进行排序,Linux