面嚮物件語言基礎二(1) (轉)

amyz發表於2007-08-14
面嚮物件語言基礎二(1) (轉)[@more@]

面向語言基礎二(1:namespace prefix = o ns = "urn:schemas--com::office" />

 

摘要:

Jeff Friesen在他的面嚮物件語言基礎系列的第二部分中對欄位和方法進行了探索。在本文中,你將理解欄位,引數,區域性變數以及學會怎樣定義和訪問欄位及方法。

 

名詞術語,提示和告戒,作業以及此專欄的其他資訊,參看相關的“學習指南”。(6400字)

 

俗話說:一葉障目、不見森林。這句話指對總體瞭解的過分詳細的。那麼在介紹類和物件時,還有什麼比過細的研究欄位和方法更能讓人迷惑呢?這就是為什麼在本系列第一部分中,我詳細的介紹總體:類和物件,而略帶的介紹欄位和方法的原因。然而,正如你最終還是要進入森林並並將注意力放在樹上一樣,這個月的專欄將非常詳細的講述欄位和方法。

整個面嚮物件語言基礎系列:

的多重繼承

 

 

Java的變數可以分為三類:欄位,引數以及區域性變數。我將先介紹欄位,在稍後討論方法時介紹引數和區域性變數。

 

定義欄位

  欄位是定義在類中的變數,其用來儲存一個物件的狀態(例項欄位)或者類狀態(類欄位)。使用如下的Java語法來定義一個變數:

  [(‘public’|’private’|’protected’)]

  [(‘final’|volatile’)]

  [‘static’][‘transient’]

  data_type field_name [ ‘=’ expression] ‘;’

  一個欄位的定義規定了欄位的名字、資料型別、(初始化欄位值)、存取限定以及修改標識等。選取一個不是保留字的識別符號作為欄位的資料型別和名字,例如:

  class Employee

  {

  String name;  //員工名

  double salary;  //薪水

  int jobID;  //工作表識號(如:會計,考勤員,經理等等)

  }

  Employee類定義了三個欄位:name,salary,以及jobID。當建立一個Employee物件時,name最終保持一個String物件的引用以儲存員工的名字。salary欄位將儲存員工的薪水,而jobID則儲存了一個整形變數以標識員工的工種。

 

存取限定符

  你可以任意地使用以下三個存取限定關鍵字來定義一個欄位:public,private或者protected。存取限定符確定了其他類中的程式碼對這個欄位的存取。存取範圍從完全訪問到完全不可存取。

 

如果你在定義變數時沒有使用存取限定符,Java將賦給這個欄位一個預設的存取級別,從本身類中的程式碼到同一個包中的所有類都能訪問該欄位。(將包想象成一個類庫——在以後的文章中我將講述這個概念。)任何不在同一個包中定義的類都無法存取該欄位,例如:

  class MyClass

  {

  int fred;

  }

只有MyClass類和定義在MyClass包中的其他類的程式碼才能存取fred。

 

如果你將一個欄位定義為private,則只有他自己的類中的程式碼能存取這個欄位。任何其他類,無論在哪個包中的,都不能存取該欄位。如:

class Employee

{

  private double salary;

  }

只有Employee類才能存取salary;

 

  如果你將一個欄位定義為public,則不僅他自己的類中的程式碼,而且所有其他包中的類也都能存取該欄位,如:

  public class Employee

  {

  public String name;

  }

Employee類中的程式碼和其他所有包中的類的程式碼都能存取name(Employee必須定義為public才能使其他包中的類能存取name)。

  將所有欄位定義為public會違反資料的封裝原則。試想你建立了一個Body類來模仿人的身體,Eye,Heart以及Lung類來模仿眼睛,心臟和肺。Body類中定義了Eye,Heart及Lung等的引用欄位,如下例:

  public class Body

  {

  public Eye leftEye,rightEye;

  private Heart heart;

  private Lung leftLung,rightLung;

  }

leftEye和rightEye欄位宣告為public是因為人的眼睛對觀察者來說是可見的。然而,heart,leftLung和rightLung宣告為private是因為這些器官是隱藏在人的身體裡面的。試想如果heart,leftLung和rightLung被宣告為public時,一個暴露了心和肺的身體還能象以前一樣工作嗎?

 

最後,一個欄位被定義為protected時與預設的存取級別類似。兩者僅有的區別是任何包中的該類的子類都能訪問protected欄位。例如:

public class Employee

{

  protected String name;

}

只有Employee類中的程式碼以及在Employee包中的所有其他類,以及Employee類的子類(定義在任何包中)才能夠存取name。

 

修改標識

  你可以任意使用如下的修改限定關鍵字來定義一個欄位:final或者volatile和/或者static和/或者transient。

 

  如果你將一個欄位定義為final,將確保欄位當成一個常量——只讀變數來初始化和處理。因為編譯器知道常量是不變的,所以在的位元組碼中對其進行了內部。如下例:

  class Employee

  {

  final int ACCOUNTANT = 1;

  final int PAYROLL_CLERK = 2;

  final int MANAGER = 3;

   

  int jobID = ACCOUNTANT;

  }

上例中定義了三個final int欄位:ACCOUNTANT,PAYROLL_CLERK和MANAGER.

 

注意:在常量的宣告中習慣上將所有字元大寫以及使用下劃線來分隔多個單詞。這將有助於在分析時區分常量和可寫讀變數。

 

  如果你將一個欄位宣告為volatile,則多執行緒將能訪問此欄位,而特定的編譯器將防止最最佳化以使該欄位能被適當的訪問。(你將在我以後的專欄中關於討論執行緒時學習volatile欄位。)

 

  如果你將一個欄位定義為static,則所有物件都將共享此欄位的一份複製。當你將一個新值賦給這個欄位時,所有物件都將得到這個新值。如果沒有指定為static,則這個欄位將是一個例項欄位,每個物件都使用他們自己的一份複製。

 

  最後,定義為transient的欄位值在物件化過程中將不被儲存。(我將在以後專欄中研究這個主題。)

 

例項欄位

“例項欄位”就是沒有使用static修改識別符號定義的欄位。例項欄位和物件緊密相連——而不是和類。當在一個物件程式碼裡修改時,僅僅這個相關的類例項——物件——可以得到這個改變。例項欄位隨物件的建立而建立,隨物件的釋放而釋放。

 

下例示範了一個例項欄位:

class SomeClass1

{

  int i = 5;

 

  void print()

  {

  System.out.println(i);

  }

 

  public static void main(String[] args)

  {

  SomeClass1 sc1 = new SomeClass1();

  System.out.println(sc1.i);

  }

  }

SomeClass1定義了一個名為i的例項欄位,並演示了兩種用相同方式訪問該例項欄位的方法—例項方法和類方法。兩個方法和例項欄位都在同一個類中。

 

  在同一類中透過例項方法來存取例項欄位,你僅需要使用該欄位名。而其他類中的例項方法訪問該例項欄位時,你必須有一個你要存取的例項欄位的物件的引用變數,該變數存放了從類建立的物件的地址。將這個物件的引用變數——和點運算子一起——做為這個例項欄位名的字首。(例項方法你可以在本文稍後看到。)

 

  在同一類中透過類方法來存取例項欄位,從這個類中建立一個物件,將其賦給一個引用變數,並將這變數做為這個例項欄位的字首。其他類的類方法訪問例項欄位時,和上述在同一類中訪問該欄位的步驟相同。(本文稍後講解類方法。)

 

  當JVM建立一個物件時,為每一個例項欄位分配空間並在接下來將這些欄位的記憶體清零。以給例項欄位賦以預設值。預設值依賴於其資料型別。引用欄位的預設值是null,數字欄位是0或0.0,布林值是false,字元是u0000.

 

類欄位

  類欄位是用static關鍵字定義的欄位。類欄位和類聯絡——而不是物件。當在一個類程式碼中修改時,這個類(以及所有建立的物件)都能感知這個變化。類欄位隨類的載入而建立,隨類的解除安裝而釋放。(我相信有些JVM解除安裝了類而其他有的JVM則沒有。)

 

  下例說明了類欄位:

  class SomeClass2

  {

  static int i = 5;

 

  void print()

  {

  System.out.println(i);

  }

 

  public static void main(String[] args)

  {

  System.out.println(i);

  }

  }

SomeClass2定義了一個名為i的類欄位,並演示了兩種用相同方式訪問該i的方法——例項方法和類方法(兩個方法都和類欄位在同一個類中)。

 

  在同類中透過例項方法來存取類欄位,你僅需要使用這個類欄位的名字。而其他類的例項方法訪問該類欄位時將定義這個類欄位的類名做為該類欄位的字首即可。例如:使用SomeClass2.i以使其他類的例項方法能存取i—這些類必須是在SomeClass2包中的,因為SomeClass2沒有宣告為public。

 

  同一類中的類方法訪問類欄位時,你也僅需使用該欄位名。其他類的類方法訪問這個類欄位時,和其他類中的例項方法訪問類欄位過程是相同的。

 

  當類一載入,JVM就給每一個類欄位分配記憶體並且賦給類欄位以預設值。類欄位的預設值和例項欄位的預設值相同。

 

  在Java中類欄位就如全域性變數。如下例:

  class Global
  {

  static String name;

  }

 

  class UseGlobal

  {

  public static void main(String[] args)

  {

  Global.name = “UseGlobal”;

  System.out.println(Global.name);

  }

  }

上面的程式碼在同一個中定義裡兩個類:Global和UseGlobal。如果你編譯和執行這個程式,JVM載入UseGlobal並且開始main()的位元組碼。當發現Global.name時,JVM查詢、載入、並檢驗Global類。一當Global類透過檢查,JVM就為name分配記憶體並將其初始化為null。在後臺,JVM建立了一個String物件並將其初始化為雙引號中的字元——UseGlobal。並將此引用賦給name。然後,程式獲取Global.name所指向的String物件的引用,然後將這傳給System.out.println()。最後,String物件的內容將顯示在標準輸出裝置上。

 

  因為無論Global還是UseGlobal都明確的定義為public,所以你可以選擇任何一個名字做為原始檔的名字。編譯此檔案的結果是兩個類檔案:Global.class和UseGlobal.class。因為UseGlobal中有main()方法,所以用這個類來執行此程式。在命令列鍵入:java UseGlobal來執行這個程式。

 

  如果你鍵入的是:java Global,你將得到如下錯誤:

  Exception in thread “main” java.lang.NoSuchMethodError: main

錯誤資訊行的最後一個詞main表示java編譯器在類Global中沒有發現main()方法。

 

常量

  “常量”是一種只讀變數;當JVM初始化這種變數後,變數的值就不能改變了。

 

  使用final關鍵字來定義常量。正如有兩種欄位——例項和類欄位,常量也有兩種——例項常量和類常量。為了提高,應當建立類常量,或者說是final static欄位。如:

  class Constants

  {

  final int FIRST = 1;

  final static int SECOND = 2;

 

  public static void main(String[] args)

  {

  int iteration = SECOND;

 

  if (iteration == FIRST)//編譯錯誤

  System.out.println(“first iteration”);

  else

  if (iteration == SECOND)

  System.out.println(“second iteration”);

  }

  }

上例中的Constants類定義了一對常量——FIRST和SECOND。FIRST是例項常量,因為JVM給每個Constants物件分配一份FIRST的複製。相反的,因為JVM在載入Constants類後只建立了一份SECOND複製,所以SECOND是類常量。

 

注意:當你嘗試在main()中直接訪問FIRST時會導致一個編譯錯誤。常量FIRST直到一個物件建立時才存在,所以FIRST僅僅只能被這個物件所訪問——而不是類。

 

  在以下兩種情況下使用常量:

1.  在原始碼中,修改常量的初始值比替換所有晦澀的數字更容易和少出錯。

2.  常量能使原始碼更具可讀性和更容易理解。例如:常量NUM_ARRAY_ELEMENTS比數字12更具意義。

 

列舉型別

  試想你正寫一個Java程式,Zoo1,來模仿一群馬戲團的動物:

  清單一,Zoo1.java

 

  //Zoo1.java

  class CircusAnimal

  {

  final static int TIGER = 1;

  final static int LION = 2;

  final static int ELEPHANT = 3;

  final static int MONKEY = 4;

  }

  class Zoo1

  {

  private int animal;

 

  public static void main(String[] args)

  {

  Zoo1 z1 = new Zoo1();

  Z1.animal = CircusAnimal.TIGER;

 

  //稍後………

  if ( z1.animal == CircusAnimal.TEGER)

  System.out.println(“This circus animal is a tiger!”);

  else

  if ( z1.animal == CircusAnimal.MONKEY)

  System.out.println(“This circus animal is a monkey!”);

  else

  System.out.println(“Don’t know what this circus animal is!”);

  }

  }

Zoo1.java定義了兩個類:CircusAnimal和Zoo1,類CircusAnimal定義了一些方便的整型常量來區分不同的動物。而Zoo1是靜態類——他的main()方法執行Zoo1程式。

 

  main()方法建立了Zoo1物件,將這個物件的引用賦給z1,並初始化這個物件的animal例項欄位給CircusAnimal.TIGER。雖然animal是private,但main()方法還是能夠訪問那些變數――透過z1這個物件的引用――因為main()是定義animal的類的一部分。接著,main()檢查animal的當前值並根據所檢查到的輸出一個適當的訊息。

 

  Zoo1有個特殊問題:animal的int資料型別關鍵字。假設我們將987324賦給z1.animal――那是合法的,因為animal和987324都是整型。但這賦值的結果卻是無意義的。987324是什麼動物?把整型做為animal的資料型別可能會產生一些不合理的程式碼,而這往往容易產生s。這個問題的解決辦法是給animal一個很適當的資料型別並能限制賦給animal的值的範圍。這就是使用列舉資料型別的基本原理。

 

  列舉資料型別就是一組限制值的引用資料型別。每個值都是由列舉資料型別建立的物件,如下:

  清單2:Zoo2.java

  //Zoo2.java

  class CircusAnimal

{

  static final CircusAnimal TIGER = new CircusAnimal ("Tiger");

  static final CircusAnimal LION = new CircusAnimal ("Lion");

static final CircusAnimal ELEPHANT = new CircusAnimal ("Elephant");

   static final CircusAnimal MONKEY = new CircusAnimal ("Monkey");

 

  private String animalName;

 

private CircusAnimal (String name)

{

animalName = name;

}

public String toString ()
{

 return animalName;

}

}

 

class Zoo2

{

  private CircusAnimal animal;

  public static void main (String [] args)

  {

 Zoo2 z2 = new Zoo2 ();

 z2.animal = CircusAnimal.TIGER;

 // Some time later ...

 if (z2.animal == CircusAnimal.TIGER)

 System.out.println ("This circus animal is a tiger!");

 else

 if (z2.animal == CircusAnimal.MONKEY)

  System.out.println ("This circus animal is a monkey!");

 else

System.out.println ("Don't know what this circus animal is!");

 }

}

 

CircusAnimal類定義了四個常量:TIGER,LION,ELEPHANT及MONKEY。每個常量初始化為一個CircusAnimal物件。

定義了一個特殊的CircusAnimal構造,其只帶一個String變數。這個字串將一個private animalName欄位傳給了建構函式。(我將在本文稍後介紹建構函式。)

建構函式被宣告為private是為了保證所建立的CircusAnimal物件不超出那被定義成常量的四個物件的範圍。你不想有建立一個無意義的987324的CircusAnimal物件吧。在Zoo2中僅這四個CircusAnimal常量是有效的。

為什麼在CircusAnimal中定義了一個toStiring()方法呢?這方法返回一個String物件,它引用的是animalName的值。假設你在初始化z2.animal為CircusAnimal.TIGER後System.out.println(z2.animal)時,結果是Tiger將被輸出,這個值是在CircusAnimal的建構函式中建立TIGER常量時賦予的。System.out.println()在後臺呼叫toString()方法以獲得Tiger。(在後續專欄中我將更多的講到toString()方法。)

現在你知道該怎麼定義何訪問欄位了,接下來你需要學習怎樣定義和訪問方法。

 

下節:定義方法

 

資源:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-956683/,如需轉載,請註明出處,否則將追究法律責任。

相關文章