2013年9月10日 星期二

Enum And Garbage collector


作者: 許裕永

1. Enum
java‧lang‧Enum是一個抽象類別。但是,它不接受設計師撰寫類別來繼承它。它只接受Java的編譯器,在編譯用關鍵字“enum”宣告的敍述時,由編譯器以背景方式產生的類別來繼承,並建構物件。
Enum建構的物件是一個列舉物件。列舉物件實務上我們時常用到,在以前我們都必須自行開發類別,來建構列舉物件。現在,Java提供了一個新的機制,讓我們可以用比較簡易的方式,開發列舉物件的類別。
我們先來看以前的寫法:
例一:
class Week{

 public static final Week SUN=new Week("星期日",0);
 public static final Week MON=new Week("星期一",1);
 public static final Week TUE=new Week("星期二",2);
 public static final Week WED=new Week("星期三",3);
 public static final Week THU=new Week("星期四",4);
 public static final Week FRI=new Week("星期五",5);
 public static final Week SAT=new Week("星期六",6);
 private String name;
 private int index;
 private Week(String name,int index){
  this.name=name;
  this.index=index;
 }
 public int getIndex(){
  return index;
 }
 public String getName(){
  return name;
 }
 public static Week[] values(){
  return new Week[]{SUN,MON,TUE,WED,THU,FRI,SAT};
 }
 public String toString(){
  return name;
 }
}
public class WeekTest{
 public static void main(String[] args){
  System.out.println(Week.MON);
  Week w=Week.TUE;
  System.out.println(w.getIndex());
  Week[] array=Week.values();
  for(Week k:array){
   System.out.println(k);
  }
 }
}
檔案重點:為了方便,兩個類別寫於同一檔案,讀者可以自行拆開。
列印結果:星期一 2 
星期日 星期一 星期二 星期三 星期四 星期五 星期六
示範主題:1、由本類別提供static final的本類別物件(我們曾在Locale類別中使用過)。2、在WeekTest中,使用者不是建構 Week物件,而是取得預設的Week物件。3、因為Week的建構方法為private,所以WeekTest中不能夠建構Week物件(),只能使用 Week中預設的物件。4、此種類別特別適合使用在值固定而且重複使用性高的資料(只要把資料撰寫在toString()之中,即可輕意顯示)。
Enum的出現,讓我們可以輕鬆的開發Week的列舉物件。
 
1-1 宣告
宣告語法:enum EnumName{ObjectName1,ObjectName2….}
enum:關鍵詞,Java編譯器會以背景作業方式建立繼承Enum類別的子類別,並建構類別中的列舉物件。
EnumName:自訂的類別名稱。
ObjectName:自訂的列舉物件名稱,個數不限。這些物件一定是:public、final、static。
例一:
enum Week{SUN,MON,TUE,WED,THU,FRI,SAT}
public class EnumTest{
 public static void main(String[] args){
  System.out.println(Week.SUN);
  Week w=Week.TUE;
  System.out.println(w.ordinal());
  Week[] array=Week.values();
  for(Week k:array)
   System.out.println(k);
 }
}
檔案重點:1、class Week修改成enum,大括號中宣告的各個名稱,就是Week類別中的類別常數成員。2、在main中除了取得物件編號的方法修改為ordinal()之外,其餘的敍述句和前一個範例一模一樣。
列印結果:SUN 2
SUN MON TUE WED THU FRI SAT
示範主題:1、enum宣告的敍述,其實就是在宣告一個類別。Java編譯器會以我們宣告的名稱,替我們開發一個繼承Enum的類別,並替我們撰寫類似我 們在上一個範例的class Week中撰寫的程式碼,讓列舉物件可以擁有一些基本的方法可以呼叫。2、列舉物件列印時的預設內容是該物件的名字。
 
1-2 自訂建構方法
如果只是為了列印SUN、MON、TUE………這些資料,建構字串陣列也就夠了,不見得需要enum。我們要的是:讓Enum物件可以列印我們指定的資料。所以,我們可以像開發類別一樣,在eunm中,宣告及定義資料成員、方法成員及建構方法。
例一:
enum Week{SUN("星期日"),MON("星期一"),TUE("星期二"),WED("星期三"),THU("星期四"),FRI("星期五"),SAT("星期六");
private String name;
 private Week(String name){
  this.name=name;
 }
 public String toString(){
  return name;
 }
}
public class EnumTest{
 public static void main(String[] args){
  System.out.println(Week.SUN);
  Week w=Week.TUE;
  System.out.println(w.ordinal());
  Week[] array=Week.values();
  for(Week k:array)
   System.out.println(k);
 }
}
檔案重點:1、enum中自訂了一個資料成員name。2、enum中自訂了一個建構方法,此建構方法的存取限制為private,表示不接受外界建構物 件。若宣告為public將無法編譯。3、enum中overriding了toString()的方法。4、enum中的類別常數成員在宣告時,必須用 小括號提供建構方法參數(因為預設的沒有參數的建構方法已經不存在)。5、特別注意:enum中的類別常數成員宣告完畢後,必須上‘;’,才可以開始自訂 其他的成員或方法。
不過如果要自訂的成員很多,就不見得要用enum機制,開發為class的功能還是比較齊全。
 
