2006年12月24日 星期日

套件

當程式寫得越來越多,管理檔案將不容易。
因此需要將程式分類,
也就是俗稱的:套件 package。
分類通常都採用目錄的方式
如將 bank 所屬的 Account.java SavingAccount.java 和 CreditAccount.java 放在同一個目錄
放到 bank 目錄後
要加上 package 名稱 (也就是資料夾名稱);
package bank;
同樣在 util 目錄中心增一個 report
package util;

public class Report {
 public static void list (CreditAccount[] cas){
  for (int c=0; c < cas.length; c++) {
   System.out.println( cas[c].toString() );
  }
 }
}
這時邊議會有很多錯誤,要 import 才能讓編譯器知道改怎麼找 class
最重要的程式
import bank.CreditAccount;
import util.Report;

public class Test {
 public static void main(String[] args) {
  CreditAccount[] cas = new CreditAccount[3];
  cas[0] = new CreditAccount(101,"Simon",200,0.02,100);
  cas[1] = new CreditAccount(102,"Mary",300,0.01,200);
  cas[2] = new CreditAccount(103,"John",500,0.01,300);

  Report.list(cas);
 }
}
這樣可以清楚的列出 creditAccount 陣列資料
而如果要每次都 import 一個檔案的話就累人了
因此可以改用 import 一個套件的所有檔案
import bank.*;
import util.*;
//這樣可以使用套件中的所有 class
import static java.lang.System.out;
//這樣可以少寫 System.out.println 的 System
import static util.Report.list;
//這樣也可以少寫 Report.

public class Test {
 public static void main(String[] args) {
  CreditAccount[] cas = new CreditAccount[3];
  cas[0] = new CreditAccount(101,"Simon",200,0.02,100);
  cas[1] = new CreditAccount(102,"Mary",300,0.01,200);
  cas[2] = new CreditAccount(103,"John",500,0.01,300);

  //原來是 Report.list(cas);
  list(cas);
  Account a = new Account(105,"Ban",200);
  //原來是 System.out.println(a);
  out.println(a);
 }
}
這樣就可以管理大量的程式,又不會混淆啦!
重新定義方法
任何類別物件都有一個父類別 是 Object
因此也繼承了這個 Object 的方法

我們可以加以改寫橫跨多個 class 的方法
首先改寫 Account.java
重新定義方法,就會把 Object 的 toString 方法覆蓋
public String toString(){
 return number+"\t"+name+"\t"+balance;
}
接下來改寫 SavingAccount.java
重新定義方法
public String toString(){
 return super.toString() + "\t"+rate;
}
接下來改寫 CreditAccount.java
重新定義方法
public String toString(){
 return super.toString() + "\t"+limit;
}
最後執行
public class Test2 {
 public static void main(String[] args) {
  CreditAccount ca = new CreditAccount(101,"Simon",200,0.02,30000);
  System.out.println( ca.toString() );
 }
}
//結果顯示 101 Simon 200.0 0.02 30000.0
就可以看到結果。

垃圾收集
當物件記憶體被剷除的時候,物件會發生一個 finalize() 物件
這也是 Object 物件的一個方法。
首先在 Account.java 中加入
protected void finalize(){
 System.out.println(number+"clean.");
}
測試程式
public class Test2 {
 public static void main(String[] args) {
  CreditAccount ca = new CreditAccount(101,"Simon",200,0.02,30000);
  System.out.println( ca );
  ca = null;
  System.gc();
 }
}
會看到記憶體垃圾回收的訊息

繼承

先前談到了 Account 物件
適用來處理帳戶
有一個帳戶名稱、編號、存款餘額
但當我們來思考 SavingAccount 儲蓄存款的時候
我們會寫一個全新的 SavingAccount 物件
會發現也有一個帳戶名稱、編號、存款餘額,
只是多了一個利率 Rate

如果我們又要思考一個 CreditAccount 信用卡帳戶
又要寫一次 帳戶名稱、編號、存款餘額?
於是 '繼承' 的必要就產生了

擴充父類別
建立 SavingAccount.java 的時候擴充父類別
public class SavingAccount extends Account {
}
很厲害的地方就是
父類別所有功能就可以立刻使用了!
public class TestAccount {
 public static void main(String[] args) {
  SavingAccount sa = new SavingAccount ();
  sa.setNumber(101);
  sa.show();
 }
}
於是我們只要把屬性 rate 以及相關法加入即可
並且重新定義方法。
public class SavingAccount extends Account {
 private double rate;

 public void setRate(double rate){
  this.rate = rate;
 }

 public void show() {
  //不用寫同樣的東西了,是不很方便呢
  //System.out.println("Number: " + number);
  //System.out.println("Name: " + name);
  //System.out.println("Balance: " + balance);
  super.show();
  System.out.println("Rate: " + this.rate);
 }
}
所謂的重新定義,是 Account 方法中的 show 和
SavingAccount 中的 show 可能不完全相同
因此借用了部份父類別的 show()
同時增加自己所需要的程式碼

