字體:小 中 大 | |
|
|
2021/06/06 16:45:11瀏覽980|回應0|推薦0 | |
大家已經學過很多程式語言的觀念,這些觀念多半屬於程式語言的主要特徵,所以在很多程式語言裡頭都可以得到印證。以Java來說,由於發展得很快,而且之前已經有C、C++與C#等語言的基礎,所以各種語法都相當地完備。我們在本章中將以Java程式為主軸,從Java來看一般程式語言的特徵。除此之外,由於程式語言也有一些比較進階的觀念與語法,例如例外處理(exception handling)、並行程式設計(concurrent programming)等,我們可以從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)。 |
|
( 知識學習|隨堂筆記 ) |