1-3 預設的重要方法
Enum中有宣告一些方法,Java編譯器會幫我們把enum宣告的類別繼承Enum,所以新類別中自然就繼承了Enum的方法。另外還會新增一些Enum中沒有宣告的方法。
方法一:String name()
運算結果:取得常數成員的名稱。toString()預設也是取得常數成員的名稱,但一般我們會overriding toString()來顯示特定的資訊,此時就可以用name()來取得常數成員的名稱。
方法二:int ordinal() 
運算結果:取得常數成員的編號(序號,從0 ~ 成員個數-1)。switch支援enum成員的判斷,判斷時便是自動呼叫該成員的ordinal()來取得整數值。
方法三:static T valueOf(Class<T> enumType, String name)
運算結果:此方法是從Enum繼承過來的方法,用來取得一個指定名稱的enum物件。第一個參數必須是一個class檔的完整名稱,例:Week‧class。這個方法的呼叫,大部份是用來取得其他enum的物件。
方法四:static T valueOf(String name)
運算結果:此方法是Java編譯器在子類別中overriding 父類別Enum的方法,用來取得一個指定名稱的enum物件。用字串指定取得一個本列舉類別中的列舉物件。和方法三比較起來,省略類別名稱比較方便,但只能取得本enum的物件。
 
方法五:static enumType[] values()
運算結果: 以呼叫此方法的enum類別中的所有列舉物件為元素,建構成陣列。
例一:
import java.util.*;
enum Week{SUN(“星期日”),MON(“星期一”),TUE(“星期二”),WED(“星期三”),THU(“星期四”),FRI(“星期五”),SAT(“星期六”);
 private String name;
 private Week(String name){
  this.name=name;
 }
 public String toString(){
  return name;
 }
}
public class EnumTest{
 public static void main(String[] args){
  Scanner s=new Scanner(System.in);
  System.out.print(“請輸入物件名稱à”);
  String name=s.next();
  Week w=Enum.valueOf(Week.class,name);
  switch(w){
   case SUN:
    System.out.println(w.name() + “:” +w);
    break;
   case MON:
    System.out.println(w.name() + “:” +w);
    break;
   case TUE:
    System.out.println(w.name() + “:” +w);
    break;
   case WED:
    System.out.println(w.name() + “:” +w);
    break;
   case THU:
    System.out.println(w.name() + “:” +w);
    break;
   case FRI:
    System.out.println(w.name() + “:” +w);
    break;
   case SAT:
    System.out.println(w.name() + “:” +w);
    break;
  }
 }
}
檔案重點:1、以使用者輸入的字串為參數,取得列舉物件。2、以列舉物件為switch判斷的運算依據。3、以列舉物件名稱為case的選項。
列印結果:依使用者輸入的名稱(例:SUN),列印該物件的名稱及資訊(例:SUN:星期日)。
示範主題:1、用valueOf取得列舉物件。2、當switch()的小括號中,用的是某一個列舉物件時,此switch的case後面,可以用該列舉 物件的類別中的所有列舉物件名稱為判斷值,Java會自動呼叫這些物件的ordinal()方法,取得整數值來做為判斷的依據。
 
1-4 宣告為內部類別
enum也可以宣告在類別之中,成為內部類別,但是不可以宣告在方法之中。
宣告為內部類別的enum,預設為static。
例一:
class Test{
 enum Week{SUN("星期日"),MON("星期一"),TUE("星期二"),WED("星期三"),THU("星期四"),FRI("星期五"),SAT("星期六");
  private String name;
  private Week(String name){
   this.name=name;
  }
  public String toString(){
   return name;
  }
 }
}
public class EnumTest{
 public static void main(String[] args){
  System.out.println(Test.Week.SUN);
 }
}
檔案重點:將enum宣告於類別之中。
列印結果:星期日。
示範主題:enum宣告類別之中,預設為static,外界可以透過外部類別名稱存取該enum。
 
範例:將各分公司基本資料建立為列舉,方便後續程式使用。
 