子類別的建構式
//建構式, 還是要有參數
public SavingAccount(int number, String name, double balance,double rate){
 //同樣的也不需要再重新建構一次
 //this.number = number;
 //this.name = name;
 //this.balance = balance;
 super(number, name, balance); //網父類別找, 找到三個參數
 this.rate = rate;
}
加入這行建構式以後
子類別的功能可說是如虎添翼了
SavingAccount sa = new SavingAccount (101,"Simon",200,0.02);
sa.show();
最後執行只需要這樣!
真是太漂亮了

快速建立子類別
信用卡的子程序,繼續繼承前述子程序
public class CreditAccount extends SavingAccount {
 private double limit;

 public CreditAccount(int number, String name, double balance, double rate, double limit){
  super(number, name, balance,rate); //使用父類別的建構式
  this.limit = limit;
 }

 public void show(){
 super.show();
  System.out.println("Rate: " + this.limit);
 }
 
 //信用卡專用的提款
 //使用 private 就可以用父類別的屬性了!

 public boolean withdraw(double amount) {
  if (balance + limit >= amount) {
   balance = balance - amount;
   if (balance < 0 ) {
   this.limit = this.limit + balance;
   }
   System.out.println("提款成功,您提了"+ amount +"元,剩餘額度"+ limit);
   return true;
  }
  else {
   System.out.println("餘款或額度不足,提款未執行");
   return false;
  }
 }
}
執行測試程式
public class TestAccount {
 public static void main(String[] args) {
  CreditAccount ca = new CreditAccount(101,"Simon",200,0.02,30000);
  ca.show();
  //信用卡的餘額很特殊,可以是負的
  //這裡使用的是信用卡 CreditAccount 裡的 withdraw 方法

  ca.withdraw(350);
  ca.show();
 }
}
保護類型
先前提到了 public 和 private 但還有一個重要的 protected,
這是允許子類別使用父類別的屬性。
這樣子類別才可以用前面的 (父類別) 屬性。

因此將 Account.java、SavingAccount.java 還有 CreditAccount.java 裡的
屬性的 private 都改為 protected (因為共用屬性的需要)

子類別的程式雖然少
但功能能夠繼續累積,非常有用!

建構式

撰寫類別 (class) 的時候,通常會設定許多屬性
但如果類別的方法經常要用到這些屬性 (類別變數) 呢?
能不能一次設定好呢?
當然可以…
就是使用建構式了。
//建立建構式
public Account(int number, String name, double balance){
 this.number = number;
 this.name = name;
 this.balance = balance;
}

//建構式也可以多載
public Account(int number, String name){
 this.number = number;
 this.name = name;
}
參見上述範例。

公開與私用
寫 class 的時候要不要公開類別?
通常,屬性要加上 private 避免被其他程式使用;
Account a = new Account(101,"Simon",200);
a.withdraw(1000); //方法有檢查的程式,不會扣成複數
a.show();

a.balance = a.balance - 1000; //外部程式試圖改變屬性的值
a.show();
程式在執行的時候,如果屬性是 "封裝" 的,
就能保護變數不會被執行範圍外的程式,
而扣到不合理的負數。
完整程式如下
//屬性,且被保護
private int number;
private String name;
private double balance;

//建立建構式
public Account(int number, String name, double balance){
 this.number = number;
 this.name = name;
 this.balance = balance;
}

//建構式也可以多載
public Account(int number, String name){
 this.number = number;
 this.name = name;
}

//公開的方法
public void setNumber(int number) {
 this.number = number;
}

public
void deposit(double amount) {
 this.balance = this.balance + amount;
}

public boolean withdraw(double amount) {
 if (this.balance >= amount) {
  this.balance = this.balance - amount;
  System.out.println("提款成功");
  return true;
 }
 else {
  System.out.println("您的餘額不足,提款未執行");
  return false;
 }
}

public void show() {
 System.out.println("Number: " + this.number);
 System.out.println("Name: " + this.name);
 System.out.println("Balance: " + this.balance);
}
於是我們了解什麼是 public 和 private,與其主要功能

2006年12月17日 星期日

自訂方法

方法的重載
設定一個稱為 Util 的 Class
public int summary (int x, int y){
return x+y;
}
public int summary (int x, int y,int z){
return x+y+z;
}
建立一個 testUtil 使用這個 Util
Util u = new Util();
System.out.println(u.summary(2,3));
System.out.println(u.summary(2,3,4));
在 JDK 5.0 以後可以使用不定長度引數。
//自訂一個加總的方法
public double total (double... nums){
double sum =0;
for (int i=0; i < nums.length ;i++ ) {
sum = sum +nums[i];
}
return sum;
}
//注意其中的引數 nums 要放在最後
執行時呼叫該方法
System.out.println(u.total(1,2,3,4,5));
System.out.println(u.total(1,2,3,4,5,6,7,8,9,10));
//分別獲得 15.0 和 55.0
這樣可以用在參數無法確認個數的方法。

