2013年9月10日 星期二

Access modifies


作者: 許裕永

學會類別開發的基礎技巧,便可以開發一個類別。但是,開發的類別要如何部署?類別或實體的成員,要如何保護才不致於接收不當的值,而影響程式的運算?
類別開發時,可以使用存取限制修飾詞,來限制類別、類別成員、實體成員及建構方法是否接受存取。

1. 類別存取的限制
1-1 存取類別(Asscees Class)
要限制類別的存取,就要先了解何謂存取類別?執行下列動作的敍述句,都是在存取A類別:
  1. 财 呼叫A類別的建構方法,建構物件。
  2. 财 以類別方稱A,呼叫類別方法成員或存取類別資料成員。
  3. 财 開發一個新類別,繼承A類別。
1-2 package宣告及檔案部署
package是部署類別的單位,也是一個置放class檔的資料夾,也是類別名稱的一部份。目前我們開發的類別,編譯成class檔之後,並沒有說一定 要置放於某一個資料夾。頂多為了管理方便,自己隨便建一個資料夾存放。要執行的時候,再把DOS視窗的工作目錄,切換到class檔的所在位置來執行。而 這樣做之所以行得通,是因為我們目前執行的檔案中,並沒有存取到其他不同資料夾的類別。如果我們現在要執行的class檔中,有存取置放於其他資料的類別 時,就必須把另外那些類別全部搬到目前執行檔的位置。累死了,而且實務上也不可能這樣做。
package的宣告,就是在開發類別的時候,直接指定本類別的部署位置,也就是置放本類別class檔的資料夾名稱,也就是要存取本類別時,必須加註在類別名稱前面的package名稱。
package名稱命名時,建議以小寫英文字母開頭。
例一:
package yung;
public class MyClass{
 public static void main(String[] args){
  System.out.println(new MyClass());
 }
}
本例存檔後按正常程序可以編譯出:MyClass‧class。但是要執行時,我們在命令列(DOS視窗)輸入:java MyClass。卻會得到下列訊息:
Exception in thread "main" java.lang.NoClassDefFoundError: MyClass
因為依照package的宣告,這個class檔,一定要置放於名字叫yung的資料夾之中。
請在目前的工作目錄中,新增一個名字叫yung的資料夾,然後把MyClass‧class檔置入其中,再輸入:java MyClass。結果還是得到下列訊息:
Exception in thread "main" java.lang.NoClassDefFoundErro: MyClass
因為現在MyClass這個類別的名字,已經不叫MyClass了。而是叫做yung‧MyClass。請在命令列輸入:java yung‧MyClass。
列印結果:yung.MyClass@35ce36
再複習一次:package是部署類別的單位,也是一個置放class檔的資料夾,也是類別名稱的一部份。還有,package的宣告一定要置於程式碼檔的第一行。
例二:
package com.yung;
public class MyClass{
 public static void main(String[] args){
  System.out.println(new MyClass());
 } 
}
編譯好的class檔,必須置於工作目錄底下的\com\yung的資料夾之中,執行時命令列必須輸入:java com‧yung‧MyClass。
列印結果:com.yung.MyClass@35ce36
示範主題:具有層次結構的package。
1-3 classpath
在上一節中,我們將package資料夾,置放於DOS視窗的工作目錄底下,但實務上是不會這麼做的。我們會把package資料夾部署在適當的位置,還要讓DOS視窗的工作目錄無論切換到那一個資料夾,都可以找到需要的package,這樣才是正確的做法。
例一:
請將上一節所製作的名字叫com的package,移動至磁碟機C:\底下。然後在命令列輸入:java MyClass com‧yung‧MyClass。將會得到下列訊息:
Exception in thread "main" java.lang.NoClassDefFoundError: com/yung/MyClass
很明顯,Java執行時,預設會在目前的工作目錄中,尋找class檔或package。現在找不到,自然無法執行。此時,我們就應該告訴它,我們的package在那裏。要怎麼告訴它呢?在java指令的後方加上參數“-classpath”或“-cp”。
語法:java -cp 路徑 類別名稱
-cp:是classpath的縮寫,要寫classpath也可以。
路徑:置放package的資料夾。相對路徑或絕對路徑均可。
類別名稱:完整的類別名稱。(包含package名稱)
請在命令列輸入:java –classpath c:/ com.yung.MyClass
java –cp c:/ com.yung.MyClass
列印結果:com.yung.MyClass@35ce36
示範主題:以絕對路徑設定classpath。
例二:請在目前的工作目錄下新增一個名字叫做abc的資料夾,並將上個範例名字叫個com的package移至abc資料夾之中。輸入:java -cp abc com‧yung‧MyClass。
列印結果:com.yung.MyClass@35ce36
示範主題:以相對路徑設定classpath。
classpath設定注意事項:
  1. 财 可以設定相對路徑或絕對路徑。
  2. 财 可以用‘;’區隔,設定一個以上的路徑。
  3. 财 路徑的層次符號為:‘/’。例:c:/abc/def。
  4. 财 設定的路徑,是置放package的資料夾,不要連package的名稱都設定進去。例如package的名稱是com,置放的資料夾是: “C:/Java/jdk/MyPackage”。那麼它的classpath便是:“C:/Java/jdk/MyPackage”;而不是: “C:/Java/jdk/MyPackage/com”。