2. Garbage Collection
2-1 物件收原基本原理
在其他支援物件導向的語言中,物件的建構與銷毀,都必須由設師自行撰寫程式碼(例:C++。以new建構;以delete銷毀)。全部由設計師撰寫,好處 是效率高,只要物件沒有再利用的機會,銷毀後可以立刻把記憶體空間歸還給作業系統。缺點是,只要有一個物件忘記銷毀,可能就是一連串惡夢的開始。
Java提供了垃圾物件自動回收機制,讓設計師可以不必分心於物件的銷毀。Java的執行環境,建構了一個垃圾集合物件(Garbage  Colletion),任何程式中建構的物件,只要是適合被回收的物件,Java便會把該物件的物件代號,記錄於Garbage Collection物 件之中(請注意,此時尚未銷毀)。執行環境會依照程式執行的狀況,找到適當的時機,再把記錄於垃圾集合件中的物件代號所代表的所有物件,全部銷毀。
這種機制,或許浪費了一些效能,但卻可以省下設計師不少的困擾(時間)。
 
2-2 適合被回收的物件
適合被回收的物件只有一種:沒有參數變數儲存其物件代號的物件。因為沒有參考變數儲存物件代號的物件,設計師已不可能再存取該物件,該物件自然沒有繼續存在的必要。
所以本小節要討論不是「什麼物件適合被回收」,而是「我們寫了什麼程式碼,會造成物件被回收」。
 
  • 匿名物件:
物件建構之後,沒有把物件代號指派給參考變數的物件。此類物件在其執行完該行敍述句後,便會被記錄於垃圾集合物件之中。
例一:
System.out.println(DateFormat.getDateInstance().format(new Date()));
列印結果:執行時的日期(例:2007/1/10)。
示範主題:本例建構了兩個匿名物件,一個是DateFormat物件,另一個是Date物件。
例二:
import java.io.*;
import java.util.*;
public class JavaTest{
 public static void main(String[] args)throws IOException{
  Scanner s=new Scanner(new File("JavaTest.java"));
  int wordCount=0;
  while(s.hasNext()){
   wordCount+=s.next().length();
  }
  System.out.println("檔案中共有" + wordCount + "個字");
 }
}
列印結果:檔案中共有261個字。
示範主題:對於只使用一次的物件,以匿名物件的方式撰寫。
本例中s‧next()的運算結果,便是一個String型別物件,我們取得此物件的長度後,並不會再次使用此物件。所以不見得要把此物件的物件代號指派給參考變數。
 
  • 指派其他物件代號給參考變數:
把B物件之物件代號,指派給原先儲存A物件代號的參考變數,而A物件已經沒有任何參考變數儲存它的物件代號時,A物件便會被記錄到垃圾集合物件之中。
例一:
String s="Java";
s="SCJP";
示範主題:值為“Java”的String物件將被回收。
例二:
String s="Java";
String s2="SCJP";
String s3=s;
s=s2;
s2="SCWCD";
示範主題:本例沒有物件將被回收。
 
  • 指派null給參考變數:
指派null給原先儲存A物件代號的參考變數,而A物件已經沒有任何參考變數儲存它的物件代號時,A物件便會被記錄到垃圾集合物件之中。
例一:
String s="Java";
s=null;
s="SCJP";
示範主題:1、ava”的String物件將被回收。2、被回收的是物件,參考變數並不會被回收,可以再指派型別符合的物件代號給它。
例二:
void a(){
 String s="Java";
 b(s);
 System.out.println(s);
}
void b(String s){
 s=null;
}
示範主題:a方法中把參考變數s所儲存的物件代號做為參數,指派給方法b的參數s。此時兩個方法中的區域變數s,儲存的是同一個物件的物件代號。在把方法 b中的參數s指派為null時,這個s是方法b的區域變數,它的任何變化並不會影響到方法a的區域變數。所以本例的方法b執行完畢之時,不會有任何物件被 回收。
  • 被回收的物件所擁有的物件:
A物件中有一個String的資料成員;也就是A物件之中有一個參考變數,儲存著另一個String型別物件的物件代號;也就是A物件擁有一個Sting 型別的物件。假設已經沒有參考變數,儲存A物件的物件代號,則A物件將被回收;而A物件被回收之後,A物件中的參考變數也就不存在,而該參考變數所代表的 String物件自然也會被回收。
例一:
public class JavaTest{
 public static void main(String[] args)throws IOException{
  A a=new A();
  a=null;
 }
 class A{
  String s;
  Integer i;
  A(){
   s="Guest";
   i=18;
  }
 }
}
示範主題:本例執行完畢,共有3個物件被回收(A,String,Integer)。
例二:
public class JavaTest{
 public static void main(String[] args){
  A b1=new A();
  A b2=new A();
  A b3=new A();
  A b4=new A();
  b1.a=b2;
  b2.a=b3;
  b3.a=b4;
  b4.a=b1;
  b1=null;
  b2=null;
  b3=null;
  b4=null;
  //……..
 }
}
class A{
 A a;
}
示範主題:在程式執行至“b4=null;”時,用類別A所建構的四個物件便會全部被記錄到垃圾集合物件之中。雖然b1中的a記錄著b2的物件代號,b2 中的a記錄著b3的物件代號……,看起來是每個物件都有參考變數儲存著它的物件代號,但是那些參考變數都是物件的成員,當擁有參考變數的物件被回收,該參 考變數就不存在了。
類別中宣告一個本類別的參考變數,來記錄另一個本類別物件的物件代號,這是集合中常用的串列寫法,也就是一個物件可以連接另一個物件。如果讀者曾經學過資 料結構的話,應該不陌生。在本例的重點只是告訴讀者,這種串列物件的物件代號,雖然有被另一個物件所記錄,但只要物件之外已經完全沒有參考變數指向它們其 中的一個,它們就變成是一個被孤立的物件群組,也就會全部被回收。
判斷的標準可以用設計師的角度來看:你有沒有辦法存取它們。
 
