大家已經學過很多程式語言的觀念,這些觀念多半屬於程式語言的主要特徵,所以在很多程式語言裡頭都可以得到印證。以Java來說,由於發展得很快,而且之前已經有C、C++與C#等語言的基礎,所以各種語法都相當地完備。我們在本章中將以Java程式為主軸,從Java來看一般程式語言的特徵。除此之外,由於程式語言也有一些比較進階的觀念與語法,例如例外處理(exception handling)、並行程式設計(concurrent programming)等,我們可以從Java來了解這些未來可能會派上用場的語法。
-
從Java的語法驗證程式語言的觀念。
-
認識Java程式語言的進階語法。
-
綜合應用Java的語法與程式語言的觀念。
6-1-1 Java類別架構
應用系統中資料之間的關係: 所謂的關聯通常是代表應用系統中資料之間的關係,應用系統的使用者對於這些關係的認知是相當敏銳的。 程式也應該很明確地描述這些關係。 可以透過Java的語法來描述兩種常見的關聯: is-a與 has-a。
關聯的實例
class A extends B { //A是B的子類別
A( ) { } ;
}
class C { //C中有A屬性的成員
A ca;
}
class B {
int anyone;
}
// class A is-a B
// class C has-a A
正確與錯誤的使用實例
public class IsHas {
public static void main(String [ ] args) {
A ma = new A( );
B mb = new B( );
C mc = new C( );
A oaref;
B obref;
C ocref;
oaref = ma;
mc.ca = new A( );
oaref = mc.ca;
obref = mb;
obref = ma;
// oaref = mb; // 這是錯誤的!
}
5-1-1-2內部類別
內部類別(inner class): 類別定義裡頭還可以包含類別定義,這就是所謂的內部類別 (inner class)的觀念。 內部類別算是Java中比較進階的語法。
TestInner.java
class tryInner {
classIn co;
tryInner ( ) {
co = new classIn( );
co.sayHi( );
}
class classIn {
void sayHi( ) {
System.out.println("Hi from classIn!");
}
}
}
public class TestInner {
public static void main(String [ ] args) {
tryInner io = new tryInner( );
}
}
6-1-2 參數的傳送規則
參數傳送的兩種情況: 程式和作業系統之間傳送的程式行參數 (command-line arguments)。 主程式和副程式之間的參數傳送。
C/C++ 中副程式的呼叫: 傳值呼叫 (call by value)。 傳址呼叫 (call by reference)。 從語法上能看出差異,不過常滋生困擾。
Java 傳遞參數的規則: 傳送簡單的變數或基本值以傳值呼叫為參數傳送的方法,所以只是把參數的值傳過去,隨後任何改變不會反映到原呼叫程式的變數中。 傳送物件或陣列時以傳址呼叫的方式來傳送參數,所以參數值的變化會反映到原來呼叫程式對應的變數中。
6-1-3 複雜資料型態的傳送
用簡單的語法來傳送複雜的資料型態可以大幅簡化程式的撰寫。 陣列 (array) 可以算得上是一種複雜的資料型態。 Java中陣列的傳送是用傳址呼叫的方式,所以傳出去的陣列若是有任何數值的改變,原呼叫程式同樣會查覺。
class array_receiver
{
public void compute(int ary[ ])
{
for (int i=0; i<ary.length; i++) {
ary[i] *= ary[i];
}
}
}
public class PassArray {
public static void main(String[ ] args)
{
int myArray[ ] = {1,3,5,7,9};
array_receiver ar = new array_receiver();
for (int i=0; i<myArray.length; i++) {
System.out.println("myArray["+i+"]="+myArray[i]);
}
ar.compute(myArray);
for (int i=0; i<myArray.length; i++) {
System.out.println("now, myArray["+i+"]="+myArray[i]);
}
}
}
myArray[0]=1
myArray[1]=3
myArray[2]=5
myArray[3]=7
myArray[4]=9
now, myArray[0]=1
now, myArray[1]=9
now, myArray[2]=25
now, myArray[3]=49
now, myArray[4]=81
Java 裡頭的有效範圍(scope): 類別層次的範圍 (class-level scope)。 方法層次的範圍 (method-level scope)。 程式片段層次的範圍 (code block-level scope)。
6-1-4 副程式的觀念
思考活動: Java裡頭有沒有副程式 (subroutine)的觀念呢? Java還需要有副程式的觀念嗎? Java裡頭就只有類別,副程式必須以類別方法的型式存在。
ModifiedPassArray.java
class array_receiver {
public void compute(int ary[ ]) {
for (int i=0; i<ary.length; i++) {
ary[i] *= ary[i];
}
}
public void print_array(int ary[ ]) {
for (int i=0; i<ary.length; i++) {
System.out.println("ary["+i+"]="+ary[i]);
}
}
}
public class ModifiedPassArray {
public static void main(String[ ] args) {
int myArray[ ] = {1,3,5,7,9};
array_receiver ar = new array_receiver( );
ar.print_array(myArray);
ar.compute(myArray);
ar.print_array(myArray);
}
}
6-1-5 系統資源的管理
記憶體空間的管理: 記憶體空間是電腦系統裡相當珍貴的資源。 一般程式語言都會提供管理記憶體的語法,例如C 裡頭的malloc、C++的new和 delete,或是Java 的new關鍵字。 基本的原則是:不必再用到的資料可以移出記憶體。
「循環參考」(circular reference): Java 支援自動回收記憶體空間的機制,叫做「垃圾回收」 (garbage collection)。 任何的記憶體空間若是沒有任何物件參考指向它,則Java 不久就會把這塊空間歸還給系統。 在自動回收記憶體空間的機制下,必須避免「循環參考」 (circular reference)。 也就是甲物件有指向乙物件的物件參考,而乙物件同時具有指向甲物件的物件參考。 這時候即使讓任一物件的物件參考值為null,由於指向另外一個物件的參考依然存在,造成記憶體空間無法被回收。
物件參考的意義: Java裡的物件建立以後,就會占用記憶體空間。 物件參考只是一種變數,可指向不同的記憶體空間,所以同一個記憶體空間可能被多個物件參考所指。 到底一個物件存在多久,得看這些物件參考何時變成null,否則就得等到程式結束才會全部清空。
ReturnArray.java
class Array_Creator {
public int[ ] create_an_array( ) {
int ary[ ]={1,3,5,7,9};
return ary;
}
}
public class ReturnArray {
public static void main(String [ ] args) {
Array_Creator ac = new Array_Creator( );
int ary[ ]=ac.create_an_array( );
for (int i=0; i<ary.length; i++) {
System.out.println("ary["+i+"]="+ary[i]);
}
}
}
6-2-1 例外處理基本觀念
程式錯誤的型態: 與系統有關的錯誤:例如記憶體不足、檔案無法開啟等問題,通常程式會終止,看不到有意義的結果。 和程式編輯有關的錯誤:此時程式可能有輸出結果,但卻不是正確的,代表程式本身的邏輯就有問題。
例外處理 (Exception handling)基本的用法:
6-2-2 Java例外處理的實例
Finally的子句: Finally後面的區塊一定會被執行。 當例外發生以後,try區塊中有些程式碼不會被執行到,可能有一些必須執行的動作,例如關閉檔案等,會被略過,這時候就可以把這些程式碼放到finally的程式區塊中。 Finally必須放到最後一個catch區塊之後,這樣才能保證裡頭的程式碼一定會被執行。
TestMyException.java
class myException extends Exception {
myException(String reportMsg) {
super(reportMsg);
}
}
class TestMyException {
public static void main(String args[ ]) {
int x=50, y=7;
try {
if (x/y < 10) throw new myException(x/y + "<10" + "give up!");
}
catch (myException e) {
System.out.println("x/y is smaller than 10!");
}
finally {
System.out.println("finally中的程式碼一定會被執行!");
}
}
}
系統內定的例外
6-3 基本觀念
多工(multitasking): 電腦系統常利用多工 (multitasking) 的方式來提昇效率,主要是讓電腦的資源時常保持忙碌,常用的方法是同時執行數個工作。 程式 (program) 儲存在媒體上,執行時才會載入電腦裡,執行緒可以看成是輕量級的執行程式,通常都比較簡短。 從多工的觀點來,執行緒能達到的效果比程序來得好,因為簡短的執行緒在安排執行時彈性比較大。 執行緒之間共享位址空間 (address space)、執行緒執行時的切換 (context switching) 比較不費事,而且執行緒之間的溝通很方便。
執行緒的特性: 共享記憶體。 同時執行。
執行緒的種類: 使用者執行緒(user threads):執行Java應用程式的時候,系統會產生一個使用者執行緒,負責執行應用程式中的main( )方法,我們也把這個執行緒稱做「主執行緒」(main thread),主執行緒還可以繼續產生所謂的「子執行緒」(child thread)。 常駐執行緒(daemon threads):屬於系統產生的執行緒,常駐行在應用程式執行的過程中一直都存在,等所有的使用者執行緒都結束後,常駐執行緒才會停止執行。
在Java 程式裡使用執行緒的方法: 實作Runnable介面:Runnable介面裡有一個run( ) 方法,實作Runnable介面的意思就是把run( ) 方法內含的程式碼寫出來,然後在類別定義的建構子中產生Thread物件,啟動Thread物件的start( ) 方法。 這個子執行緒會自動執行run( ) 方法。 繼承Thread類別:直接定義成Thread類別的子類別,同時也提供run( ) 方法的程式碼,然後執行start( ) 方法,和實作Runnable介面的方式很類似。
6-3-1-1 多執行緒程式設計的優點
執行緒的組成: 執行緒的識別碼 (thread ID) 、程式計數器 (program counter)、 暫存器組 (register set) 、堆疊 (stack)
多執行緒程式設計的優點: 資源的共用。 提升系統回應的效率。 整體效能的提升。
6-3-1-2 執行緒狀態的變化
傳統的處理元可以看成僅有單一的執行緒,常稱為heavyweight process或single threaded process, 具有多執行緒的處理元(multi-threaded process)可同時進行多項工作,等於是讓各執行緒分頭進行,同屬於一個處理元的執行緒會共用程式碼、資料與一些作業系統的資源。
1. new的狀態: 使用new關鍵字建立執行緒物件之後就進入了new的狀態。
2. runnable的狀態: 呼叫start( )方法會促使記憶體空間分配給該執行緒,同時執行run( )方法進入runnable的狀態,正在執行的或是等待執行的執行緒都是處在runnable的狀態。
3. dead的狀態: run( )方法執行結束或是呼叫stop( )方法會使執行緒進入dead(即結束)的狀態。
4. blocked的狀態: 執行中的執行緒會因I/O, 呼叫sleep( )或呼叫suspend( )方法而進入blocked(即中止)的狀態,但可呼叫resume( )方法回到runnable的狀態。
6-3-1-3 在 Java中建立執行緒
在Java 中建立執行緒: 定義Thread類別的子類別: Java在 Thread類別中定義了很多和執行緒相關的方法,只要繼承了這些方法,就可以對執行緒進行一些處理。 實作Runnable介面: 實作Runnable介面,在建構子中建立執行緒,呼叫start( )方法,然後把執行緒的真正功能放在 run( )方法中。
用Thread 子類別的方式建立執行緒
class ThreadBySubclassing extends Thread {
ThreadBySubclassing( ) {
start( ); //標準的建構子啟始碼
}
public void run( ) // thread建立後執行的程式碼 {
System.out.println("A thread is created!");
}
}
public class MainPgm1 {
public static void main(String args[ ]) {
Thread t = Thread.currentThread( ); System.out.println("Current thread name is " + t.getName( )); //建立並啟始thread
ThreadBySubclassing nt = new ThreadBySubclassing( ); System.out.println("Thread name is " + nt.getName( )); nt.setName("NewName");
System.out.println("Thread name has been changed to " + nt.getName( ));
}
}
實作Runnable 介面來建立執行緒
class ThreadWithRunnable implements Runnable {
Thread thread; ThreadWithRunnable( ) { //標準的建構子啟始碼 thread = new Thread(this,"ThreadName");
thread.start( );
}
public void run( ) // thread建立後執行的程式碼 {
System.out.println("A thread is created!");
}
}
public class MainPgm2 {
public static void main(String args[ ]) { //建立並啟始thread
ThreadWithRunnable nt = new ThreadWithRunnable( );
System.out.println("Thread name is " + nt.thread.getName( ));
}
}
6-3-1-4 Java執行緒中常用的方法
Java 執行緒中常用的方法: sleep( )方法: 強迫執行緒進入中止的狀態,裡頭的參數代表中止的時間,以千分之一秒為單位。 SetPriority( )方法: Java執行緒具有執行上的優先順序(priority),最高是10,最低為1,正常的為5。 由於作業系統才是真正的主宰,所以不見得完全如願。 Join( )方法: 等待某個執行緒結束執行。
MainPgm3.java
// 執行緒優先順序的設定
class MyThread extends Thread {
MyThread(String TName) {
super(TName); start();
}
public void run() {
try {
System.out.println("目前的執行緒: " + (Thread.currentThread()).getName());
Thread.sleep(1000);
}
catch(InterruptedException e) { } System.out.println((Thread.currentThread()).getName() +
"執行緒結束");
}
}
public class MainPgm3 {
public static void main(String args[]) {
MyThread t1 = new MyThread("thread-1");
MyThread t2 = new MyThread("thread-2");
MyThread t3 = new MyThread("thread-3");
t1.setPriority(1);
執行時期的行為
6-3-2 共用程式碼與同時性控制
同時性控制(synchronization): 由於Java 執行緒共用程式碼與位址空間,在某些不能共用的情況下,我們必須限制執行緒的行為。 例如執行緒甲預期對A帳戶加入存款100 元,假如執行緒乙恰巧也同時將A帳戶扣除100 元,則正在執行中的執行緒甲所看到的結果是帳戶餘額不增不減,這是不合理的。 解決的辦法是每次只讓一個執行緒對相同的帳戶進行處理,處理完後才可以由其他執行緒來用。這就叫做同時性的控制。
程式方塊的同時性控制(I)
class MainPgm4 {
public static void main(String args[ ]) {
CommonArea common = new CommonArea();
MyThread thread1 = new MyThread(common,"執行緒甲");
MyThread thread2 = new MyThread(common,"執行緒乙");
MyThread thread3 = new MyThread(common,"執行緒丙");
try {
thread1.join( );
thread2.join( );
thread3.join( );
}
catch(InterruptedException e) { }
}
}
程式方塊的同時性控制(II)
class MyThread extends Thread {
CommonArea CA;
public MyThread(CommonArea CA, String string) {
super(string);
this.CA = CA;
start( );
}
public void run( ) {
synchronized(CA) {
CA.SharedCodeBlock(Thread.currentThread( ).getName( ));
}
}
}
程式方塊的同時性控制(II)
class CommonArea {
void SharedCodeBlock(String string) {
System.out.println("開始進行的執行緒 : "+string);
try {
Thread.sleep((long)(Math.random()*500));
} catch (InterruptedException e) { }
System.out.println("結束的執行緒 : "+string);
}
}
6-3-3 執行緒之間的溝通
執行緒之間的溝通(I)
class Common {
int signal=0;
synchronized void SharedCodeBlock( ) {
try {
Thread.sleep(1000);
} catch(InterruptedException e) { }
signal=1;
notify( );
}
synchronized int getResult() {
try {
wait( ); } catch(InterruptedException e) { }
return signal;
}
}
執行緒之間的溝通(II)
class MyThread1 extends Thread {
Common Common;
public MyThread1(Common Common, String string) {
super(string);
this.Common=Common;
start( );
}
public void run( ) {
System.out.println("結果為: "+Common.getResult());
}
}
執行緒之間的溝通(II)
class MyThread2 extends Thread {
Common Common;
public MyThread2(Common Common, String string) {
super(string);
this.Common=Common;
start( );
}
public void run( ) {
Common.SharedCodeBlock( );
}
}
執行緒之間的溝通(III)
class WaitNotify {
public static void main(String args[ ]) {
Common Common=new Common( ); MyThread1 thread1=new MyThread1(Common,"one"); MyThread2 thread2=new MyThread2(Common,"two");
}
}
執行WaitNotify.java 的結果
重點整理: 1. Java類別架構衍生出來的語法。2. 副程式與資料的傳遞。3. 資源管理。4. 例外處理。5. Java執行緒(thread)。