2013年9月10日 星期二

Inner classes



作者: 許裕永

開發類別是為了能建構物件;一個類別能夠建構無數個物件。但如果只是一個,片段程式碼中會用到的物件,或者只是某個大物件中的一個獨有的物件。這時是否值得為了這個物件,來獨立開發一個類別,就值得商確了。
Java中的內部類別,指的是在類別之中開發類別,或是在方法之中開發類別,甚至可以開發沒有名字的類別。這些類別的寫法不難,限制也不多,在認證時卻可 以在大部份的題目中,看到它的身影。而且在開發實務上,也是佔有相當的份量,不會使用內部類別來開發程式的人,不算學會Java。
 
1. 實體內部類別成員
之前我們曾說過:「類別之中只有兩種成員:資料成員及方法成員。」。現在這句話要修正了。因為類別之中也可以宣告類別成員。
而因為宣告的類別是外部類別(Enclose Classes)的成員,所以實體內部類別成員(Inner Classes)宣告時,可以在class之前加上存取限制。而且類別之中,可以任意存取外部類別中其他的成員。
語法:[存取限制][類別形態]class ClassName{}
存取限制:和一般實體成員相同。
類別形態:和一般類別相同(final,abstract,但不可以是static)。
例一:
public class OuterClass{
    private int x,y;
    class InnerClass{
         private int z;
        void setValue(int s){
             z=x=y=s;
         }
    }
}
示範主題:1、InnerClass宣告的內容如同一般的類別。2、在InnerClass中可以存取外部類別中的任意成員(即使宣告為private)。
1-1 在外類別中建構內部類別物件
外部類別的任何實體方法成員中,均可以建構內部類別物件。
例一:
public class OuterClass{
 private int x,y;
 public int getValue(){
  InnerClass ic=new InnerClass();
  ic.setValue(10);
  return x+y;
 }
 class InnerClass{
  private int z;
  void setValue(int s){
   z=x=y=s;
  }
 }
 public static void main(String[] args){
  OuterClass oc=new OuterClass();
  System.out.println(oc.getValue());
 }
}
列印結果:20。
示範主題:在外部類別的實體方法成員中,建構內部類別物件。
1-2 在其他類別中建構內部類別物件
在取存限制許可的範圍之中,其他類別也可以建構某一個類別的內部類別。但是必須先建構外部類別物件,並透過此外部類別物件才能建構內部類別物件。
例一:
public class OuterClass{
 private int x,y;
 public int getValue(){
  return x+y;
 }
 class InnerClass{
  private int z;
  void setValue(int s){
   z=x=y=s;
  }
 }
 public static void main(String[] args){
  OuterClass oc=new OuterClass();
  OuterClass.InnerClass ic=oc.new InnerClass();
  ic.setValue(20);
  System.out.println(oc.getValue());
 }
}
列印主題:40。
示範主題:在其他類別中建構內部類別物件的宣告及建構語法。
例二:
將例一main中的建構內部類別物件的語法修改為:
OuterClass.InnerClass ic=new OuterClass().new InnerClass();
列印結果為:0。
示範主題:1、如果不需外部類別物件時,可以用此語法建構內部類別物件。
2、因為ic的建立與oc無關,也就是ic設定的x和y不是oc的x和y,所以列印結果為0。
1-3 內部類別的this
類別開發時常使用this來代表本類別物件,在內部類別中也不例外。
例一:
public class OuterClass{
 class InnerClass{
  void showThis(){
   System.out.println(this);
   System.out.println(OuterClass.this);
  }
 }
 public static void main(String[] args){
  OuterClass.InnerClass ic=new OuterClass().new InnerClass();
  ic.showThis();
 }
}
列印結果:OuterClass$InnerClass@757aef OuterClass@d9f9c3
示範主題:內部類別中,“this”指的是本類別物件;“外部類別名稱‧this”指的是外部類別物件。
2. 區域內部類別
宣告在方法中的類別,便是區域內部類別(Method-Local Inner Classes)。而因為是宣告在方法之中,就如同其他宣告在方法中的區域變數一般,不可以加存取限制修飾詞。
宣告語法:[類別形態]class ClassName{}
類別形態:abstract 或 final。(不可以是static)
因為是宣告在方法之中,自然也只能在本方法之中建構成物件,但是必須注意宣告及建構的順序,也就是程式敍述句排列的順序。如同其他區域變數一般,必須先宣告再建構。
例:
void abc(){
 class aa{}
 aaa a=new aa();
}
類別Aaa的宣告,必須在建構敍述句之上。
宣告在方法中的類別,也可以在類別中存取或呼叫外部類別的任意成員(即使宣告為private的成員)。
例一:
public class OuterClass{
 private int x;
 public void showLocal(){
  x=9;
  class MethodLocal{
   int y=10;
   void showValue(){
    System.out.println(x*y);
   }
  }
  MethodLocal ml=new MethodLocal();
  ml.showValue();
 }
 public static void main(String[] args){
  new OuterClass().showLocal();
 }
}
列印結果:90。
示範主題:Method-Local Inner Classes可以存取外部類別的任意成員。
區域內部類別有一個特別要注意的重點:「不可以存取方法中宣告的區域變數,除非該變數宣告為final」。這是因為區域變數的存活期為該方法的執行時期, 若方法結束則區域變數便不存在,但是該區域內部類別建構的物件,卻可能在方法執行完畢後,還繼續存在。如果它仍然存取已經不存在的區域變數,便會產生錯 誤。但是宣告為final的區域變數,其記憶體配置的方式與區域變數不同,其值並不會消失,所以可以被區域內部類別建構的物件存取。
例二:
public class OuterClass{
 public void showLocal(){
  int x=9;
  class MethodLocal{
   int y=10;
   void showValue(){
    System.out.println(x*y);
   }
  }
 MethodLocal ml=new MethodLocal();
  ml.showValue();
 }
 public static void main(String[] args){
  new OuterClass().showLocal();
 }
}
本例編譯錯誤。
示範主題:區域內部類別不可以存取非宣告為final的區域變數。(若在int x=9;之前加上final,則本例列印:90)。
 