1-4 import
在撰寫程式敍述句時,如果要存取另一個package的類別時,那麼程式碼中的類別名稱也必須包含package名稱。
例一:
public class UseMyClass{
 public static void main(String[] args){
  com.yung.MyClass object=new com.yung.MyClass();
  System.out.println(object);
 }
}
此列編譯時,會發生錯誤,錯誤訊息:
UseMyClass.java:3: package com.yung does not exist
因為編譯器不知你的package置放在那裏。(作者假設你的package置於工作目錄底下的資料夾abc)
請在編譯時也加上-cp或-classpath的參數。例:
javac -cp abc UseMyClass‧java
編譯完成後,輸入:java UseMyClass。
出現錯誤訊息:Exception in thread "main" java.lang.NoClassDefFoundError: com/yung/MyClass。因為java後面沒有設定classpath參數。
請輸入:java -cp abc UseMyClass。
出現錯誤訊息:Exception in thread "main" java.lang.NoClassDefFoundError: UseMyClass。 請注意:這次找不到的類別是UseMyClass。因為我們設定了classpath的參數後,Java只會到classpath指定的位置去尋找 class檔,所以即使UseMyClass是置放於工作目錄下的class檔,它也找不到。也就是說,我們設定的classpath必須也包含目前的工 作目錄。
請輸入:java -cp ‧;abc UseMyClass。
列印結果:com.yung.MyClass@d9f9c3
示範主題:1、編譯器(javac)及執行器(java)均需設定classpath參數。2、classpath可以用‘;’區隔,設定一個以上。3、‘‧’代表本工作目錄。(“‧‧/”代表上一層資料夾)
在DOS視窗中,為了省略執行或編譯時,都要輸入classpath的參數,可以使用DOS的指令來設定環境變數:
set classpath=path;path;path
例:set classpath=‧;abc
如此在本DOS視窗中編譯或執行Java程式時,便可以省略classpath的參數,但是若重新開啟DOS視窗,必須重新設定。
在程式碼中,要使用另一個package的類別時,必須輸入類別全名,也是一件很累人的事,所以Java提供了一個關鍵字“import”來方便引用另一個package的類別。
 