例三:
public class JavaTest{
 public static void main(String[] args){
  A b1=new A();
  A b2=new A();
  A b3=new A();
  A b4=new A();
  b1.a=b2;
  b2.a=b3;
  b3.a=b4;
  b4.a=b1;
  b2=null;
  b3=null;
  b4=null;
  System.out.println(b1.a.a.a);
 }
}
class A{
 A a;
}
列印結果:A@35ce36
示範主題:本例在main執行完畢之前,沒有任何物件會被回收。
本例中雖然b2、b3、b4被指派為null。但還是可以透過b1這個物件外的參考變數,存取整個串列。也就是整個串列中的所有元素,設計師還是可以存取,這些物件自然不會被回收。
此部份是為了應付認證的試題,如果讀者志不在認證,有概念即可。有志認證者,在測驗時,應該在計算紙上繪製記憶體配置圖。
 
2-3 立刻執行物件的銷毀
物件代號被記錄在垃圾集合物件中的物件,什麼時候會真的被銷毀?答案是:看Java的執行環境高興。Java的執行環境會自行安排時間,找出最不影響程式 執行的時機來執行。但如果設計師覺得有必要的話,也可以透過下列兩種方式,強迫Java執行環境立刻執行物件銷毀的動作,把記憶體空間歸還給作業系統。
方式一:呼叫類別System中的類別方法成員gc()。
例:
System.gc();
方式二:建構RunTime物件,執行其實體方法成員gc()。
例:
Runtime rt=Runtime.getRuntime();
rt.gc();

Runtime.getRuntime().gc();
3. 認證重點整理
3-1 Enum
  • 财 Enum是一個抽象類別。但是,它不接受設計師撰寫類別來繼承它。
  • 财 enum是一個宣告列舉類別的關鍵詞,Java編譯器會以背景作業方式建立繼承Enum類別的子類別,並建構類別中的列舉物件。
  • 财 我們可以像開發類別一樣,在eunm中,宣告及定義資料成員、方法成員及建構方法。
  • 财 enum中的類別常數成員宣告完畢後,必須上‘;’,才可以開始自訂其他的成員或方法。
  • 财 若自訂有參數的建構方法,enum中的類別常數成員在宣告時,必須用小括號提供建構方法參數。
  • 财 enum中自訂建構方法的存取限制,若宣告為public將無法編譯。
  • 财 switch支援enum成員的判斷,判斷時便是自動呼叫該成員的ordinal()來取得整數值。
  • 财 enum也可以宣告在類別之中,成為內部類別,但是不可以宣告在方法之中。
  • 财 宣告為內部類別的enum,預設為static。
3-2 Garbage Collection
  • 财 Java的執行環境,建構了一個垃圾集合物件(Garbage Colletion),任何程式中建構的物件,只要是適合被回收的物件,Java便會把該物件的物件代號,記錄於Garbage Collection物件之中。
  • 财 執行環境會依照程式執行的狀況,找到適當的時機,再把記錄於垃圾集合件中的物件代號所代表的所有物件,全部銷毀。
  • 财 適合被回收的物件只有一種:沒有參數變數儲存其物件代號的物件。
  • 财 物件建構之後,沒有把物件代號指派給參考變數的物件。此類物件在其執行完該行敍述句後,便會被記錄於垃圾集合物件之中。
  • 财 把B物件之物件代號,指派給原先儲存A物件代號的參考變數,而A物件已經沒有任何參考變數儲存它的物件代號時,A物件便會被記錄到垃圾集合物件之中。
  • 财 指派null給原先儲存A物件代號的參考變數,而A物件已經沒有任何參考變數儲存它的物件代號時,A物件便會被記錄到垃圾集合物件之中。
  • 财 一個被孤立的物件群組,會全部被回收。
  • 财 呼叫類別System中的類別方法成員gc(),可以命令Java執行環境立刻執行物件銷毀的動作。
  • 财 取得RunTime物件,執行其實體方法成員gc(),可以命令Java執行環境立刻執行物件銷毀的動作。

沒有留言:

張貼留言