3. 匿名類別
匿名類別的白話文叫做:沒有名字的類別(Anonymous Inner Classes)。在學會匿名類別的寫法之前,一定要先知道兩個重點:1、匿名 類別是寫在方法中的程式碼,所以它也必須符合區域內部類別的規範,尤其是:「不可以存取非宣告為final的區域變數」。2、此類別只能使用一次,也就是 只能建構一個物件。如果此物件有必要重複使用,應該用參考變數儲存此物件之物件代號。
匿名類別,是開發某一個類別的下層類別,或開發實作某一個界面的類別。但是,沒有辦法既繼承類別又實作界面,這是要特別注意的。
宣告語法一:new ClassName(參數列){}
建構一個匿名類別的物件,並指定呼叫上層類別的建構方法。
ClassName指的是匿名類別要繼承的上層類別名稱。但是連同參數列一起看,其實是呼叫上層類別的建構方法。而在建構方法後端,以大括號表示建立一個 新類別,大括號中可以定義下層類別的成員,一般用來overriding上層類別的方法成員。而整句的意思便是:「開發一個新類別繼承 ClassName,再以這個新類別建構成物件」。
以此語法來體會,我們應該要知道,Java這個匿名類別機制的重點不是匿名類別本身;而是要建構某一個類別的下層類別物件。因為這個下層類別物件必須擁有上層類別物件所沒有的功能,而開發一個全新的類別又沒必要的時候,便使用匿名類別機制,來建構這個下層類別物件。
例一:
import java.io.*;
public class AnoClassTest{
 public static void main(String[] args){
  File f=new File("AnoClassTest.java"){
   public String getName(){
    return "檔案名稱:" + super.getName();
   }
  };//請特別注意這個‘;’
  System.out.println(f.getName());
 }
}
列印結果:檔案名稱:AnoClassTest.java
示範主題:1、建構下層類別物件,並指派給以上層類別名稱宣告之參考變數。(物件多型)2、下層類別中overriding上層類別的方法成員,來增加上 層類別物件所沒有的功能。3、物件執行的,是下層類別重新定義的方法內容(物件多型)。4、撰寫此語言時,一定要注意File f=…這行敍述句一直到 ‘;’才結束。撰寫時是為了方便程式碼的閱讀,才會分成數行文字,但一定不要忘記,敍述句是以‘;’結尾。
宣告語法二:new InterfaceName(){Overriding all methods}
建構實作此界面的匿名類別的物件。
和建構下層類別物件有兩大差異:1、沒有參數列(要保留小括號)。2、要overriding InterfaceName中的所有方法。
例二:
interface MyInterface{
 void showText();
}
public class AnoClassTest{
 public static void main(String[] args){
  MyInterface mi1=new MyInterface(){
   public void showText(){
    System.out.println("Hello");
   }
  };
  MyInterface mi2=new MyInterface(){
   public void showText(){
    System.out.println("Hi");
   }
  };
  mi1.showText();
  mi2.showText();
 }
}
列印結果:Hello Hi
示範主題:1、建構的不是界面物件,而是實作界面的匿名類別的物件。2、物件指派給以界面名稱宣告的參考變數(物件多型)。3、不同的物件執行的方法,是建構自己的匿名類別中定義的方法內容。
例一和例二中都是把物件指派給參考變數,其優點是透過參考變數,可以重複使用該物件。但並不是一定要把物件指派給參考變數,也可以把物件直接當參數使用。
例:addWindowListener(new WindowListener(){……..});
…….代表overriding WindowListener中的所有方法(請參閱本章範例二)。這種寫法要注意的還是括號及分號,請注意是整個建構匿名類別物件的語法都寫在小括號之中當參數。
匿名類別定義的時候還有一些要注意的事項:
  1. 财 不可以宣告建構方法。
    因為匿名類別沒有名字,而建構方法的名稱必須和類別名稱完全一樣,自然無法定義。
  2. 财 不能加任何修飾詞。無論是final,abstract,static,或存取限制修飾詞。
    因為這些修飾詞必須加在類別宣告句之前,我們剛才的範例中有寫到類別宣告句嗎?沒有。我們宣告的是參考變數。也就是說,如果你在宣告句前加上final,編譯和執行自然都沒有問題,只不過你是宣告參考變數為final,而不是類別為final。
  3. 财 沒有必要宣告任何下層類別的新成員。
    在匿名類別中新增資料成員或方法成員都是合法的,也就是編譯沒有問題,只是沒有必要。因為不可能用得到:「以上層類別宣告的參考變數只能存取上層類別中宣告的成員,不能存取在下層類別中宣告的成員(物件多型)」。