例二:
import com.yung.MyClass;
public class UseMyClass{
 public static void main(String[] args){
  MyClass object=new MyClass();
  System.out.println(object);
 }
}
編譯前先輸入:set classpath=.;abc
再輸入:javac UseMyClass.java
再輸入:java UseMyClass
列印結果:com.yung.MyClass@d9f9c3
示範主題:1、環境變數classpath的設定。2、用import宣告引用後,程式碼中撰寫該類別名稱時,便可以不必輸入package名稱。
在本章之前我們撰寫的所有程式碼中,我們存取String、Integer等類別時,均沒有引用java‧lang,是因為這個package預設會引 用,所以不必宣告。我們引用了很多的package卻都沒設定classpath參數,是因為這些package都是Java提供的,編譯器及執行器都預 設有它們的路徑,所以不必設定。
import宣告時注意事項:
  1. 财 本宣告句必須置於package宣告之後,class宣告之前。
    例:package abc;
    import com.yung.MyClass;
    public class UseMyClass{}
  2. 财 本宣告句可以引用一個class,也可以用*代表該package中的所有class。
    例:package abc;
    import com.yung.*;
    public class UseMyClass{}
    請注意:目前我們所做的範例中,yung裏只有一個class,若用上述的寫法,在部份系統中並無法執行。
  3. 财 ‘*’只能代表類別,不能代表子package。
    例:package abc;
    import com.*;
    public class UseMyClass{}
    這種寫法代表引用com中的所有class,但不包含yung中的class。
1-5 public
在類別宣告時,類別名稱前面可以使用存取限制詞public,來設定本類別是否允許不同package的類別來存取。如果有宣告public,表示允許不同package的類別來存取本類別,如果沒有宣告,則表示本類別只允許同一個package的類別存取。
例一:
package com.yung;
class MyClass{
 public static void main(String[] args){
  System.out.println(new MyClass());
 }
}
將class MyClass前面的public取消。重新編譯後再部署至資料夾yung之中。
輸入:javac UseMyClass‧java編譯UseMyClass時,出現下列錯誤訊息:
UseMyClass.java:4: cannot access MyClass
因為UseMyClass不屬於com‧yung這個package,所以無法存取沒有宣告為public的MyClass。
 
例二:
package com.yung;
public class UseMyClass{
 public static void main(String[] args){
  MyClass object=new MyClass();
  System.out.println(object);
 }
}
將UseMyClass也宣告package為com‧yung。重新編譯後,完成後將class檔部置至com/yung之中。
輸入:java com‧yung‧UseMyClass
列印結果:com.yung.MyClass@757aef
public宣告注意事項:
  • 财 class的存取限制詞,只有一個public。差別是有宣告和沒有宣告。請不要在類別名稱前面宣告其他的存取限制詞,那就錯誤了。(內部類別的宣告除外,後續章節介紹)
  • 财 一個程式碼檔中(java檔),可以開發一個以上的class,但是只能有一個是public,而且檔案名稱必須和該宣告為public的類別名稱相同。
1-6 import static
如果我們要使用的只是某一個類別的類別成員,可以用static imports的機制,只引入指定類別中的類別成員。
請注意:「import是以類別為引入單位,import static是以類別中的類別成員為引入單位」。
語法:import static packageNameclassNamememberName;

 import static packageNameclassName*;
例一:
package com.yung;
import static java.lang.System.out;
import static java.lang.Math.*;
public class UseMyClass{
 public static void main(String[] args){
  out.println(max(8,10));
 }
}
列印結果:10。
示範主題:1、指定引用類別成員out(也可以是方法成員)。2、指定引用類別中的所有類別成員。
 