自訂類別

自訂類別
建立有相似屬性的物件的類別
在建立物件類別的檔案中不需要 main 的區塊。
//決定這個物件變數
int number;
String name;
double balance;
然後建立一個含有 main 區塊的程式
//建立物件
Account a = new Account();
//設定資料
a.number = 101;
a.name = "Simon";
a.balance = 300;
//顯示資料
System.out.println("number: " + a.number);
System.out.println("name: " + a.name);
System.out.println("blance: " + a.balance);
就可以正確印出資料。
但如果要進行存款、提款的動作,不要在 main 中製作,
要在 method 中進行處理。

方法
定義參數和返回值
//設定存款動作
public void deposit(double amount){
}
//設定提款動作
public boolean withdraw(double amount){
if (balance -amount <0) {
balance = balance-amount;
return true;
}
else{
return false;
}
}
然後再回到主程式。
加上使用方法的程式:
a.deposit(5000);
//顯示資料
System.out.println("number: " + a.number);
System.out.println("name: " + a.name);
System.out.println("blance: " + a.balance)
就會發現存款加上 5,000元了呢。
提款的範例
a.withdraw(5000);
//餘額判斷
if (a.withdraw(5000)) {
System.out.println("number: " + a.number);
System.out.println("name: " + a.name);
System.out.println("blance: " + a.balance);
}
else
{
System.out.println("帳戶不足無法提款");
}
甚至,我們可以把帳號資訊顯示整個作為方法來用。
只要 a.show() 就可以顯示帳號資訊
public void show(){
System.out.println("number: " + number);
System.out.println("name: " + name);
System.out.println("blance: " + balance);
}

陣列物件

陣列物件
陣列也是一種物件。通常用來放置同型態的資料。
如果要放 10 個人的年齡,而要取 10 個變數名稱的話,
那就太麻煩了!

因此,除了用 int 變數名稱 = 數字的這種方法外,
利用一維陣列 (one-dimensional arrays) 把他們整理起來。
int v;
int[] va = new int[5]; //宣告,建立這個物件
System.out.println(va[0]); //會印出陣列的初始值

va[0]=32;
va[1]=33;
va[2]=34;
va[3]=35;
va[4]=36;
for (int i=0 ; i <5 ; i++) {
System.out.println( va[i]);
}
剛建立好陣列的時候,各資料的資料型態會有不同的初始值。
每個資料稱為:元素 (element)。
初始值還可以這樣設定
int[] va= new int[]{32,33,34,35,36};
for (int i=0 ; i < names.length ; i++) {
//存取時不能超過陣列邊界
System.out.println(va[i]);
}
所有的陣列物件都有一個 .length 屬性。
可以善加利用
String[] names = new String[3];
names[0] = "Simon";
names[1] = "Mary";
names[2] = "John";
for (int i=0; i < names.length ; i++){
System.out.println(names[i]);
}
//String 物件的初始值為 null //
利用迴圈印出 3x3的方塊(二維矩陣)
但3x3程式無法適用於不規則陣列。
要改成下列程式:
int[][] ia = {{1,2,3},{4,5,6,7,8},{9,10,11,12}};
for (int i=0; i<ia.length; i++) {
for (int i2=0; i2<ia[i].length; i2++) {
System.out.print(ia[i][i2] + "\t");
}
System.out.println();
}
//其中尤其是 la[i].length 要特別小心
不規則陣列用於行事曆程式
int m=PPJUtil.getInt("Month");
//簡易行事曆程式
String[][] cal = new String[12][];
cal[0] = new String[31];
cal[1] = new String[28];
cal[2] = new String[31];
cal[3] = new String[30];
cal[4] = new String[31];
cal[5] = new String[30];
cal[6] = new String[31];
cal[7] = new String[31];
cal[8] = new String[30];
cal[9] = new String[31];
cal[10] = new String[30];
cal[11] = new String[31];
//行事曆內容
cal[11][16] = "要上Java課"
cal[11][23] = "要上Java課"
//顯示內容
for(int j=0; j<cal[m-1].length; j++) {
if (cal[m-1][j] != null) {
System.out.println((m) + "/" + (j+1) + ":" + cal[m-1][j]);
}
另外有一種foreach 的作法
for (int[] x:ia ) {
for (int y:x ) {
System.out.print(y+ "\t");
}
System.out.println();
}
main 方法中的參數
命令列引數是一個非常特別字串物件陣列
例如操作者下
編譯 javac Test.java
執行 java Test Simon Mary "This is a book"

在 EditPlus 執行過程中可以用 Ctrl-3 來加入命令參數。
for (String s:args) {
System.out.println(s);
}