4. 靜態內部類別
在宣告類別成員時,在宣告句前端加上修飾詞static,則此內部類別即為靜態內部類別(Static Inner Classes)。意思是類別中的靜態內部類別。
因為宣告為static,所以和其他類別成員一樣,不屬於物件,而且不可以存取類別中的非static成員。
宣告語法:static[存取限制] [類別形態]class ClassName{}
除了一定要有static之外(若沒有即變成實體內部類別成員),其他均和一般類別相同。
例:
public class OuterClass{
 static class InnerClass{}
}
1-1 在外類別中建構類別成員內部類別物件
例一:
public class OuterClass{
 static int x;
 static class InnerClass{
  void setValue(){
   x=8;
  }
 }
 public void abc(){
  InnerClass ic=new InnerClass();
  ic.setValue();
 }
 public static void main(String[] args){
  OuterClass oc=new OuterClass();
  oc.abc();
  System.out.println(oc.x);
 }
}
列印結果:8。
示範主題:1、Static Nested Classes中只能存取外部類別的static成員。2、任何外部類別的實體方法成員(abc()),均可 以存取Static Nested Classes。3、Static Nested Classes中宣告的成員,不一定要是static成員。
1-2 在其他類別中建構類別成員內部類別物件
例二:
public class OuterClass{
 static int x;
 static class InnerClass{
  void setValue(){
   x=8;
  }
 }
 public static void main(String[] args){
  OuterClass.InnerClass oic=new OuterClass.InnerClass();
  oic.setValue();
  System.out.println(OuterClass.x);
 }
}
列印結果:8。
示範主題:在其他類別建構Static Nested Classes物件時,只需要用外部類別的名稱來呼叫其建構方法,不需要先建構外部類別物件。而且new指令下的位置也不同,請注意。
實體內部類別成員物件建構:
Outer outer=new Outer();
Outer.Inner inner=outer.new Inner();

Outer.Inner inner=new Outer().new Inner();
總之,要透過外部類別物件才能建構內部類別物件。
類別靜態類別成員:
Outer.Inner inner=new Outer.Inner();
直接以外部類別名稱呼叫建構方法。
1-3 this
static成員中不可存取任何實體成員,因為和物件無關,所以也就不可以使用代表本類別物件的this。
 
範例一:以實體內部類別成員成式實作視窗監聽器界面。
檔案:MyWindow.java
import java.awt.*;
import java.awt.event.*;
public class MyWindow extends Frame{
 public MyWindow(){
  super("大笨視窗");
  setBounds(200,200,180,100);
  addWindowListener(new MyWindowListener());
 }
 class MyWindowListener implements WindowListener{
  public void windowActivated(WindowEvent e) {}
  public void windowClosed(WindowEvent e) {}
  public void windowClosing(WindowEvent e) {
   dispose();
  }
  public void windowDeactivated(WindowEvent e) {}
  public void windowDeiconified(WindowEvent e) {}
  public void windowIconified(WindowEvent e) {}
  public void windowOpened(WindowEvent e) {}
 }
 