最後,如果我們現在要開發一個大型專案,class要如何部署,如何引用。若沒有妥善的規劃,上百個class檔案,也是真夠頭疼的。下圖是專案部署的資料夾結構建議:
└ ProjectName
└ src
└ packageName
└ subPackageName1
└ subPackageName2
└ subPackageName3
└ docs
└ classes
└ packageName
└ subPackageName1
└ subPackageName2
└ subPackageName3
以專案名稱為最上層資料夾,底下建立src、docs及classes三個子資料夾。其中src及classes中再建立相同名稱的子資料夾,分別置放各個package及subPackage的程式碼檔(java檔)與class檔。docs則置放說明文件。
2. 成員存取的限制
2-1 成員存取
類別中所宣告的成員,只有兩種,資料成員及方法成員(建構方法不算成員)。而所謂成員存取有下列三種:1、用類別名稱存取類別資料成員或用類別名稱呼叫類 別方法成員。2、用物件存取實體資料成員或呼叫實體方法成員(物件也可以存取或呼叫類別成員)。3、在子類別中,存取父類別的資料成員或呼叫父類別的方法 成員。
特別要注意的是:本節所謂的成員存取限制。限制的是上述的三種存取模式。指的都是類別開發完成之後,對類別使用者的限制。而不是在類別開發的時候,對類別設計者的限制。也就是說:「在開發類別的時候,類別中所有的成員,彼此的呼叫或存取,是完全沒有限制的」。
2-2 private
宣告為private的成員,不接受類別使用者用任何模式呼叫。只允許類別開發者,在撰寫類別中的程式碼時,呼叫或存取。
例一:
public class AccessModify{
 private int a;
 public void setA(int a){
  this.a=a;
 }
 public int getA(){
  return a;
 }
 public static void main(String[] args){
  AccessModify object=new AccessModify();
  object.setA(8);
  System.out.println(object.getA());
 }
}
請各位務必記得:把main當做是另一個class中的程式碼,也就是在寫main時,是以類別使用者的角度來撰寫程式碼。類別中的這個main是在類別開發過程中,類別開發者用來測試這個類別的使用結果用的。
列印結果:8。
示範主題:將資料成員宣告為private,使用者便只能透過宣告為public的兩個方法成員,才可以存取資料成員a的值,可以保護資料成員。
讓我們回頭想想上一章的一個範例:開發一個學生類別。這範例的程式碼之中,我們宣告了數個public的資料成員。並針對每個資料成員,設計了設定及取得 其值的方法。其實那樣設計是錯誤的,因為那些資料成員既然宣告為public表示可以讓物件存取(例:object‧age=18),那又何必設計 setAge(int age)的方法呢?
再囉嗦一次,類別開發者不見得是類別使用者。各位現在以Student這個類別的開發者的角度來思考:會不會有一個白目的(或粗心的)類別使用者,寫出這 樣的程式碼:“object‧age=1000;”。如果他給了這個值,而造成程式執行時期,發生類別開發者預期外的錯誤,這要怪誰呢?
為了不讓類別使用者,有機會給資料成員錯誤的值,最好的方式就是把資料成員隱藏起來,再另外設計存取該資料成員的方法。於是類別使用者只能透過那兩個public的方法來存取該資料成員。而類別開發者便可以在那兩個public的方法中限制或過濾要設定給資料成員的值。
 
例二:
public class AccessModify{
 private int a;
 public boolean setA(int a){
  if(a>0 && a<=180){
   this.a=a;
   return true;
  }else{
   this.a=18;
   return false;
  }
 }
 public int getA(){
  return a;
 }
 public static void main(String[] args){
  AccessModify object=new AccessModify();
  if(object.setA(800)){
   System.out.println("設定成功,a=" + object.getA());
  }else{
   System.out.println("設定失敗,a=" + object.getA());
  }
 }
}
列印結果:設定失敗,a=18。
示範主題:在設定資料成員的方法中,建立過濾機制,保護資料成員。並提供布林值型別的運算結果,來通知類別使用者設定成功或失敗。
把重要的資料成員宣告為private,讓外界無法直接存取,甚至不知道也不必知道它的存在,這叫做「資訊隱藏」。就像我們也不必去知道String和 StringBuffer類別之中,到底有那一些資料成員,它們的運算模式又有什麼不一樣。我們只要學會,如何使用這些類別提供的public的成員,來 協助我們運算就可以了。
站在類別開發者的角度,現在你開發的類別也一樣,不必讓使用者知道所有你設計的成員,只要公開那些接受使用者存取或呼叫的成員就夠了。這樣的行為叫做「封裝」(Encapsulation)。
在Java中對於要讓使用者使用的類別,會建議使用嚴密的封裝(tight encapsulation)。就是把所有的資料成員宣告為private,再針對必要的資料成員設計public的方法,來讓使用者設定或取得該值。(類別常數成員除外)
但是如果不是讓使用者使用的類別,而是要自己用的呢(不建構成物件的類別)?在我們開發專案時,要開發的類別自然不只一個,而且部份的類別會有相互依存的關係(Coupling)。
 
