物件導向
文章推薦指數: 80 %
類別-封裝### 定義類別在Java物件導向中,物件並不會憑空出現,必須先使用class關鍵字定義類別,類別就相當於物件的規格書或是設計藍圖,在使用該類別來產生一個個的 ...
Published
LinkedwithGitHub
Like
Bookmark
Subscribe
Edit
#物件導向
######tags:`java`
##目錄
-[類別-封裝](#類別-封裝)
-[類別-繼承](#類別-繼承)
-[類別-多型](#類別-多型)
-[抽象-abstract](#抽象-abstract)
-[介面-interface](#多型-interface)
##前言
物件導向是一種對程式開發上的思考方式,與一般程式語言的的開發方式有所不同,Java是一種支援物件導向的語言,並不代表寫Java程式就是在使用物件導向,這是不一樣的兩件事情。
物件導向是一種思考方式,與程式語言無關,要完全理解並不容易,光物件導向這門技術,可能就可以寫上整本書來說明。
Java作為一門支援物件導向的,幾乎完整的支援物件導向開發發方式的所有需求。
##類別-封裝
###定義類別
在Java物件導向中,物件並不會憑空出現,必須先使用class關鍵字定義類別,類別就相當於物件的規格書或是設計藍圖,在使用該類別來產生一個個的物件,再透過該物件提供的方法來操作物件。
其語法為:
```
publicclass類別名稱{
類別成員
}
```
例如:
```
publicclassCar{
...
}
classSchool{
...
}
classBank{
...
}
```
Car是類別的名稱,由於這個類別前面使用了「public」關鍵字來修飾,檔案的主檔名必須與類別名稱相同,也就是檔案要取名為「Car.java」,這是Java的規定,在一個檔案中可以定義多個類別,但只能有一個類別被設定為「public」,且檔案名稱主檔名必須與這個public的類別相同名稱。
###類別成員
物件裡主要會有兩個種成員:
1.資料(Field)。
2.方法(Method)。
語法為:
```
publicclassCar{
存取修飾資料型態資料成員名稱;
...
存取修飾回傳型態方法成員名稱(參數列){
陳述句;
...
return回傳值;
}
}
```
####資料成員
在定義資料成員時可以指定初值,如果沒有指定初值,則會有預設值,資料成員如果是基本型態,則預設值與陣列介紹時的預設直一樣,如果是物件型態,則預設值為null,也就是不參考任何的物件。
存取權限有「public」、「protected」和「private」三種關鍵字:
1.「public」關鍵字表示所定義的成員可以使用宣告的物件名稱加上點「.」運算子來直接呼叫,稱為「公用成員」或「公開成員」,所有外部的物件都可以存取該成員。
2.「private」這個關鍵字用來定義一個「私用成員」,私用成員不可以透過參考名稱加上點「.」直接呼叫,稱為「私有成員」,私有成員只有在類別內部才能存取。
例如:
```
publicclassCar{
publicintprice;
protectedStringmodelName;
privatefloatgas;
}
```
一個類別中的資料成員,若宣告為"private",則其可視範圍(Scope)為整個類別內部,由於外界無法直接存取私用成員,所以您要使用兩個公開方法getAccountNumber()與getBalance()分別傳回其這兩個成員的值。
####方法成員
一個方法被宣告為「public」,表示該方法可以藉由物件的參考名稱加上「.」被直接呼叫,一個方法成員為一小個程式片段,方法可重複被呼叫使用,並可傳入參數或回傳執行結果。
參數列用來傳入方法成員執行時所需的資料,==如果傳入的參數是基本資料型態(Primitivedatatype),則該資料為原本資料的複製,如果傳入的是物件,則該參數存放的原物件的參考==。
方法區塊內宣告的變數(Variable)在方法區塊執行結束後就會被自動清除,==如果方法中宣告的變數名稱與類別資料成員的名稱同名,則方法中的變數名稱會暫時覆蓋資料成員的作用範圍;參數列上的參數名稱也會覆蓋資料成員的作用範圍==,如果要在方法區塊中操作資料成員,可以透過「this」關鍵字來指定。
>**補充**
>
>1.在定義類別時,有一個基本原則是:==將資料最小化公開,並盡量以方法來操作資料==,也就是說不開放外面直接存取物件內部的資料成員(也就是Field成員),這個原則是基於安全性的考量,避免程式設計人員隨意操作內部資料成員而造成臭蟲。
>2.如果在宣告成員時不使用存取修飾詞,則預設將以「套件」(package)為存取範圍,也就是在package外就無法存取。
```
publicclassCar{
publicintprice;
protectedStringmodelName;
privatefloatgas;
publicvoidsetModelName(StringmodelName){
this.modelName=modelName;
}
publicStringgetModelName(){
returnmodelName;
}
privatevoidsetPrice(intprice){
this.price=price;
}
}
```
方法的傳回值可以將計算的結果或其它想要的數值、物件傳回,傳回值與傳回值型態的宣告必須一致,在方法中如果執行到"return"陳述,則會立即終止區塊的執行;如果方法執行結束後不需要傳回值,則可以撰寫"void",且無需使用"return"關鍵字。
在物件導向程式設計的過程中,有一個基本的原則,如果資料成員能不公開就不公開,在Java中若不想公開成員的資訊,方式就是宣告成員為"private",這是「資訊的最小化」,此時在程式中要存取"private"成員,就要經由setXXX()與getXXX()等公開方法來進行設定或存取,而不是直接存取資料成員。
透過公開方法存取私用成員的好處之一是,如果存取私用成員的流程有所更動,只要在公開方法中修改就可以了,對於呼叫方法的應用程式不受影響,例如您的Car類別中,gas()如果為50已經滿的時後,再呼叫addGas()就會提醒油已經滿了,無法繼續加油:
```
finalstaticintMAX_GAS=50;
publicbooladdGas(intgas){
if(this.gas>=MAX_GAS){
returnfalse;
}elseif(this.gas+gas>=MAX_GAS)
this.gas=GAS_MAX;
}else{
this.gas+=gas;
}
returntrue;
}
```
這麼一來,setGas()對gas做了些檢查,但對於addGas()呼叫者來說,則不用做修改,也不用理會細節。
>**提示**
>
>1.習慣上,方法名稱的命名慣例為第一個字母小寫,名稱以了解該方法的作用為原則,之後每個單字的第一個字母為大寫,例如showMeTheMoney()。
>
>2.為資料成員設定setXXX()或getXXX()存取方法時,XXX名稱最好與資料成員名稱相對應,例如命名modelName這個資料成員對應的方法時,可以命名為setModelName()與getModelName(),這樣可以在閱讀程式碼上更清楚其作用。
>**補充**
>
>參數與引數的差別:
>
>在定義方法時,可以定義「參數列」:
>
>```
>publicvoidsetXXX(intsomething){//something為參數
>//...
>}
>```
>
>呼叫方法時傳遞的數值或物件稱為「引數」:
>
>```
>someObject.setXXX(99);//99是引數
>```
###物件
定義好類別之後,就可根據這個類別來建立物件,也就是產生類別的實體,建立物件為透過使用「new」關鍵字來達成。
```
Carcar1=newCar();
Carcar2=newCar();
```
在上面的程式中宣告了car1與car2兩個Car型態的參考名稱,並讓它們分別參考至物件。
要透過公開成員來操作物件或取得物件資訊的話,可以在物件名稱後加上「.」運算子來進行,例如:
```
car1.price;
car2.setModelName("BMWM3");
```
###建構方法
與類別名稱同名的方法稱之為「建構方法」(Constructor),也有人稱之為「建構子」,它沒有傳回值,建構方法的作用是在建構物件的同時,可以初始做一些必要的初始化,建構方法可以有多個,也就是可以被「重載」(Overload),用來滿足物件建立時的各種不同初始化需求,例如:
```
classCar{
publicintprice;
protectedStringmodelName;
privatefloatgas;
publicCar(){
price=-1;
modelName="未知的型號";
gas=0;
}
publicvoidsetModelName(StringmodelName){
this.modelName=modelName;
}
publicStringgetModelName(){
returnmodelName;
}
privatevoidsetPrice(intprice){
this.price=price;
}
}
```
**解說**
在這裡有兩個建構方法,一個是:
```
publicCar(){
price=-1;
modelName="未知的型號";
gas=0;
}
```
用來將物件成員price初始化為-1,modelName初始化為「未知的型號」,gas初始化為0,而這個方法不需要呼叫,也無法直接呼叫,它會在`new`出該物件的同時自動被呼叫,也是物件第一個被執行的方法。
###this
this關鍵字用來明確指定物件本身的成員,在每一個方法成員內都會隱含該名稱;例如有時方法成員內可能會出現跟資料成員同名的變數或參數,這時如果要操作資料成員,就必須使用this來明確指定,例如:
```
classCar{
publicintprice;
protectedStringmodelName;
privatefloatgas;
publicCar(intprice,StringmodelName,intgas){
this.price=price;
this.modelName=modelName;
this.gas=gas;
}
publicvoidsetModelName(StringmodelName){
this.modelName=modelName;
}
publicStringgetModelName(){
returnmodelName;
}
privatevoidsetPrice(intprice){
this.price=price;
}
}
```
**解說**
在建構方法Car內有三個參數:price、modelName和gas都剛好跟資料成員名稱重複,所以當要使用時,必須明確指定才能正確操作,例如:`this.price=price`代表將參數price的值指派的資料成員price。
而在方法成員getModelName()內因為沒有同名的參數或變數,所以即使沒有使用this關鍵字,也會代表資料成員的modelName。
>**補充**
>
>當變數名稱重複時,程式執行會有內向外找有沒有符合該變數名稱的宣告,例如:區域變數->資料成員,如果找完一輪都沒有符合名稱的變數或成員,就會出現編譯錯誤。
###靜態-static
同一個類別每次透過new所產生的物件,其資料成員都是各自獨立且不會互相影響的,在某些時候,會需要所有物件可以擁有共享的資料成員,這時就可以透過static關鍵字來宣告一個資料成員,A物件如果修改了該static資料成員,B物件的該static成員內容也會跟著改變,因其實他們所擁有的參考都是指向同一筆資料:
被宣告為static的資料成員,稱為「靜態資料成員」,靜態成員是屬於類別所擁有,而不是個別的物件。
要宣告靜態資料成員,只要在宣告資料成員時加上static關鍵字即可,例如:
```
classCar{
publicstaticStringbrand="BMW";//宣告static資料成員
...
}
```
靜態成員屬於類別所擁有,可以不用建立物件直接使用類別名稱加上點「.」運算子就可以存取靜態資料成員,不過靜態資料成員仍然會受到「public」、「protected」與「private」關鍵字的存取權限來規範,例如:
```
classCar{
publicstaticStringbrand="BMW";//宣告static資料成員
privatestaticStringengine="V12";//私有靜態資料成員
}
publicclassApp{
publicstaticvoidmain(String[]args){
System.out.println(Car.brand);
System.out.println(Car.engine);//錯誤,無法存取靜態資料成員
}
}
```
>**補充**
>
>雖然也可以在宣告物件之後,透過物件名稱加上點「.」運算子來存取靜態資料成員,但該方式較不建議,通常建議使用類別名稱加上點「.」運算子來存取,可以在閱讀程式碼時比較清楚知道該成員是靜態或非靜態成員,例如下面存取靜態成員方式不建議使用:
>```
>CarmyCar=newCar();
>System.out.println(myCar.brand);
>```
除了靜態資料成員方法成員也可以宣告為靜態,一樣使用static關鍵字,該方法成員稱為「靜態方法」,被宣告為靜態的方法通常用來作為「工具方法」,不需要再建立物件的情況下就可以直接透過類別名稱來呼叫:
```
classCar{
privatestaticStringbrand="BMW";
publicstaticvoidshowBrand(){
System.out.println(brand);
}
}
publicclassApp{
publicstaticvoidmain(String[]args){
Car.showBrand();
}
}
```
與靜態資料成員一樣,可以直接透過類別名稱使用點「.」運算子來呼叫static方法,相同的也有public、private和protected的權限問題,例如:
```
classCar{
privatestaticStringbrand="BMW";
privatestaticStringcolor="白色";
publicstaticvoidshowBrand(){
System.out.println(brand);
}
privatestaticvoidshowColor(){
System.out.println(color);
}
}
publicclassApp{
publicstaticvoidmain(String[]args){
Car.showBrand();
Car.showColor();//錯誤:ThemethodshowColor()fromthetypeCarisnotvisible
}
}
```
>**補充**
>
>靜態資料與靜態方法的作用通常是為了提供共享的資料或工具方法,例如將數學常用的Math類別:Math.random(),或是Integer.parseInt()其實就是靜態方法。
#####靜態方法無法存取物件成員,沒有this參考名稱可以用
由於靜態成員是屬於類別而不是物件,所以在呼叫靜態方法時,並不會傳入物件的參考,所以靜態方法中不會有this參考名稱,由於沒有this名稱,所以無法存取到物件的資料成員和方法成員。
如果在靜態方法中使用非靜態資料成員,在編譯時就會出現下面錯誤訊息:
```
non-staticvariabletestcannotbereferencedfromastaticcontext
```
或者是在靜態方法中呼叫非靜態方法,編譯時會出現下面錯誤訊息:
```
non-staticmethodshowHello()cannotbereferencedfromastaticcontext
```
也就是說規則為:
1.靜態方法只能呼叫靜態方法。
2.靜態方法只能操作靜態資料。
3.物件方法可以呼叫物件方法和靜態方法。
4.物件方法可以操作物件資料和靜態資料。
#####static區塊
在類別內可以定義一種叫static的區塊,區塊內可以有陳述句,用來做一些類別相關的初始化,語法為:
```
publicclass類別名稱{
static{
//一些初始化程式碼
}
....
}
```
在類別被載入時,預設會先執行靜態區塊中的程式碼,而且整個程式的生命週期只會執行一次,例如:
```
classCar{
publicCar(){
System.out.println("Car()");
}
}
publicclassApp{
static{
System.out.println("staticblock");
}
publicApp(){
System.out.println("app()");
}
publicstaticvoidmain(String[]args){
newApp();
newApp();
}
}
```
執行結果為:
```
staticblock
app()
app()
```
會發現即使建立了多個App實例,static區塊只有被執行一次,且會==比建構方法還要早被執行==。
#####靜態區塊、非靜態區塊、資料成員初始化和建構方法呼叫順序
看下面例子:
```
classCar{
publicCar(){
System.out.println("Car()建構方法");
}
}
publicclassApp{
static{
System.out.println("staticblock");
}
{
System.out.println("non-staticblock");
}
publicApp(){
System.out.println("app()建構方法");
}
Carcar=newCar();
publicstaticvoidmain(String[]args){
newApp();
newApp();
}
}
```
執行結果為:
```
staticblock
non-staticblock
Car()建構方法
app()建構方法
non-staticblock
Car()建構方法
app()建構方法
```
當靜態區塊、非靜態區塊、資料成員初始化和建構方法同時存在時,其執行順序為:
1.靜態區塊
2.非靜態區塊
3.資料成員初始化
4.建構方法
###方法多載-Overload
方法「重載」(Overload),也可稱為「超載」或「過載」,其主要是讓類別提供多個相同名稱,但是有不同參數的方法,可以提供在不同情況下使用同一種方法的機制,例如String類別的valueOf()方法提供了多個版本:
```
staticStringvalueOf(booleanb)
staticStringvalueOf(charc)
staticStringvalueOf(char[]data)
staticStringvalueOf(char[]data,intoffset,intcount)
staticStringvalueOf(doubled)
staticStringvalueOf(floatf)
staticStringvalueOf(inti)
staticStringvalueOf(longl)
staticStringvalueOf(Objectobj)
```
雖然呼叫的方法名稱都是valueOf(),但是根據所傳遞的引數資料型態不同,就會呼叫對應版本的方法來進行對應的動作,例如:
1.`String.valueOf(99)`會呼叫`valueOf(inti)`的版本。
2.`String.valueOf(1.2)`,因為1.2是double型態,所以會呼叫的是`valueOf(doubled)`的版本。
除了型態的不同之外,,參數列的參數個數也可以用來作為方法重載,例如:
```
publicclassMyClass{
publicvoidmethod(){
//...
}
publicvoidmethod(inti){
//...
}
publicvoidmethod(inti,floatf){
//...
}
publicvoidmethod(inti,intj){
//...
}
}
```
**注意**:回傳值型態即使不相同,也無法作為方法重載的根據,例如:
```
publicclassMyClass{
publicintmethod(inti){
//...
return0;
}
publicfloatmethod(inti){
//...
return0.0F;
}
}
```
上面範例會出現編譯錯誤,因為對編譯器來說,這兩個是一樣的方法,錯誤訊息為:
```
Duplicatemethodmethod(int)intypeApp
```
###boxing問題
```
publicclassApp{
publicstaticvoidmain(String[]args){
someMethod(1);
}
publicstaticvoidmethod(inti){
System.out.println("int版本被呼叫");
}
publicstaticvoidmethod(Integerinteger){
System.out.println("Integer版本被呼叫");
}
}
```
結果為:
```
int版本被呼叫
```
在這個例子下,裝箱(boxing)的動作並不會發生,如果想要呼叫Integer版本的方法,需要明確指定,例如:
```
method(newInteger(1));
```
###不定長度參數
在呼叫某個方法時,如果需要的引數數量無法固定,例如System.out.printf()方法中並沒有辦法事先知道引數長度,例如:
```
System.out.printf("%d",10);
System.out.printf("%d,%d,%d",10,20,30);
System.out.printf("%d,%d,%d,%d,%d",10,20,30,40,50);
```
要使用不定長度引數,在宣告參數列時要於型態後面加上「...」三個點,然後該參數在使用時,實際上會是一個陣列,可以以陣列的方式來操作它,例如實際上編譯器會將參數列的(String...args)解釋為(String[]args),這樣就可以只訂一一個方法來接收各種不同的長度的引數給方法使用,例如:
```
classMyTool{
publicstaticintsum(int...nums){//使用...宣告參數
intsum=0;
for(intnum:nums){
sum+=num;
}
returnsum;
}
}
publicclassApp{
publicstaticvoidmain(String[]args){
inttotal=0;
total=MyTool.sum(1,2);
System.out.println("1+2="+total);
total=MyTool.sum(1,2,3);
System.out.println("1+2+3="+total);
total=MyTool.sum(1,2,3,4,5);
System.out.println("1+2+3+4+5="+total);
}
}
```
執行結果:
```
1+2=3
1+2+3=6
1+2+3+4+5=15
```
>**補充**
>
>編譯器會將不定長度引數轉成陣列,又稱為:「編譯糖」(CompilerSugar)。
當使用不定長度參數時,如果還有其它固定參數,則不定長度參數必須放在最後一個,例如:
```
publicvoidmethod(intarg1,intarg2,int...args){
//....
}
```
下面為不合法的定義方式:
```
publicvoidmethod(int...varargs,intarg1,intarg2){
//....
}
```
不定長度參數只能有一個,例如下面方式不合法:
```
publicvoid,ethod(int...args1,int...args2){
//....
}
```
>**補充**
>
>編譯器在處理重載方法、裝箱和不定長度引數,會依下面的順序來尋找符合的方法:
>
>-未裝箱、符合引數個數與型態的方法。
>-裝箱、符合引數個數與型態的方法。
>-嘗試「不定長度引數」可以符合的方法。
>-找不到合適的方法,編譯錯誤。
>
###垃圾回收
使用new配置的物件,基本上當必須清除以回收物件所佔據的記憶體空間,Java語言有提供垃圾收集機制,在「適當」的時候,執行環境會自動檢查物件,如果有未被參考的物件,就會被清除並回收物件所佔據的記憶體空間。
Java中垃圾收集的時機何時開始無法知道,可能在記憶體資源不足的時候、可能在程式執行的空閒時候,也可以主動透過程式建議執行環境進行垃圾收集,但也只是建議,垃圾回收機制並不一定會馬上進行。
在物件中有個finalize()這個方法,它被宣告為"protected",finalize()會在物件被回收時執行,但不可以當成一般物件結束時打算執行某些程式碼的解構方法來使用,因為資源回收的時間是不可控制的,所以並不是物件一不使用就會被呼叫,但仍然可以使用finalize()來進行不需要及時的一些相關資源的清除動作。
如果確定不再使用某個物件,可以直接將該物件參考設定為「null」,例如:`car=null`,表示這個名稱不再參考至任何物件,不被任何名稱參考的物件就會被回收資源,透過呼叫`System.gc()`方法就可以建議垃圾回收進行,如果建議被採納,則回收機制就會被觸發,回收前會呼叫物件的finalize()方法,例如:
```
classGCTest{
privateStringname;
publicGCTest(Stringname){
this.name=name;
System.out.println(name+"建立");
}
//物件回收前執行
protectedvoidfinalize(){
System.out.println(name+"被回收");
}
}
publicclassApp{
publicstaticvoidmain(String[]args){
GCTestobj1=newGCTest("object1");
GCTestobj2=newGCTest("object2");
GCTestobj3=newGCTest("object3");
//名稱不參考至物件
obj1=null;
obj2=null;
obj3=null;
//建議回收物件
System.gc();
}
}
```
執行結果:
```
object1建立
object2建立
object3建立
object1被回收
object3被回收
object2被回收
```
可以看到finalize()方法被垃圾回收機制自動呼叫了。
##類別-繼承
類別可以基於某個類別(該類別可以稱之為父類別)對來加以擴充,擴充後誕生的新的類別稱為子類別,子類別可以繼承父類別原來的某些功能,並增加原來的父類別所沒有的功能,或者是將父類別原有的功能重新定義。
事實上,在Java中,所有的類別是繼承自java.lang.Object類別。
###擴充-extends
有時候,想要擴充原始類別已經定義好的功能,但不想去修改原始類別的程式碼,或是根本沒有原始類別的原始碼,可以使用extends關鍵字來保留原始類別以機定義好的功能,然後再加上新功能來成為一個新的類別,此時原始類別為新類別的父類別,相對來說,新的類別則是原始類別的子類別。
而extends類別這件事,在物件導向裡就是所謂的「繼承」(Inherit)。
以車子來說,不同的車子可能會有其他類型車子沒有的功能,但他們都可以前進,例如:
```
classCar{
privateStringbrand;
publicCar(){
}
publicvoidmove(intmiles){
System.out.println("Move"+miles+"miles.");
}
publicvoidsetBrand(Stringbrand){
this.brand=brand;
}
privatevoidshow(){
System.out.println("Thisisaprivatemethod.")
}
}
classSportCarextendsCar{
privatebooleanisTurboMode=false;
publicvoidturbo(){
this.isTurboMode=!this.isTurboMode;
System.out.println("Turbomode:"+this.isTurboMode);
}
}
publicclassApp{
publicstaticvoidmain(String[]args){
SportCarsportCar=newSportCar();
sportCar.move(10);
sportCar.turbo();
sportCar.turbo();
}
}
```
**解說**
SportCar繼承自Car,所以雖然沒有定義move()方法,但其實在富類別中有定義了,所以也可以呼叫該方法,當擴充某個類別時,該類別的所有public成員都可以在衍生類別中被呼叫使用,而private成員則不可以直接在衍生類別中被呼叫使用(例如show()方法)。
####super()-指定建構式
```
classCar{
privateStringbrand;
publicCar(){
System.out.println("建構方法一號");
this.brand="無品牌";
}
publicCar(Stringbrand){
System.out.println("建構方法2號");
this.brand=brand;
}
publicvoidmove(intmiles){
System.out.println("Move"+miles+"miles.");
}
publicvoidsetBrand(Stringbrand){
this.brand=brand;
}
}
classSportCarextendsCar{
privatebooleanisTurboMode=false;
publicSportCar(){
super("無品牌跑車");
}
publicvoidturbo(){
this.isTurboMode=!this.isTurboMode;
System.out.println("Turbomode:"+this.isTurboMode);
}
}
publicclassApp{
publicstaticvoidmain(String[]args){
SportCarsportCar=newSportCar();
sportCar.move(10);
sportCar.turbo();
sportCar.turbo();
}
}
```
執行結果:
```
建構方法2號
Move10miles.
Turbomode:true
Turbomode:false
```
在擴充某個類別之後,子類別建構方法被呼叫前會先呼叫傅類別的建構方法,預設會呼叫無參數的父類別建構方法,但透過super()方法,透過參數,可以指定要呼叫建構方法版本,且==super()必須在建構方法一開始就呼叫==。
父類別的public成員可以直接在衍生類別中使用,而private成員則不行,private成員只限於定義它的類別之內來存取,如果真的想存取父類別的private成員,只能透過父類別中繼承下來的public方法成員內去間接呼叫,例如夠過setBrand()方法去存取private的brand成員。
###受保護的-protected
資料成員如果設為private成員,也就是私用成員,就只能在物件內部使用,不能直接透過參考名稱加上點「.」運算子來使用,即使是擴充了該類別的衍生類別也無法直接使用父類別的私有成員,只能透過父類別提供的「public」方法成員來呼叫或設定私用成員。
但有些時候,會希望子類別也可以取用父類別的的成員,但又不想要公開給其它套件使用,這時就可以使用protected(受保護的)關鍵字來修飾成員,保護的意思表示存取該成員是有條件限制的,當類別的成員宣告為受保護的成員之後,繼承的類別就可以直接使用這些成員,但這些成員受到保護,所以如果是不同套件的(package)的類別就無法使用。
**demo/Shape.java**
```
publicclassShape{
//受保護的成員
protectedintx;
protectedinty;
protectedintwidth;
protectedintheight;
publicShape(){
}
publicShape(intx,inty,intwidth,intheight){
this.x=x;
this.y=y;
this.width=width;
this.height=height;
}
publicvoidsetX(intx){this.x=x;}
publicvoidsetY(inty){this.y=y;}
publicvoidsetWidth(intwidth){this.width=width;}
publicvoidsetHeight(intheight){this.height=height;}
publicintgetX(){returnx;}
publicintgetY(){returny;}
publicintgetWidth(){returnwidth;}
publicintgetHeight(){returnheight;}
}
classRectangleextendsShape{
publicRectangle(){
super();
}
publicRectangle(intx,inty,intz,intwidth,intheight){
super(x,y,width,height);
}
publicintgetPositionX(){returnx;}
publicintgetPositionY(){returny;}
}
```
**App.java**
```
publicclassApp{
publicstaticmain(String[]args){
Shapeshape=newShape();
shape.x=0;//錯誤,無法存取pretected成員
shape.y=0;//錯誤,無法存取pretected成員
}
}
```
Shape類別的x、y、width、height成員因為被宣告成protected,所以在子類別Rectangle類別內可以直接使用,但是在不同套件的App類別內操作x和y成員將會出現編譯錯誤:
```
ThefieldShape.xisnotvisible
```
同理,方法成員也可以宣告為受保護的成員。
父類別中想要讓子類別擁有的資料成員會宣告為protected,父類別中想要子類別也可以使用的方法成員也會宣告為protected,這些方法對不同套件(package)的類別來說,可能是呼叫它並沒有意義或是有某些風險,所以就會宣告成protected。
###重載-Override
類別是物件的定義,如果父類別中的定義不符合需求,需要做調整個話,可以在擴充類別的同時重新定義,可以重新定義:
-方法的實作內容
-成員的存取權限
-成員的返回值型態
例如:
```
publicclassCar{
protectedintgas;
publicCar(){
}
publicvoidsetGas(intgas){
this.gas=gas;
}
....
}
```
Car類別有一個可以用來設定汽油量的方法setGas(),但setGas()方法不夠安全,因為汽油量不可能是負的,所以做了一個汽油量的檢查,如果汽油量小於0,就不會進行設定:
```
publicclassSportCarextendsCar{
publicSportCar(){
}
//重新定義setGas()
publicvoidsetGas(intgas){
if(gas<0)
return;
this.gas=gas;
}
....
}
```
這麼一來,以SportCar類別的定義所產生的物件,就可以使用新的定義方法,就SportCar類別來說,由於操作介面與Car是一致的,所以可以這麼使用:
```
Carcar=newSportCar();
car.setGas();
```
SportCar與Car擁有一致的操作介面,因為SportCar是Car型態的子類,擁有從父類中繼承下來的setGas()操作介面,雖然使用了Car型態的介面來操作SportCar實例,但由於實際運作的物件是SafeArray的實例,所以被呼叫執行的會是SafeArray中重新定義過的setGas()方法,這個就叫「多型」(Polymorphism)。
如果想在衍生類別中呼叫基底類別的建構方法,可以使用super()方法;如果要在衍生類別中呼叫父類別方法,則可以使用`super.methodName()`的方式來達成,但使用super()呼叫父類別建構方法或使用super.methodName()呼叫父類別中方法一樣需要遵從權限設定,也就是該方法不能是private(私有成員)。
>**注意**
>
>重新定義方法可以加大父類別中的方法權限,但不可以縮小父類別的方法權限,例如:原來成員是public的話,不可以在父類別中重新定義它為private或protected,所以在擴充Car時,就不可以這樣:
```
publicclassSportCarextendsCar{
publicSportCar(){
}
//重新定義setGas()
protectedvoidsetGas(intgas){
if(gas<0)
return;
this.gas=gas;
}
....
}
```
嘗試將setGas()方法從public權限縮小成private會在編譯時得到下面錯誤訊息:
```
setGas(int)inSportCarcannotoverridesetGas(int)inCar;attemptingtoassignweakeraccessprivileges;waspublic
privatevoidsetGas(inti){
^
1error
```
也可以重新定義返回值的型態,其條件就是,原本的回傳值必須是==原本回傳值的子類別==,例如有個父類別為:
```
publicclassCar{
protectedintgas;
publicCar(){
}
publicvoidsetGas(intgas){
this.gas=gas;
}
publicCargetClone(){
returnnewCar();
}
....
}
```
getClone()方法原本回傳的會是Car物件,現在打算Car類別衍生了一個SportCar類別,然後重新定了getClone()方法,並且回傳值改為SportCar物件,由於回傳值是原本型態的子類別,有繼承關係,所以可以這樣重新定義回傳值:
```
publicclassSportCarextendsCar{
publicSportCar(){
}
//重新定義setGas()
protectedvoidsetGas(intgas){
if(gas<0)
return;
this.gas=gas;
}
publicSportCargetClone(){
returnnewSportCar();
}
....
}
```
>**注意**
>
>static方法無法被重新定義,一個方法要被重新定義,必須是非static的,如果在子類別中定義一個相同名稱和參數的static成員,那其實不是重新定義,而是定義一個屬於該子類別的static成員,兩者互不相干。
###Object類別
在Java中只要使用class關鍵字定義類別,就已經開始使用繼承的機制了,因為在Java中所有的物件都是擴充自java.lang.Object類別,Object類別是Java程式中所有類別的父類別,每個類別都直接或間接繼承自Object類別,例如當定義一個類別時:
```
publicclassFoo{
//實作
}
```
在Java中定義類別時如果沒有指定要繼承的類別,會自動繼承自Object類別,上面的類別定義其實是下面的樣子:
```
publicclassFooextendsObject{
//實作
}
```
由於Object類別是Java中所有類別的父類別,所以使用Object宣告的名稱,可以參考至任何的物件而不會發生任何錯誤,因為每一個物件都是Object的子物件,例如在使用容器時,必須設定容器可以存放的類別物件:
```
importjava.util.ArrayList;
classCar{
publicCar(){}
}
publicclassApp{
publicstaticvoidmain(String[]args){
ArrayList
延伸文章資訊
- 1定義類別(Class) | Java SE 6 技術手冊 - caterpillar
以物件導向的思維來思考一個問題的解答時,會將與問題相關的種種元素視作一個個的物件,問題的發生是由物件的交互所產生,而問題的解答也由某些物件彼此合作來完成。所以 ...
- 2JAVA 物件導向程式設計
1.2 開始學習JAVA 前的準備. 1.3 第一個JAVA 程式. 1.4 資料型別. 1.5 運算子. 1.6 迴圈. 1.7 條件判斷. 1.8 使用者輸入. Ch2. 學習JAVA 的第...
- 3物件導向
類別-封裝### 定義類別在Java物件導向中,物件並不會憑空出現,必須先使用class關鍵字定義類別,類別就相當於物件的規格書或是設計藍圖,在使用該類別來產生一個個的 ...
- 4我要學會Java (二):物件導向其實很簡單 - Noob's Space
在物件導向的世界裡,很多東西都是由一個一個物件構成的。舉凡一些感覺很「具體」的東西都可以是物件,例如房子、Xperia Z5、杯子、橡皮擦、哈士奇、Noob…
- 5Java 學習筆記[12] 物件與類別 - iT 邦幫忙
Java 學習筆記系列第12 篇 ... 這就是物件導向程式設計(Object Oriented Progamming,簡單OOP), ... 類別(Class)**是物件的模子,可以用模子產生...