 public static void main(String[] args){
  new MyWindow().show();
 }
}
範例主題:1、在內部類別中實作監聽器界面,可以讓外部類別不必實作界面的許多方法,而擁有太多方法成員。2、addListener()中的參數是內部類別的物件。
範例二:以實作視窗監聽器界面的匿名類別建構之物件為參數。
檔案:MyWindow.java
import java.awt.*;
import java.awt.event.*;
public class MyWindow extends Frame{
 public MyWindow(){
  super("大笨視窗");
  setBounds(200,200,180,100);
  addWindowListener(new WindowListener(){
   public void windowActivated(WindowEvent e) {}
   public void windowClosed(WindowEvent e) {}
   public void windowClosing(WindowEvent e) {
     dispose();
   }
   public void windowDeactivated(WindowEvent e) {}
   public void windowDeiconified(WindowEvent e) {}
   public void windowIconified(WindowEvent e) {}
   public void windowOpened(WindowEvent e) {}
 });
}
 public static void main(String[] args){
  new MyWindow().show();
 }
}
範例主題:以實作視窗監聽器界面的匿名類別建構之物件為addWindowListener()的參數,可以省略內部類別之宣告,但是僅適用於:此物件只須建構一個的狀況。
範例三:運用監聽器轉接類別來建構匿名類別物件。
檔案名稱:MyWindows.java
import java.awt.*;
import java.awt.event.*;
public class MyWindow extends Frame{
 public MyWindow(){
  super("大笨視窗");
  setBounds(200,200,180,100);
  addWindowListener(new WindowAdapter(){
   public void windowClosing(WindowEvent e) {
    dispose();
   }
  });
 }
 public static void main(String[] args){
  new MyWindow().show();
 }
}
範例主題:在java‧awt‧event中,Java針對常用的視窗元件的事件監聽器,均發了轉接器類別(XxxAdapter)。這些類別分別實作了 一個或一個以上的界面,也就是說已經overriding了那些界面的方法。需用到監聽器時,只須開發新類別繼承那些轉接器類別後,overriding 會用到的方法即可。這樣就不必像實作界面一般,要overriding界面中的所有方法。特別適用於匿名類別。
5. 認證重點整理
5-1 實體內部類別成員
  • 财 實體內部類別成員之中,可以任意存取外部類別中其他的成員(即使宣告為private)。
  • 财 外部類別的任何實體方法成員中,均可以建構內部類別物件。
  • 财 必須先建構外部類別物件,並透過此外部類別物件才能建構內部類別物件。
  • 财 內部類別中,“this”指的是本類別物件;“外部類別名稱‧this”指的是外部類別物件。

5-2 區域內部類別
  • 财 因為是宣告在方法之中,就如同其他宣告在方法中的區域變數一般,不可以加存取限制修飾詞。
  • 财 必須注意宣告及建構的順序,也就是程式敍述句排列的順序。如同其他區域變數一般,必須先宣告再建構。
  • 财 方法中的類別,也可以在類別中存取或呼叫外部類別的任意成員(即使宣告為private的成員)。
  • 财 不可以存取方法中宣告的區域變數,除非該變數宣告為final。
5-3 匿名類別
  • 财 匿名類別是寫在方法中的程式碼,所以它也必須符合區域內部類別的規範,尤其是:不可以存取非宣告為final的區域變數。
  • 财 此類別只能使用一次,也就是只能建構一個物件。
  • 财 匿名類別,是開發某一個類別的下層類別,或開發實作某一個界面的類別。但是,沒有辦法既繼承類別又實作界面。
  • 财 撰寫時是為了方便程式碼的閱讀,才會分成數行文字,但一定不要忘記,敍述句是以‘;’結尾。
  • 财 實作界面的匿名類別的物件和建構下層類別物件有兩大差異:1、沒有參數列(要保留小括號)。2、要overriding InterfaceName中的所有方法。
  • 财 不是一定要把物件指派給參考變數,也可以把物件直接當參數使用。
  • 财 不可以宣告建構方法。
  • 财 不能加任何修飾詞。無論是final,abstract,static,或存取限制修飾詞。
  • 财 沒有必要宣告任何下層類別的新成員。
5-4 巢狀類別成員
  • 财 Static Nested Classes中只能存取外部類別的static成員。
  • 财 任何外部類別的實體方法成員(abc()),均可以存取Static Nested Classes。
  • 财 Static Nested Classes中宣告的成員,不一定要是static成員。
  • 财 在其他類別建構Static Nested Classes物件時,只需要用外部類別的名稱來呼叫其建構方法,不需要先建構外部類別物件。
  • 财 不可以使用代表本類別物件的this。
 

沒有留言:

張貼留言