2-3 default
請注意:default不是一個用來宣告的關鍵字。它是指,如果成員宣告句前方,並沒有放置任何存取修飾詞的時候。而這個時候預設的存取限制為:package。
例:
package abc;
public class Test{
 int a;
 void seta(){}
 int getA(){retrun a;}
}
class中的三個成員的存取限制,均宣告為package。
宣告為package存取限制的成員,在同一個package的其他class中,可以任意的使用成員存取的三種模式來存取。但是,只要不是同一個package的class,就完全不接受存取(即使是子類別)。
2-4 protected
protected的宣告,對同一個package的類別而言,其限制和default是相同的,也就是說:在同一個package的其他class中,可以任意使用成員存取的三種模式來存取。protected的宣告是針對不同package的class才有效用。
基本上,宣告為protected的成員,是允許其他package的子類別繼承後,在子類別中存取。但是不允許建構成物件後,用物件存取。也就是只允許成員存取的三種模式中的第三種。
例:
package abc;
public class Test{
 protected int a;
}
package def;
import abc.Test;
public class Test2 extends Test{
 void doSomeThing(){
  a=8;
 }
 void doSomeThing2(){
  Test t=new Test();
  t.a=8;
 }
}
例中Test2中的doSomeThing直接以類別內部存取的方式存取該成員是合法的。但是doSomeThing2中,以物件呼叫成員的方式存取該成員,雖然是在子類別之中,還是不合法。
最後再提醒一次:以上的限制,專指兩個類別不在同一個package。若在同一個package,即使不是子類別,也可以用物件存取。總而言之,protected的宣告,對屬於同一個package的class,完全沒有意義。
另外,不要忘記,要存取不同package的類別,必須該類別宣告為public,而且要import。
2-5 public
宣告為public的成員,完全沒有限制,不管是否在同一個package,均可以任意使用三種存取模式存取。
但是和protected一樣要注意:若是要存取不同package的class,必須先注意該類別是否宣告為public,而且要先import。
到底什麼性質的類別應該嚴密的封裝,什麼性質的類別應該寬鬆?在大家有實務經驗之前自然很難掌握。基本上可以先用類別是否要建構成物件來做區隔。現在開發 的類別,如果要被建構成物件(無論建構該物件的人是不是你自己),均採嚴密的封裝。若開發的類別(界面),並沒有要建構成物件,而是要被繼承(實作),那 就使用寬鬆的封裝。
至於成員限制的區隔,就用要不要接受物件存取(呼叫)來區隔。如果要,就設為public,若不要,就設為private或protected。
若是開發大型專案,就可以好好利用不宣告。不宣告就是宣告為package,就等於對同一個pacakge的class完全沒有限制,而對其他 package的class卻又完全限制。而大型專案會開發多個package,每個package中都會有要公開給其他package存取的 class;也會有不公開給其他package存取的class。設計師只要注意宣告為public的class中的成員的宣告;其他非public的 class中的成員,以不宣告的方式處理,可以增加開發的彈性。
 

3. 建構方法的存取限制
建構方法不是實體成員也不是物件成員,所以獨立寫成一節,但只有兩個重點:
1、建構方法可以宣告任何存取限制修飾詞。(即使是private)
2、預設的建構方法其存取限制和類別一樣。
特別注意:設計師為類別開發了一個建構方法,而這個建構方法前面沒有加存取限制修飾詞,那麼這個建構方法的存取限制是:「package」。不是和類別一 樣,和類別一樣的是預設的建構方法,是設計師沒有開發建構方法時,由編譯器提供的那一個預設的建構方法。不要誤解為建構方法預設的存取限制和類別一樣,那 錯大了。
 
4. 認證重點整理
4-1 類別存取的限制
  • 财 執行下列動作,均是存取某一個類別:1、呼叫建構方法,建構物件。2、以類別方稱呼叫類別方法成員或存取類別資料成員。3、繼承某一個類別。
  • 财 package是部署類別的單位,也是一個置放class檔的資料夾,也是類別名稱的一部份。
  • 财 package的宣告,就是在開發類別的時候,直接指定本類別的部署位置,也就是置放本類別class檔的資料夾名稱,也就是要存取本類別時,必須加註在類別名稱前面的package名稱。
  • 财 package名稱命名時,建議以小寫英文字母開頭。
  • 财 package的宣告一定要置於程式碼檔的第一行。
  • 财 classpath設定注意事項:1、可以設定相對路徑或絕對路徑。2、可以用‘;’區隔,設定一個以上的路徑。3、路徑的層次符號為:‘/’。4、設定的路徑,是置放package的資料夾,不要連package的名稱都設定進去。
  • 财 import宣告時注意事項:1、本宣告句必須置於package宣告之後,class宣告之前。2、本宣告句可以引用一個class,也可以用*代表該package中的所有class。3、
    ‘*’只能代表類別,不能代表子package。
  • 财 在類別宣告時,類別名稱前可以用存取限制詞public,來設定本類別是否允許不同package的類別存取。
  • 财 public宣告注意事項:1、class的存取限制詞,只有一個public。差別是有宣告和沒有宣告。請不要在類別名稱前面宣告其他的存取限制詞。 2、一個程式碼檔中(java檔),可以開發一個以上的class,但是只能有一個是public,而且檔案名稱必須和該宣告為public的類別名稱相 同。
财 用static imports的機制,只引入指定類別中的類別成員。
4-2 成員存取的限制
  • 财 類別中所宣告的成員,只有兩種,資料成員及方法成員。
  • 财 成員存取有下列三種:1、用類別名稱存取類別資料成員或用類別名稱呼叫類別方法成員。2、用物件存取實體資料成員或呼叫實體方法成員(物件也可以存取或呼叫類別成員)。3、在子類別中,存取父類別的資料成員或呼叫父類別的方法成員。
  • 财 在開發類別的時候,類別中所有的成員,彼此的呼叫或存取,是完全沒有限制的。
  • 财 宣告為private的成員,不接受類別使用者用任何模式呼叫。只允許類別開發者,在撰寫類別中的程式碼時,呼叫或存取。
  • 财 把重要的資料成員宣告為private,讓外界無法直接存取,甚至不知道也不必知道它的存在,這叫做「資訊隱藏」。
  • 财 不必讓使用者知道所有你設計的成員,只要公開那些接受使用者存取或呼叫的成員。這樣的行為叫做「封裝」(Encapsulation)。
  • 财 父類別中設計寬鬆,各個子類別,便能依各自需求來設計從父類別繼承過來的成員,如此可以增加設計上的彈性,專業詞叫「降低依存性」(loosely coupled)。
  • default不是一個用來宣告的關鍵字。它是指,如果成員宣告句前方,並沒有放置任何存取修飾詞的時候。這個時候預設的存取限制為:package。
  • 财 宣告為package存取限制的成員,在同一個package的其他class中,可以任意的使用成員存取的三種模式來存取。但是,只要不是同一個package的class,就完全不接受存取(即使是子類別)。
  • 财 protected的宣告,對同一個package的類別而言,其限制和default是相同的。
  • 财 宣告為protected的成員,是允許其他package的子類別繼承後,在子類別中存取。但是不允許建構成物件後,用物件存取。
  • 财 protected的宣告是針對不同package的class才有效用。
  • 财 在子類別中,以物件呼叫成員的方式存取宣告為protected的成員,是不合法的。
  • 财 要存取不同package的類別,必須該類別宣告為public,而且要import。
  • 财 宣告為public的成員,完全沒有限制,不管是否在同一個package,均可以任意使用三種存取模式存取。
4-3 建構方法的存取限制
  • 财 建構方法可以宣告任何存取限制修飾詞。(即使是private)
  • 财 預設的建構方法其存取限制和類別一樣。

沒有留言:

張貼留言