Java/Java slide

From YuntechWiki

Jump to: navigation, search

Contents

簡介

這份講義嘗試用精簡的方式,將學習學習者用於閱讀厚重電腦書的時間,轉換成使用 plweb 以learn by coding 的方式學習的時間。如果某部分的內容需要更詳細的說明,則可以用 google 查找許多熱心人士提供的教材,例如:

另外,教師也可以把這份講義當成不分頁的投影片使用(因為用分頁式的投影片解釋很長的程式碼,很不方便)。也可以在上機考試時,讓學生將這份講義帶進考場,以避免記憶許多語法的細節。

關於程式設計的基本概念

  • 電腦程式的主要功用是處理資料,所以 code + data = program;
  • 如同說話需要使用名詞、動詞等,程式中的 code 與 data 也需要名字,讓程式設計師可以寫程式中的句子;
  • 存放 data 的名字通常叫變數(variable)。變數常有一對應的型態(type),例如:學號應該是整數,名字應該是字串(String)。
  • 存放 code 的名字通常叫方法(method)。而有的程式語言叫函式(function)或程序(procedure)。
  • 方法也有對應的型態,由於方法是用於處理資料,與回傳結果,所以它需要指定數入的資料的型態,與輸出的結果的型態。例如:computeRectangelArea這個計算長方形面積的方法,便需要傳入長與寬,並輸出面積。而長、寬與面積的型態都可以是 int (整數);
  • 編譯器(compiler)的功用是將程式設計師寫的程式,轉換成電腦可以執行的低階程式碼;
  • 程式設計師的程式除了自己寫的部分外,還會用到常被使用的 library 中的程式。
  • 這個為 Java 預備的 library、編譯器與執行環境叫做 JDK(Java Development Kit)。

安裝JDK(Java Development Kit)

  • 可以從這裡下載新版的JDK
  • 為了讓作業系統能知道,所安裝的 JDK 存放在電腦的那個目錄,所以需要設定作業系統的 path 及 classpath,例如 JDK 1.6.0 版的 path 與 classpath 的設定值是:

第一個Java程式與程式執行的起始點

  • Java 程式是由一或多個類別(class)所組成;
  • 類別是 Java 程式的基本單元,一個小的程式只需要一個類別;一個大的程式可能有許多的類別;
  • 其中的一個類別至少要有一個命名為 public static void main 的方法(method);
  • 而這個類別必須是 public 類別(public 類別容許其他類別使這這個類別的變數或方法);
  • 程式由 main 開始執行( 在瀏覽器執行的 Java applet 不適用此規則);
  • main 所屬檔案的名稱(例如:Hello.java),必須與 main 所屬的 class 的名稱(例如:public class Hello {...})一樣。

JVM

為了讓以 Java 撰寫的程式有跨平台的優點,而能夠不需修改的在不同的電腦硬體上執行, Java 選用了一個與傳統的程式語言不一樣的執行模式:

  • 傳統的程式語言在編譯後會產生 machince code(機器碼),然後直接在硬體上執行;
  • Java 在編譯後則會產生 Byte Code 並間接的在 Java Virtual Machine(JVM)上執行。

JVM 實際上是一個瞭解如何解譯與執行 Byte Code 的軟體;因此只要為不同的電腦硬體,分別安裝不同的 JVM 即可達到跨平台的目的。這便是「Write once, runs everywhere或一次編譯、到處執行」的由來。

Java的特色

  • 支援結構化程式設計與物件導向程式設計;
  • 多執行緒:讓一個程式可以執行數個工作;
  • 例外狀態處理:讓處理例外的程式碼也能夠物件化;
  • 自動記憶體回收:讓程式設計師免除了使用低階的指標(pointers)來設計資料結構及管理記憶體的負擔;這個特色成了 C 語言程式設計師的福音,因為它可以為程式設計師減少許多不容易 debug 的錯誤。

編譯及執行Java程式

可以使用程式開發環境(program development environment),例如:Eclipse;或是使用一般的程式碼編輯器。以下是使用「記事本」寫 Java 程式時所需要進行的三個步驟:

  1. 使用「記事本」輸入以下的程式並將檔案命名為 EnglishExam.java(注意:附檔名必須是 .java 而不是 .txt,而這個檔案的主檔名必須與 public class 後面的 EnglishExam 相同):
    public class EnglishExam {                     
    public static void main(String argv[]) {
    System.out.println("Your score is 97.");
    }
    }
  2. 執行「命令提示字元」並將目錄切換至儲存 EnglishExam.java 的目錄,然後執行:
    javac EnglishExam.java
    執行這個 javac 指令就是執行 Java 的編譯器(compiler),其結果是在同樣的目錄產生一個 EnglishExam.class 的 byte code 檔。
  3. 上面的步驟如果有編譯錯誤則繼續修改程式。如果沒有編譯錯誤則可以執行:
    java EnglishExam
  4. 執行後 Your score is 97便會顯示在螢幕上。

命名規則

  • 類別名稱的第一個字母要用大寫
  • 方法或變數的第一個字母則是小寫
  • 若有數個字合併時則後續的字的第一個字母也習慣用大寫。例如:aCar, employAccount。

程式錯誤

  • 編譯錯誤(compile-time error):程式的語法有錯誤。這部分的錯誤,學習者可以自行將一個正確的程式,以刪除某個字元(例如:「;」、「}」),將拼錯某個變數、方法或保留字(程式語言的語法用字,例如:public, void, while, if, double等),然後編譯、看錯誤信息,藉以學習經驗;
  • 執行錯誤(run-time error):程式的邏輯有錯誤;
  • 都需要使用編輯器修改、編譯、執行、除錯,直到沒有錯誤為止。

範例解說

在解說這個範例之前,需要知道關於類別的更多概念。Java 程式的類別,有的可以產生物件,有的並不能產生物件。

所謂物件,是物件導向程式語言的設計者,為了讓程式設計師,方便的以我們日常生活中常用的「物件」概念寫程式而有的。人類的日常生活有許多「物件」(object),或稱作「實例」(instance),例如:考卷,一份考卷有姓名、考題、分數等等屬性,也需要計算分數的「方法」,而一個讓個別考生都得到一份考卷的「類別」,便需要規定如何為每個考生產一份包含了:name, questions, score等實例變數,以及如何計算分數的「實例方法」的設定。這種用來設定產生實例的變數或方法,稱做實例變數或實例方法。

然而有的變數與方法,與實例並沒有直接的關連,例如:這次考試產生了多少份考卷的概念,便不隸屬於任一實例,而是這個「考試類別」的概念,而定義這種不隸屬於實例的變數,稱作「類別變數」,不隸屬於實例的方法則稱作「類別方法」,例如:getExamCount(傳回共產生了多少份考卷物件)。

public class EnglishExam {                     
public static void main(String argv[]) {
System.out.println("Your score is 97.");
}
}
  • public class EnglishExam 指定 EnglishExam 這個類別是 public 是公用的,也就是可以被程式中其他的類別引用
  • public static void main(String argv[]) 的意義是:
    1. public:指定 main 為一個可以被其他類別使用的 public method;
    2. static:指定 main 為一個類別方法(static method);
    3. void:代表 main 執行完畢後回傳的型態,因為 main 沒有回傳任何數值,因此它的回傳型態是 void;
    4. String argv[]:指這個方法的輸入參數是 argv[] 而 String 則是它的型態
    5. main 的輸入參數 String argv[] 可以在執行一個 Java 程式時將字串(String)資料輸入這個程式。例如在編譯以下的程式之後:
      public class HelloJava {                     
      public static void main(String argv[]) {
      System.out.println("Hello " + argv[0] + argv[1]);
      }
      }
      以「命令提示字元」執行:
      java HelloJava Basic C++
      便會呼叫 System.out.println 並輸出:
      Hello BasicC++

程式區塊

  • Java 程式中用大刮號 { } 標示的 Block(區塊)是用來組織程式層次關係的語法
  • 上例的程式有兩個區塊
    1. 一組用來標示 class 的區塊
    2. 另一組用來標示 main 的區域
  • 區塊中可以包含其他的區塊
  • 在撰寫程式時也應注意要把區塊的內容往右縮排
  • 一組用來標示類別的區塊內,可以有數個變數與方法
  • 一組用來標示方法的區塊內可以有一或多句以「;」結束的程式碼。這些程式碼共同構成了這個方法的 body。

Java 的變數、方法與型態:總覽

  • 電腦程式的組成有兩部分:
  1. data:這部分要為所處理的資料命名、指定其型態、並存放於變數所在的記憶體中;
  2. code:這部分的程式碼要使用撰寫方法(method)的運算式、條件判斷式、迴圈,來存取資料、計算結果、然後輸出。

如何取名字

  • 程式語言的句子與一般說話的語言一樣是由基本的字詞(names)組合而成。
  • Java 的命名規則:一個字詞可以包含一個或多個英文字母、數字、_ 及 $ 所組成的字元,而第一個字元不可以是數字。
  • Java的保留字有 public, class, void, if, while, for 等保留字

變數

  • 一個變數包括:
    1. 一個名字(name)
    2. 一個資料值(value)
    3. 一塊儲存資料值的記憶體
    4. 以及這個資料值的型態(type)(如 int、double 等)
  • 一個變數的型態,定義了這個變數的值所需要的記憶體的大小
  • 在執行時,便會配置適當大小的記憶體空間,以存放這些變數的值

Java變數的種類

Java 有三種變數:

  1. 區域變數(local variable):宣告在方法的參數部分與宣告在方法內的變數;
  2. 類別變數(class variable or static field):在一個類別中以 static 宣告的變數;
  3. 實例變數(instance variable or non-static field):在一個類別中沒有使用 static 宣告的變數。這部分會在「物件導向程式設計」的部分詳細介紹。

Java的方法

  1. 類別方法(class method or static method):這種方法以 static 宣告,隸屬於類別。呼叫的方式是 C.m,其中 C 是類別名稱,m 是方法名稱。
  2. 實例方法(instance method or non-static method):這種方法不以 static 宣告。這部分也會在「物件導向程式設計」的部分詳細介紹。

何時使用類別方法或實例方法

  • Java 同時支援結構化(例如:C 與 Basic)與物件導向程式設計
  • 結構化程式使用類別變數、類別方法與在類別方法中使用的區域變數。
    • 類別變數在概念上與一般程式語言的全域變數(global variable)一致;而類別方法在概念上則與一般程式語言的函式(function)或程序(procedure)一致。
  • 物件導向程式設計使用實例變數、實例方法與在實例方法中使用的區域變數
  • Java 程式可以同時使用結構化與物件導向並存的方式設計程式。

Java的型態

Java 變數的型態有兩大類:

  1. primitive type,包括:int、double、boolean、char等。
  2. reference type,包括:
    1. 類別型態:經由類別(class)的宣告而得到。如果 Car 是一個類別,而 aCar 是一個這個類別的變數,則 Car 便是 aCar 的型態(type)。之所以稱為 reference type,是因為 aCar 這個變數在記憶體中的位置,實際上是存著指向(reference)一個 Car 實例的地址。
    2. 介面型態:經由介面(interface)的宣告而得到。
    3. 陣列(array)型態。
    4. enum 型態:一種特別的類別宣告方式,用於宣告月份、一週的七天等。
    5. 以上幾種 reference type, 會在以後的單元陸續的介紹。

區域變數與基本資料型態

區域變數是一個方法的參數或是宣告在一個方法的區塊中。以下是宣告區域變數的幾個範例,其中 int 代表整數,而 double 代表倍精準浮點數,宣告的意義是告訴編譯器一個變數的型態是什麼:

public class EnglishExam {                     
  public static void main(String argv[]) {
    // argv 是傳入參數,同時也是一種區域變數 

    // 宣告三個區域變數
    int vocabulary;
    int grammar;
    int listening;
    ...
  }                                        
}

public class EnglishExam {                     
  public static void main(String argv[]) {
    // 將以上三個區域變數,合併在一行
    int vocabulary, grammar, listening;
    ...
  }                                        
}

public class EnglishExam {                     
  public static void main(String argv[]) {
    // 在宣告時也將數值存入這三個區域變數中
    int vocabulary = 24;
    int grammar = 26;
    int listening = 33;
    ...
  }                                        
}

public class EnglishExam {                     
  public static void main(String argv[]) {
    // 也可以這樣寫:
    double vocabulary = 22.5, grammar = 25.4, listening = 32.0;
    ...
  }                                        
}

有了變數之後,可以使用設值運算符號(=),將數值存入區域變數中,如果一個區域變數尚未被存入數值,則其預設值(default value)會被存入,而數字的預設值是 0。例如:

public class EnglishExam {                     
  public static void main(String argv[]) {
    int vocabulary, grammar, listening;
    int score;
   
    vocabulary = 22;
    grammar = 26;
    score = vocabulary + grammar + listening;
    System.out.print("The score of the exam is ");
    System.out.println(score);  
    // listening 的預設值是0, 所以印出 48
  }                                        
}

Java的註解是以:

// 這是註解
/*
這也是註解
這還是註解
*/

來標示。

螢幕輸出及鍵盤輸入

螢幕輸出有幾種方式。第一種是前面章節已經使用過的 System.out.print 及 System.out.println。這兩種方法的的差別是前者沒有換行,而後者有換行。如果有數個資料需要一起印出時,則可以使用 + 進行串接。例如:

public class Show {                      
  public static void main(String argv[]) {
    int design, acting;    

    design = 3;
    acting = 5;
    System.out.println("Design is " + design + "and acting is " + acting);
  }
}

第二種方式是在 J2SDK5 之後才支援的 System.out.printf。這個方式與 C 語言的 printf 功能類似。例如:

System.out.printf("Today is %s, %d.\n", "January", 18);
// %s 的位置替換成 January 這個 String
// %d 的位置替換成 18 這個整數 
// \n 表示換行符號

顯示:

Today is January, 18
double score = 92.345
System.out.printf("My score is %.2f.\n", score);
// %.2f 的意義是小數點以下取兩位,並四捨五入。
System.out.printf("My score is %6.2f.%n", score);
// %6.2f 的意義是:包括小數點共6位,小數點以下取兩位,
// 並四捨五入。所以9的左邊多空一格。

顯示:

My score is 92.35.
My score is  92.35.

鍵盤輸入則可以透過 java.util.Scanner。例如:

// 使用時先載入 Scanner 所屬的 package
import java.util.*; // * 的意義是 java.util 內所有的類別
 
// 定義物件:
Scanner scanner = new Scanner(System.in);
 
// 輸入字串:
String name = scanner.nextLine();
 
// 輸入整數:
int score = scanner.nextInt();
 
// 輸入double
double height = scanner.nextDouble();
 
// 輸入float
float weight = scanner.nextFloat();

以下是一個完整的範例:

import java.util.*;  
 
public class EnglishExam {
public static void main(String argv[]) {
int vocab, grammar, listen, score;
 
Scanner scanner = new Scanner(System.in);
String name = scanner.nextLine();
vocab = scanner.nextInt();
grammar = scanner.nextInt();
listen = scanner.nextInt();
score = vocab + grammar + listen;
System.out.printf("The total score of %s is %d.%n", name, score);
}
}

算術運算式

使用算術運算式要注意的是運算的優先次序。例如:先乘除、後加減。如果不記得運算的優先次序,那麼最簡便的方法是使用()也就是指定內層的刮號先執行。以下是算術運算式的幾個範例:

2 + 3         // 傳回 5
4 - 1 // 傳回 3
(4 + 5) * -3 // 傳回 -27
x * y // 傳回 x 的值與 y 的值相乘的結果
y / z // 傳回 y 的值與 z 的值相除的結果
7 % 4 // 傳回兩數相除的餘數 3

使用算術運算式另一項要注意的是型態的轉換,也就是在一個算術式中同時有整數與浮點數時,Java會將整數先轉換成浮點數然後再進行運算。

例如:

int i = (3 + 4) / 3 // 兩個整數相除,結果仍是整數,小數部分捨棄。傳回 2
double f;
f = (3 + 4) / 3.0   // 7 轉型成浮點數,然後與浮點數 3.0 相除。傳回 2.333...
System.out.println(i);
System.out.println(f);

印出:

2
2.3333333333333335

如果需要指定轉換的型態,則可以使用以下的方式:

(double) i    // 得到 i 的 double 的值
(int) f // 得到 f 的 int 的值

至於 i、f 的原來的值還是不變。

如同一般的算術運算式,設值運算式(=)也會傳回值。例如:

x = 3         // 傳回 3
y = (x = 3) // 因為 x = 3 傳回3, 所以 y 的值也設成 3

類別變數與類別方法

非物件導向程式語言(例如:C),程式設計師主要是使用函式(function)、全域變數與區域變數將一個大的程式分割成幾個小的部份,以簡化程式的撰寫。這些觀念在 Java 中仍然可以使用,而使用的方式是透過類別變數與類別方法。

舉例而言,如果要為英文檢定考試寫一個計算成績的程式,那麼這個程式應該有一個計算成績的方法。例如:

public class EnglishExam {
 
  public static int englishScore(int v, int g, int l) {
    return v + g + l;
  }

  public static void main(String argv[]) {
    System.out.print("The score of the exam is ");
    System.out.println(englishScore(24, 27, 32));
  }
}

public static int englishScore(int v, int g, int l) 定義了 englishScore 這個類別方法,這個方法有三個命名為 v, g, l 的輸入參數,它們的型態都是 int。這個方法的輸出型態也是 int,也就是會使用 return 傳出一個 int 的值 (v + g + l)。Java 使用 static 這個保留字來定義類別方法。因為這種方法是靜態的,也就是在程式執行時,呼叫這個方法的程式碼,一定都會執行相同的方法。

在 main 中的 System.out.println(englishScore(24, 27, 32)) 將 24, 27, 32 傳入 englishScore 中,並依序成為 v, g, l 三個輸入參數的值,而這三個數相加的結果 83 會繼續的傳入 System.out.println,然後顯示在螢幕上。一個方法的輸入參數也是那個方法的區域變數。所以 v, g, l 三個輸入參數也是 englishScore 的區域變數。

除了直接將數值傳入方法中以外,還可以將變數或其他也有傳回值的式子,寫在方法呼叫中傳入的位置。例如:

public class EnglishExam {
   
  public static int englishScore(int v, int g, int l) {
    return v + g + l;
  }

  public static void main(String argv[]) {
    int a = 3, b = 4;
    System.out.print("The score of the exam is ");
    System.out.println(englishScore(a, b, a + b));
  }
}

Java 會在得到 a, b, a + b 的數值後,才將 3, 4, 7 傳入 englishScore 中。也就是先得到數值再傳入,然後 v, g, l 便使用傳入的數值生成三個區域變數。這個特性稱為 call-by-value 或傳值呼叫。v, g, l 三個參數之所以也是區域變數,因為這三個變數的可見範圍(scope)只包含 englishScore 的區塊。

如果一個方法沒有傳回值,那麼這個方法的輸出型態便是 void。例如:

public class EnglishExam {
  public static void displayScore(int v, int g, int l) {
    System.out.print("The score of the exam is ");    
    System.out.println(v + g + l);
  }

  public static void main(String argv[]) {
    displayScore(24, 27, 32);
  }
}

displayScore 這個方法將字串顯示在螢幕上,不需要傳回值,因此它的輸出型態是宣告成 void,而 main 的輸出型態也是 void。

不同的類別中也可以定義同名的方法。這個功能稱做 overloading。而Java 是以
類別名稱.方法名稱(0或多個參數);
呼叫宣告在不同類別的類別方法。例如:
public class Exam {
  public static void main(String argv[]) {
    int voc = 3, grammmar = 7, listen = 8;
    System.out.print("The score of the english exam is ");    
    System.out.println(EnglishExam.displayScore(voc, grammar, listen));
    System.out.print("The score of simple english exam is ");    
    System.out.println(SimpleEnglishExam.displayScore(voc, grammar, listen));
  }
}

class EnglishExam {  
  public static int displayScore(int v, int g, int l) {  
    return v + g + l;
  }
}

class SimpleEnglishExam {
  public static int displayScore(int v, int g, int l) {  
    return v + g + 0;
  }
}

一個類別中也可以有同名的方法,但是他們必須有不同的輸出入型態。例如:

public class Exam {
  public static void main(String argv[]) {
    int voc = 3, grammmar = 7, listen = 8;
    System.out.print("The int score of the exam is ");   
    System.out.println(EnglishExam.displayScore(3, 7, 8));
    System.out.print("The double score of the exam is ");    
    System.out.println(EnglishExam.displayScore(3.0, 8.0, 7.0));
  }
}

class EnglishExam {  
  public static int displayScore(int v, int g, int l) {  
    return v + g + l;
  }
  public static double displayScore(double v, double g, double l) {  
    return v + g + l;
  }
}

另一個 overloading 的例子是:+。+可以用來將數字相加,也可以將字串合併。例如:

int a = 4, b = 5;
System.out.print(3 + a + b);   // 印出 12

String a = "xy", b = "Z";
System.out.print("3" + a + b); //印出 3xyz

使用類別方法在程式中有許多好處:

  1. 增加程式碼的再用性:同樣的計算步驟,只需要透過呼叫類別方法便可以重複使用。
  2. 讓程式碼的細節,被隱藏在類別方法中:程式設計師在完成類別方法的撰寫後,便只需要知道那個類別方法的輸入、輸出與功用即可,而不用擔心執行的細節。
  3. 容易除錯:除錯的過程可以一個類別方法、一個類別方法的進行,容易找出錯誤的根源。
  4. 容易擴充類別方法內程式碼的功能:只要在類別方法內擴充其功能,而不用在每次呼叫時都重複的擴充。例如以下的程式碼擴充了成績的計算方式,所有 displayScore 的呼叫的計算結果都同步改變:
class SimpleEnglishExam {
  public static int displayScore(int v, int g, int l) {  
    return v * 0.3 + g * 0.3 + l * 0.4;
  }
}

除了使用 static 宣告類別方法外,還有也是使用 static 宣告的類別變數。以下是一個在程式中內建三筆考試成績的資料,呼叫 displayScore 計算成績後,將三筆資料加總並存入 total 這個類別變數中的範例:

public class Exam {
  public static int total = 0;

  public static void main(String argv[]) {
    total = displayScore(3, 4, 5);
    total = total + displayScore(4, 5, 6);
    total = total + displayScore(1, 2, 3);
    System.out.print("The total score is ");    
    System.out.println(total));
  }

  public static int displayScore(int v, int g, int l) {  
    return v + g + l;
  }
}
程式設計師也可以使用不是定義在自己類別中的類別變數,而 Java 是以
類別名稱.變數名稱
使用定義在其他類別中的類別變數。以下便是一種將 total 宣告在另一個類別 EnglishExam 中的寫法是:
public class Exam {
  public static void main(String argv[]) {
    EnglishExam.computeScore(3, 4, 5);
    EnglishExam.computeScore(4, 5, 6);
    EnglishExam.computeScore(1, 2, 3);
    System.out.print("The total score is ");    
    System.out.println(EnglishExam.total));
  }
}

class EnglishExam { 
  public static int total = 0;

  public static void computeScore(int v, int g, int l) { 
    total = total + (v + g + l);
  }
}

以下則是一個為考試成績的計算,加入權重的範例。在這個範例中是以 Exam.wV, Exam.wG, Exam.wL 來使用這三個類別變數:

public class Exam {

  public static double wV = 0.3, wG = 0.3, wL = 0.4;

  public static void main(String argv[]) {
    int voc = 3, grammmar = 7, listen = 8;
    System.out.print("The score of the english exam is ");    
    System.out.println(EnglishExam.displayScore(voc, grammar, listen));
    System.out.print("The score of simple english exam is ");    
    System.out.println(SimpleEnglishExam.displayScore(voc, grammar, listen));
  }
}

class EnglishExam {  
  public static double displayScore(int v, int g, int l) {  
    return v * Exam.wV + g * Exam.wG + l * Exam.wL;
  }
}

class SimpleEnglishExam {
  public static double displayScore(int v, int g, int l) {  
    return v * Exam.wV + g * Exam.wG + 0;
  }
}

類別變數與區域變數,在變數的可用「區域」與存在的「時間」上都不相同。類別變數若是定義為 public,則它的可用區域便包括整個程式,而且在整個程式執行時都存在。區域變數則是在程式執行到一個區塊或方法內時,那個區塊或方法的區域變數才存在,一旦離開那個區塊或方法,便消失了。因此區域變數的可用區域,只在定義該區域變數的區塊或方法內。

以下是一個「計算蛋與水果總價」的程式及其執行過程的動畫:

class Market {
    static int sEgg = 5, sFruit = 20;
    static int getMoney(int nEgg, int nFruit) {
        return sEgg * nEgg + sFruit * nFruit;
    }
} 
public class Ex1 {
    public static void main(String argv[]) {
        int egg = 20, fruit = 30;
        System.out.print("Money:");
        System.out.println(Market.getMoney(egg, fruit));
    }
}

執行結果:

Money:700

觀看執行過程

運算式、句子與條件判斷句

運算式(expression)執行完畢會後傳回一個值;句子(statement)在執行之後,則不回傳值。一個運算式與句子在執行時都可能會改變電腦的狀態,例如:更改變數、陣列或檔案的內容。算術運算式已經介紹了,而最簡單的句子,包括:

  • 句子式(expression statement):在運算式後加一個 ";" 。
  • 區塊句(block statement):在 { } 內可以有 0 或多個變數宣告或句子。

這兩種句子,也都已經看過範例。這個單元將介紹關係運算式、邏輯運算式及 if、switch等句子。

關係運算式

以下是在寫關係運算式(relational expression)時常常會使用到的關係運算子(relational operator),整理如下:

關係運算子 名稱 範例1(=True) 範例2(=False)
== 等於 6 == 6 6 == 4
!= 不等於 6 != 4 6 != 6
> 大於 6 > 4 4 < 6
>= 大於等於 6 >= 6 4 >= 6
< 小於 4 < 6 6 < 4
<= 小於等於 6 <= 6 6 >= 4
  • 需注意關係運算式使用的 "==" ,和寫設值運算式(assignment expression)的 "=" 是不一樣的。


邏輯運算式

Java 常用的邏輯運算子(logical operators)有三個,分別是 && (AND)、|| (OR)、! (NOT)。邏輯運算式使用邏輯運算子以連結二個或以上的關係運算式。

  • && (AND)

&& 是「AND(且)」運算:左右二邊「都」為 true,結果為 true;其餘情況其結果都為 false。

A B A && B
true true true
true false false
false true false
false false false

如果左邊為 false,則系統就不會去執行右邊;因為結果必為 false;既然左邊已經為 false,不管右邊結果為何,結果一定為 false。

  • | | (OR)

|| 是「OR(或)」運算,左右二邊只要有一邊為 true,結果為 true;只有左右二邊都為 false,結果才為 false。

A B A || B
true true true
true false true
false true true
false false false

如果左邊為 true,則系統就不會去執行右邊;因為結果必為 true;即然左邊已經為 true,不管右邊結果為何,結果一定為 true。

  • ! (NOT)

! 是「NOT(相反)」運算,其結果為 ! 右邊的相反。

A !A
true false
false true


運算子的優先順序

以下是在在寫運算式時常常會使用到的運算子的優先順序,整理如下:

優先順序 運算子
1 ((括號) )(右括號) ++(左遞增) --(左遞減)
2 +(正) -(負)  !(NOT) ++(右遞增) --(右遞減)
3 *(乘) /(除)  %(取餘數)
4 +(加) -(減)
5 <(小於) <=(小於等於) >(大於) >=(大於等於)
6 ==(等於)  !=(不等於)
7 &&(AND)
8 ||(OR)
9 ?:(條件判斷)

上表雖然不易記憶,然而,由於 "(" 與 ")" 的優先順序是最優先,所以在程式的設計過程中,我們可以藉由使用左、右括號來指定運算式的優先順序。

Java 條件判斷句的種類

Java 的條件判斷句(statement)可以分成以下 3 種:

  1. if...
  2. if... else...
  3. switch


if 條件判斷句


Java 語言 if (...) {...} 條件判斷句的流程圖及語法如下:
If-1.png

1 if (條件判斷)
2 {
2  條件判斷成立時執行的程式碼;
3  ....
4 }

說明:

  • 如果第 1 行的條件判斷成立(值 == true),則會執行第 2 ~ 4 行的程式碼(圖形藍色的路徑)。而如果條件判斷不成立(值 == false),則會跳過第 2 ~ 4 行程式碼不執行(圖形黑色 False 的路徑)。
  • 如果第 1 行的條件判斷成立時,執行的程式碼只有一行,則區塊符號可以省略不寫。如果超過一行,其區塊符號 { } 不可以省略。


Example 1:請設計一個 Java 程式,讓使用者自行輸入一個成績,判斷輸入的成績是否「大於或等於」60 ,如果是就輸出「成績及格!」。
If-2.png

import java.util.Scanner;
class if_loop {
   public static void main(String[] args) {
     if_condition();
   }
 
   public static void if_condition() {
     System.out.print("請輸入成績:");
     Scanner scanner = new Scanner(System.in);
     int grade = scanner.nextInt();

     if (grade >= 60)
       System.out.println("成績及格!");
   }
 }

執行結果:

請輸入成績:60
成績及格!

說明:

  • 輸入的 grade 為 60 ,條件判斷的結果為 true(60 >= 60),會執行條件判斷成立區塊內的程式碼,輸出「成績及格!」(圖形藍色的路徑)。
  • 因為範例中條件判斷成立時,執行的程式碼只有一行:
System.out.println("成績及格!");
所以區塊符號可以省略不寫。


if… else… 條件判斷句


Java 語言 if (...) {...} else {...} 條件判斷句的語法如下:
If-else-1.png

1  if (條件判斷) 
2 {
3 條件判斷成立時執行的程式碼;
4  ....
5 }
6 else
7 {
8   條件判斷不成立時執行的程式碼;
9   ....
10 }

說明:

  • 如果第 1 行的條件判斷成立(值 == true),則執行第 2 ~ 5 行區塊符號內的程式碼(圖形藍色的路徑)。
  • 如果第 1 行的條件判斷不成立(值 ==false),則跳過第 2 ~ 5 行的程式碼,而執行第 7 ~ 10 行區塊符號內的程式碼(圖形紅色的路徑)。
  • 如果 if 條件判斷成立或不成立時,執行的程式碼只有一行,則該區塊符號可以省略不寫。而如果超過一行,則區塊符號不可以省略。
  • if 和 else 是彼此互斥的關係,二個條件區塊在程式執行的過程中,只會選擇一個條件區塊去執行。


Example 2:請設計一個 Java 程式,讓使用者輸入一個成績,判斷該輸入的成績是否「大於或等於」60 ,如果是就輸出「成績及格!」;如果不是則輸出「成績不及格!」。
If-else-2.png

import java.util.Scanner;
class if_else_loop {
  public static void main(String[] args) {
    if_else_condition();
    if_else_condition();
  }

  public static void if_else_condition() {
    System.out.print("請輸入成績:");
    Scanner scanner = new Scanner(System.in);
    int grade = scanner.nextInt(); 

    if (grade >= 60)
      System.out.println("成績及格!");
    else
      System.out.println("成績不及格!");
  }
}

執行結果:

請輸入成績:88
成績及格!
請輸入成績:59
成績不及格!

說明:

  • 第一次輸入的 grade 為 88 ,其條件判斷的結果為 True(88 >= 60),執行 if 成立區塊內的程式碼,輸出 「成績及格!」(圖形藍色的路徑)。
  • 第二次輸入的 grade 為 59 ,其條件判斷的結果為 False(59 >= 60),執行 else 區塊內的程式碼,輸出 「成績不及格!」(圖形紅色的路徑)。
  • 因為 if 條件判斷成立和不成立時,執行的程式碼都只有一行,所以二者的區塊符號都可以省略不寫。


數個 if 的條件判斷


幾個接續在一起的 if 句子,可以寫成: if (...) {...} else if (...) {...} else {...}:
If-elseif-else-1.png

1  if (條件判斷1) {
2 條件判斷1成立時執行的程式碼;
3 ....
4 } else if (條件判斷2) {
5 條件判斷2成立時執行的程式碼;
6 ....
7 }
8 ....
9 ....
10 } else {
11 上述條件判斷都不成立時執行的程式碼;
12 ....
13 }

說明:

  • 如果在第 1 行的條件判斷1 成立(值 == true),則執行第 2 ~ 3 行區塊符號內的程式碼(圖形藍色的路徑),並略過其餘的程式碼。
  • 如果在第 4 行的條件判斷2 成立(值 == true),則執行第 5 ~ 6 行區塊符號內的程式碼(圖形綠色的路徑),並略過其餘的程式碼。
  • 如果所有的條件判斷都不成立(值 == false),則跳過第 1 ~ 10 行的程式碼,而執行第 11 ~ 12 行區塊符號內的程式碼 (圖形紅色的路徑) 。
  • if 、 else if 和 else 是彼此互斥的關係,所有條件區塊在程式執行的過程中,只會選擇一個條件區塊去執行。


Example 3:請設計一個 Java 程式,讓使用者自行輸入一個成績,判斷該輸入的成績是屬於 A, B, C, D 或 E 。
If-elseif-else-2.png

import java.util.Scanner;
class if_elseif_else_loop {
  public static void main(String[] args) {
    if_elseif_else_condition();
    if_elseif_else_condition();
    if_elseif_else_condition();
    if_elseif_else_condition();
    if_elseif_else_condition();
  }
 
  public static void if_elseif_else_condition() {
    System.out.print("請輸入成績:");
    Scanner scanner = new Scanner(System.in);
    int grade = scanner.nextInt();
 
    if (grade >= 90)
      System.out.println("成績為A");
    else if (grade >= 80)
      System.out.println("成績為B");
    else if (grade >= 70)
      System.out.println("成績為C");
    else if (grade >= 60)
      System.out.println("成績為D");
else
      System.out.println("成績為E");
  }
}

執行結果:

請輸入成績:90
成績為A
請輸入成績:89
成績為B
請輸入成績:70
成績為C
請輸入成績:60
成績為D
請輸入成績:59
成績為E

說明:

  • 第一次輸入的 grade 為 90 ,符合條件判斷 1,輸出 "成績為A" (圖形藍色的路徑) 。
  • 第二次輸入的 grade 為 89 ,符合條件判斷 2,輸出 "成績為B" (圖形綠色的路徑) 。
  • 第三次輸入的 grade 為 70 ,符合條件判斷 3,輸出 "成績為C" (圖形粉紅色的路徑) 。
  • 第四次輸入的 grade 為 60 ,符合條件判斷 4,輸出 "成績為D" (圖形淺藍色的路徑) 。
  • 第五次輸入的 grade 為 59 ,都不符合上面的條件判斷,會執行 else 區塊內的程式碼,輸出 "成績為E" (圖形紅色的路徑) 。
  • 因為 if 條件判斷、所有 else if 條件判斷或 else 成立時,執行的程式碼都只有一行,所以區塊符號都可以省略不寫。


switch 與 break


breake


  • break:在程式執行時,遇到 break,會跳過目前執行區塊後的程式碼,並跳出目前的區塊。


switch


當需要對一個 int、short、char、byte 或是 enum 型態值做多種不同的判斷時,可以使用 switch ,以下是 switch 的語法:
Switch-1.png

1  switch (變數或運算式) {
2 case1:
3 符合值1執行的程式碼;
4 ....
5 break;
6 case2:
7 符合值2執行的程式碼;
8 ....
9 break;
10
11
12 case 值n:
13 符合值n執行的程式碼;
14 ....
15 break;
16 default:
17 都不符合上述值執行的程式碼;
18 ....
19 }

說明:

  • 第 1 行: Switch 後面括號內的程式碼,可以是變數(例如:grade)或是運算式,然而其型態必需是 int、short、char、byte 或是 enum 型態(圖形藍色菱形的部份)。
  • 第 2 、 6 、 12 行:用以判斷 switch 後面括號內的變數或運算式的值是否符合值1(第2行;圖形藍色的路徑)、值2(第6行;圖形綠色的路徑)、值n(第12行;圖形粉紅色的路徑)的條件判斷。值1、值2、值n必須是常數(compile-time constant)。
  • 第 (3~4) 、 (7~8) 、 (13~14) 行:如果符合值1(圖形藍色的路徑)、值2(圖形綠色的路徑)、值n(圖形粉紅色的路徑)時會執行的程式碼。
  • 第 16 行:如果都不符合 case 值1 到 case 值n 的情況下,則會去執行 default 區塊內(第17、18行;圖形紅色的路徑)的程式碼,然後離開整個 swtich 的條件判斷。
  • 第 5 、 9 、 15 行:當程式執行遇到 break 敘述,會結束 swtich 的執行
  • 在 Java 語言中,switch 條件判斷的 case 的值只能是單一的常數值(compile-time constant),不可以是範圍值(例如:>=90)


Example 4:請使用 switch 來設計一個 Java 程式,讓使用者自行輸入一個成績,判斷該輸入的成績是介於哪一個區間之內。
Switch-2.png

import java.util.Scanner;
class Switch_Statement {
  public static void main(String[] args) {
    switch_statement();
    switch_statement();
switch_statement();
    switch_statement();
    switch_statement();
switch_statement();
  }
 
public static void if_else_condition() {
    System.out.print("請輸入成績:");
    Scanner scanner = new Scanner(System.in);
    int grade = scanner.nextInt();
grade = (int)grade / 10;
 
switch (grade) {
case 10:
case 9:
System.out.println("90~100");
break;
case 8:
System.out.println("80~89");
break;
case 7:
System.out.println("70~100");
break;
case 6:
System.out.println("60~69");
break;
default:
System.out.println("0~59");
}
}
}

執行結果:

請輸入成績:100
90~100
請輸入成績:90
90~100
請輸入成績:89
80~89
請輸入成績:74
70~59
請輸入成績:60
60~69
請輸入成績:59
0~59

說明:

  • 第一次輸入的 grade 為 100,符合 case 10 的條件判斷,但 case 10 區塊內沒有 break 敘述(圖形藍色的路徑)會繼續往下執行到 case 9 區塊內的程式碼,輸出 "90~100"(圖形綠色的路徑),之後遇到 break 敘述跳離開整個 swtich。
  • 第二次輸入的 grade 為 90,符合 case 9 的條件判斷,會執行 case 9 區塊內的程式碼,輸出 "90~100"(圖形綠色的路徑),之後遇到 break 敘述跳離開整個 swtich 。
  • 第三次輸入的 grade 為 89,符合 case 8 的條件判斷,會執行 case 8 區塊內的程式碼,輸出 "80~89"(圖形粉紅色的路徑),之後遇到 break 敘述跳離開整個 swtich。
  • 第四次輸入的 grade 為 74,符合 case 7 的條件判斷,會執行 case 7 區塊內的程式碼,輸出 "70~79"(圖形淺藍色的路徑),之後遇到 break 敘述跳離開整個 swtich。
  • 第五次輸入的 grade 為 60,符合 case 6 的條件判斷,會執行 case 6 區塊內的程式碼,輸出 "60~69"(圖形橘色的路徑),之後遇到 break 敘述跳離開整個 swtich。
  • 第六次輸入的 grade 為 59,都不符合上面 case 的條件判斷,會執行 default 區塊內的程式碼,輸出 "0~59"(圖形紅色的路徑),之後離開 swtich。


巢狀條件判斷


巢狀條件判斷(nested-if):是指在一個 if 裡面還有 if 。

Example 5:請寫一個能判斷使用者輸入的整數是 2、是 3、是 6、或都不是它們的倍數的 Java 程式。
Nested if.png

import java.util.Scanner;
public class Times {
public static Scanner keyboard = new Scanner(System.in);
 
public static void main(String[] args) {
times();
times();
times();
times();
}
 
public static void times() {
System.out.print("請輸入一個整數:");
int num = keyboard.nextInt();
 
if ((num % 2) == 0) {
if ((num % 3) == 0) {
System.out.println(num + "是2、3、6的倍數。");
}
else {
System.out.println(num + "是2的倍數。");
}
}
else {
if ((num % 3) == 0) {
System.out.println(num + "是3的倍數。");
}
else {
System.out.println(num + "都不是2、3、6的倍數。");
}
}
}
}

輸出結果:

請輸入一個整數:12
12是2、3、6的倍數。
請輸入一個整數:4
4是2的倍數。
請輸入一個整數:9
9是3的倍數。
請輸入一個整數:11
11都不是2、3、6的倍數。

說明:

  • 當輸入的 num 為 12 時,第一層(外層)可被 2 整除(執行 if 區塊),再往第二層(內層)可被 3 整除(執行 if 區塊),輸出 "12是2、3、6的倍數。"(圖形藍色的路徑)。
  • 當輸入的 num 為 4 時,第一層(外層)可被 2 整除(執行 if 區塊),再往第二層(內層)不可被 3 整除(執行 else 區塊),輸出 "4是2的倍數。"(圖形粉紅色的路徑)。
  • 當輸入的 num 為 9 時,第一層(外層)不可被 2 整除(執行 else 區塊),再往第二層(內層)可被 3 整除(執行 if 區塊內),輸出 "9是3的倍數。"(圖形綠色的路徑)。
  • 當輸入的 num 為 11 時,第一層(外層)不可被 2 整除(執行 else 區塊),再往第二層(內層)不可被 3 整除(執行 else 區塊),輸出 "11都不是2、3、6的倍數。"(圖形紅色的路徑)。


? : 條件判斷式

條件判斷 ? 條件判斷成立時執行的程式碼 : 條件判斷不成立時執行的程式碼 ;

if 與 ? : 的差別在於 if 不傳回值;而 ? : 可以傳回值(見以下範例)

Example 6:請設計一個 Java 程式,可以判斷成績是否及格。

if (grade >= 60)
message = "成績及格!";
else
message = "成績不及格!";

上面 Example 6 可以改寫成下面只有一行的 ? : 條件判斷式。

message = (grade >=60) ? "成績及格!" : "成績不及格!";

message 的值是上例執行 ? : 運算式之後傳回的結果。

迴圈與遞迴

迴圈

我們在撰寫程式時,常常需要用到迴圈。像是要讓使用者輸入10位學生的成績,如果沒有使用迴圈,就必須將輸入成績的程式碼寫10次,而這10次的程式碼卻完全一樣。如果使用迴圈,便只需要寫 1 次就可以了。使用迴圈讓這輸入成績的程式碼可以連續執行10次,以達到相同的效果,因此可以讓程式設計的更精簡、有彈性。

Java 用來寫迴圈的句子有以下三個:

  1. for
  2. while
  3. do-while


for-loop


for-loop 的語法如下:

1  for (控制變數的初始值設定; 控制變數的條件判斷; 控制變數值的改變)
2 {
3   符合控制變數的條件判斷時,執行的程式碼區塊;
4   ....
5 }

說明:

  • for-loop 主要由四個部份所組成,分別是「控制變數的初始值設定」、「控制變數的條件判斷」、「控制變數值的改變」與「符合控制變數的條件判斷時,執行的程式碼區塊」。
  • 「控制變數的初始值設定」:只有在迴圈第一次執行時才會執行,其目的是用來設定控制迴圈變數(loop control variable)初始值的設定。例如:int i = 0; 表示 for 迴圈一開始執行時,宣告一個 int 的控制變數,其值為0。
  • 「控制變數的條件判斷」:每一次執行 { } 內的程式碼區塊前,會先執行「控制變數的條件判斷」。如果符合該條件判斷,才會去執行迴圈內的程式碼;不符合便結束迴圈的執行。例如:i < 5; 表示如果此時迴圈的控制變數 i 的值小於 5 才會去執行迴圈內的程式碼,否則便離開迴圈。
  • 「控制變數值的改變」:每一次 for 執行完在 { } 內的程式碼區塊之後,會執行的部份。它會改變迴圈控制變數的值。例如:i++; 表示每一次迴圈執行完之後,將變數 i 的值加 1。
  • 「符合控制變數的條件判斷時,執行的程式碼區塊」(第 2 行的 { 至第5行的 }):符合控制變數的條件判斷時,會執行的程式碼。例如:System.out.println(i); 表示將迴圈的控制變數 i 的值輸出。


Example 1:請利用 for-loop 來設計一個程式,讓迴圈執行5次,每次執行時都輸出迴圈執行到第幾次。

public class for_loop {
  public static void main(String[] args) {
       for (int i=1; i<=5; i++)
           System.out.println("for迴圈執行第" + i + "次。");
  }
}

執行結果:

for迴圈執行第1次。
for迴圈執行第2次。
for迴圈執行第3次。
for迴圈執行第4次。
for迴圈執行第5次。
  • 由於區塊內的程式碼只有一個句子,所以 { } 可以省略不寫。


Example 2:請利用 for-loop 搭配 if 條件判斷來設計一個 Java 程式,將 1 到 10 中的偶數輸出。

public class for_loop2 {
  public static void main(String[] args) {
       for (int i=1; i<=10; i++)
           if (i % 2 == 0)
               System.out.println("1到10的偶數有:" + i);
  }
}

輸出結果:

1到10的偶數有:2
1到10的偶數有:4
1到10的偶數有:6
1到10的偶數有:8
1到10的偶數有:10


while-loop


while-loop 是前測式的迴圈,其語法如下:

1 控制變數的初始值設定;
2 while (控制變數的條件判斷)
3 {
4 符合控制變數的條件判斷時,執行的程式碼區塊;
5 ....
6 控制變數值的改變;
7 }

說明:

  • while-loop 和 for-loop 同樣由四個部分所組成,分別是「控制變數的初始值設定」、「控制變數的條件判斷」、「符合控制變數的條件判斷時,執行的程式碼區塊」與「控制變數值的改變」。
  • while-loop 是「前測式迴圈」,因為控制變數的條件判斷位於迴圈一開始的地方,因此 while-loop 執行次數可以為0次,也就是控制變數的條件判斷在一開始便不符合。


Example 3:請利用 while-loop 設計一個 Java 程式,讓迴圈執行5次,每次執行時都輸出迴圈執行到第幾次。

public class while_loop {
  public static void main(String[] args) {
       int i = 1;
       while (i <= 5) {
           System.out.println("while迴圈執行第" + i + "次。");
           i++;
       }
  }
}

執行結果:

while迴圈執行第1次。
while迴圈執行第2次。
while迴圈執行第3次。
while迴圈執行第4次。
while迴圈執行第5次。


Example 4:請利用 while-loop 設計一個 Java 程式,這個程式一直讓使用者從鍵盤輸入數字,直到輸入 -1 時為止。這個程式輸出這些數字的和。

import java.util.Scanner;
public class Sentinel {
    static Scanner keyboard = new Scanner(System.in);
    static int i = -1;
    
    public static void main(String[] args) {
        int total = 0;
        int s = 0;
        
        System.out.print("Please enter score or enter -1 to stop: ");
        s = keyboard.nextInt();
        while (s != i) {
            total = total + s;
            System.out.print("Please enter score or enter -1 to stop: ");
            s = keyboard.nextInt();
        }
        System.out.println("The sum of scores: "+ total);
    }
}

輸出結果:

Please enter score or enter -1 to stop: 3
Please enter score or enter -1 to stop: 5
Please enter score or enter -1 to stop: 7
Please enter score or enter -1 to stop: 9
Please enter score or enter -1 to stop: -1
The sum of scores: 24
  • 這個程式的特性是輸入數字的程式碼,在進入迴圈前及迴圈尾各出現一次。
  • 這種迴圈叫旗標控制迴圈(sentinel-controlled loop)。


do-while


do-while loop 是一種後測式迴圈,其語法如下:

1 控制變數的初始值設定;
2 do
3 {
4 符合控制變數的條件判斷時,執行的程式碼區塊;
5    ....
6 控制變數值的改變;
7 } while (控制變數的條件判斷);

說明:

  • do-while loop 和 for-loop、while-loop 同樣由四個部分所組成,分別是「控制變數的初始值設定」、「控制變數的條件判斷」、「符合控制變數的條件判斷時,執行的程式碼區塊」與「控制變數值的改變」。
  • do-while loop 是「後測式迴圈」,因為控制變數的條件判斷位於最後面。
  • do-while loop 最少會執行 1 次。因為一開始會先執行 do 區塊內的程式碼,之後才會進行 while 之後控制變數的條件判斷。
  • 特別留意在最一行 while 控制變數的條件判斷後面,需加上「;」以表示 do-while loop 的結束。


Example 5:請利用 do-while loop 設計一個 Java 程式,讓迴圈執行5次,每次執行時都輸出迴圈執行第幾次。

public class do_while_loop {
  public static void main(String[] args) {
       int i = 1;
       do {
           System.out.println("do while迴圈執行第" + i + "次。");
           i++;
       } while (i <= 5);
  }
}

執行結果:

do while迴圈執行第1次。
do while迴圈執行第2次。
do while迴圈執行第3次。
do while迴圈執行第4次。
do while迴圈執行第5次。


Example 6:請利用 do-while loop 搭配 if 條件判斷來設計一個 Java 程式,將 1 到 10 中間的偶數輸出。

public class do_while_loop2 {
  public static void main(String[] args) {
       int i = 1;
       do {
           if (i % 2 == 0)
               System.out.println("1到10的偶數有:" + i);
           i++;
       } while (i <= 10);
  }
}

執行結果:

1到10的偶數有:2
1到10的偶數有:4
1到10的偶數有:6
1到10的偶數有:8
1到10的偶數有:10


breake & continue


breake


  • break:可以離開目前的 switch、for、while、do while 的區塊,並跳離至區塊後的下一行程式碼。在 switch 中主要用來離開;而在 for、while 與 do while 迴圈中,主要用於中斷目前迴圈的執行。


Example 7:下面是一個使用 break 敘述的 Java 程式,觀察程式碼及程式執行的過程。

public class break1 {
  public static void main(String[] args) {
for (int i=1; i<=10; i++) {
if (i == 5)
break;
System.out.println("迴圈執行第" + i + "次。");
}
  }
}

執行結果:

迴圈執行第1次。
迴圈執行第2次。
迴圈執行第3次。
迴圈執行第4次。
  • 此迴圈只會執行 4 次,因為當 i==5 時就會執行 break; 述敘而離開迴圈。


continue


  • continue:作用與 break 敘述類似,主要使用於 for、while、do while 迴圈,所不同的是 break 敘述會結束迴圈區塊的執行,而continue 只會結束目前執行中區塊的程式碼,並跳回迴圈區塊的開頭繼續下一迴圈,而不是離開迴圈。


Example 8:下面是一個使用 continue 敘述的 Java 程式,觀察程式碼及程式執行的過程。

public class continue1 {
  public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
if (i == 3)
continue;
System.out.println("迴圈執行第" + i + "次。");
}
  }
}

執行結果:

迴圈執行第1次。
迴圈執行第2次。
迴圈執行第4次。
迴圈執行第5次。
  • 此迴圈會執行 1 ~ 2 加 4 ~ 5 次,當 i == 3 時,會執行 continue; 述敘而跳離此次迴圈,然後從區塊開頭執行下一次迴圈。


巢狀迴圈


  • 巢狀迴圈(nested loop)是指一個迴圈裡面還存在一個以上的迴圈。
  • 巢狀迴圈執行的總次數為每一個迴圈執行次數的乘積。例如:一個輸出九九乘法表程式的執行次數,就是外層的8次乘以內層的9次,因此得到 8 x 9 = 72 次。
  • 巢狀迴圈裡面的每一個迴圈都不可以與其它迴圈重疊,只能是彼此包含的關係。


Example 9:請使用巢狀迴圈設計一個輸出九九乘法表的程式,並計算巢狀迴圈總共執行了幾次。

public class NineNineTable {
  public static void main(String[] args) {
int count = 0;
for (int i=2; i<=9; i++) {
for (int j=1; j<=9; j++) {
System.out.printf("%d*%d=%2d ", i, j, i*j);
count++;
}
System.out.printf("\n");
}
System.out.printf("巢狀迴圈總共執行了%d次\n", count);
  }
}

執行結果:

2*1= 2 2*2= 4 2*3= 6 2*4= 8 2*5=10 2*6=12 2*7=14 2*8=16 2*9=18
3*1= 3 3*2= 6 3*3= 9 3*4=12 3*5=15 3*6=18 3*7=21 3*8=24 3*9=27
4*1= 4 4*2= 8 4*3=12 4*4=16 4*5=20 4*6=24 4*7=28 4*8=32 4*9=36
5*1= 5 5*2=10 5*3=15 5*4=20 5*5=25 5*6=30 5*7=35 5*8=40 5*9=45
6*1= 6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36 6*7=42 6*8=48 6*9=54
7*1= 7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49 7*8=56 7*9=63
8*1= 8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64 8*9=72
9*1= 9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81
巢狀迴圈總共執行了72次


Example 10:請使用巢狀迴圈設計一個 Java 程式,這個程式持續輸入兩個整數 m, n,利用 while-loop 計算 m 的 n 次方,直到輸入 999 為止。

import java.util.Scanner;
public class PowerOf {
public static Scanner input = new Scanner(System.in);
 
public static void main(String[] args) {
int n, m;
 
System.out.println("Input: ");
m = input.nextInt();
 
while (m != 999) {
n = input.nextInt();
 
System.out.print(n + "^" + m + "=");
 
int result = 1;
while (n > 0) {
result *= m;
n--;
}
 
System.out.println(result);
 
System.out.println("Input: ");
m = input.nextInt();
}
}
}
 
------Result--------
 
Input:
3
4
4^3=81
Input:
4
5
5^4=1024
Input:
5
6
6^5=15625
Input:
999

說明:

  • 外層的 while 迴圈控制程式執行到輸入 999 才結束。
  • 內層的 while 迴圈計算 m 的 n 次方。


for-loop、while-loop 與 do-while-loop 的相互轉換


  • for-loop、while-loop、do-while-loop可以彼此相互轉換,前面曾經介紹這三種迴圈主要由 4 個部分所組成,我們可以藉由搬動這4個部分的程式碼來完成迴圈的相互轉換。
  • for-loop -> while-loop 可以遵從下面三個步驟:
  1. 將「控制變數的初始值設定」從 for-loop 拉到 while-loop 前面。
  2. 將「控制變數的條件判斷」保留在 while-loop 的「控制變數的條件判斷」內。
  3. 將「控制變數值的改變」拉到 while-loop 的「符合控制變數的條件判斷時,執行的程式碼區塊」內的最後一行。
  • while-loop -> do-while-loop :
  1. 將 while-loop 的「控制變數的條件判斷」拉到 do-while-loop 最後一行的「「控制變數的條件判斷」」內即可完成。


Example 11:請分別利用 for 、 while 與 do-while 設計計算 1 加到 100 的程式。

public class Loop_Transfer1 {
public static void main(String[] args) {
// for-loop
System.out.println("for迴圈1加到100為" + for_loop());
 
// while-loop
System.out.println("while迴圈1加到100為" + while_loop());
 
// do-while-loop
System.out.println("do-while迴圈1加到100為" + do_while_loop());
}
 
public static int for_loop() {
int sum = 0;
for(int i=1; i<=100; i++)
sum += i;
return sum;
}
 
public static int while_loop() {
int sum = 0;
int i = 1;
while (i <= 100) {
sum += i;
i++;
}
return sum;
}
 
public static int do_while_loop() {
int sum = 0;
i = 1;
do {
sum += i;
i++;
} while (i <= 100);
return sum;
}
}

執行結果:

for迴圈1加到100為5050
while迴圈1加到100為5050
do-while迴圈1加到100為5050


Example 12:請分別利用 for、while 與 do-while 設計計算 1 到 100 中間 3 的倍數的總合。

public class Loop_Transfer2 {
public static void main(String[] args) {
// for-loop
System.out.println("for迴圈1到100間3的倍數總合為" + for_loop());
 
// while-loop
System.out.println("while迴圈1到100間3的倍數總合為" + while_loop());
 
// do-while-loop
System.out.println("do-while迴圈1到100間3的倍數總合為" + do_while_loop());
}
 
public static int for_loop() {
int count = 0;
for (int i=1; i <=100; i++)
if (i % 3 == 0)
count += i;
return count;
}
 
public static int while_loop() {
int count = 0;
int i = 1;
while (i <= 100) {
if (i % 3 == 0)
count += i;
i++;
}
return count;
}
 
public static int do_while_loop() {
int count = 0;
int i = 1;
do {
if (i % 3 == 0)
count += i;
i++;
} while (i <= 100);
return count;
}
}
 
------Result--------
 
for迴圈11003的倍數總合為1683
while迴圈11003的倍數總合為1683
do-while迴圈11003的倍數總合為1683


遞迴

  • 遞迴是一個相當獨特,對訓練邏輯思考很有助益的程式設計方式,為了能循序漸進的闡述遞迴程式的撰寫,在本節中,我們將以 String 資料型態當作程式的輸入資料,並以 1 來表示 boolean 的 true;而以 0 來表示 boolean 的 false。
  • 我們需要使用 Integer.parseInt(<String Variable>) 將 String 型態的資料轉換為 int 型態的資料。其轉換過後的值如果 == 1,則代表 true;如果 != 1,則代表 fasle。
[註] Java 的每一個 primitive type 都有一個與之對應的 reference type 或類別,例如:int 與 Integer。parseInt 則是 Integer 的一個類別方法。Java 這麼設計的原因,會在《Java 物件導向程式設計》中的「泛型與 Collection」部分說明。


簡易的遞迴程序


以下是呼叫 and2 的範例:

System.out.println(and2("11")); => true 
System.out.println(and2("10")); => false
System.out.println(and2("01")); => false
System.out.println(and2("00")); => false

and2 的特性是,它的參數中的兩個資料值都要是 1 結果才是 true,要不然結果就是 false。

現在我們試著將 and2 的功能擴充,並將擴充後的方法命名為 allTrue。而 allTrue 可以判斷一個 string 中的值是否都是 true,這個 string 可以有不止兩個資料值。以下是呼叫 allTrue 的範例:

System.out.println(allTrue(""));      => true  輸入參數中沒有一個false,所以結果是true
System.out.println(allTrue("111")); => true
System.out.println(allTrue("1111")); => true
System.out.println(allTrue("11010")); => false


以下是 allTrue 的寫法:

public class Recursion {
public static void main(String args[]) {
System.out.println(allTrue("")); // => true 輸入參數中沒有一個false的值,所以結果是true。
System.out.println(allTrue("111")); // => true
System.out.println(allTrue("1111")); // => true
System.out.println(allTrue("11010")); // => false
}
public static boolean allTrue(String str) {
if (empty(str)) // (str.equals(""))
return true;
else if (first(str) == 1) // (Integer.parseInt(str.substring(0, 1)) == 1 )
return allTrue(rest(str)); // allTrue(str.substring(1));
else
return false;
}
 
public static boolean empty(String str) {
return (str.equals(""));
}
 
public static int first(String str) {
return Integer.parseInt(str.substring(0, 1));
}
 
public static String rest(String str) {
return str.substring(1);
}
}
  • Integer.parseInt():將 () 裡的 string 由 String 型態轉換為 int 型態,以提供 if 來判斷其為 true(== 1) 或 false(!= 1)。
  • str.substring(0, 1):取出 str 字串變數中的第 0 個位置(index)至第 1 個位置(index)間的資料。(例如:"1234" => "1")
  • str.substring(1):取出 str 字串變數中第 1 個位置以後的資料。(例如:"1234" => "234")


allTrue("111") ; 的執行過程可 trace 如下:

allTrue("111")
=> allTrue("11")
=> allTrue("1")
=> allTrue("")
=> true


allTrue("1101") ; 的執行過程可 trace 如下:

allTrue("1101")
=> allTrue("101")
=> allTrue("01")
=> false


觀察 allTrue 這個程序以及它的執行過程,我們發現:

  1. allTrue 內有三個執行的可能(if 條件判斷的三個條件情況)。
  2. 其中一行呼叫 allTrue 自己。這個呼叫自己的遞迴呼叫,傳入少了頭一筆資料值的 String(str.substring(1))。另外兩行沒有遞迴呼叫,而程式執行到這兩行之後便傳回 true 或 false,然後結束。
  3. if 條件判斷式,控制程式執行哪個式子。
  4. allTrue 這個程式的輸出入型態是:String -> boolean,也就是傳入一個 String、傳出一個布林值的意思。


一般而言,遞迴程式都有類似的模式:

  1. 有終止條件(termination condition)如:str.equals("")、n == 0。
  2. 有遞迴呼叫,而遞迴呼叫所傳入的資料一定會趨近終止條件(例如使用 substring(1) 使 string 愈來愈縮短)。

這很合理,因為這樣程式才能結束。

這個單元的練習都是 String -> boolean 型態的程序。我們還會在後續的單元,陸續的介紹遞迴程式的其他型式。另外,在撰寫程式的過程中,最簡單的除錯工具是把變數的值印出來。System.out.println(<expression>) 與 System.out.println() 是可以顯示一個式子的傳回值與換行的兩個程序:

public class Recursive {
    public static void main(String args[]) {
      System.out.println(allTrue("111"));
    }
    
    public static boolean allTrue(String str) {
        System.out.println("str=" + str);

        if (str.equals(""))
            return true;
        else if (Integer.parseInt(str.substring(0, 1)) > 0)
            return allTrue(str.substring(1));
        else
            return false;
    }
}


執行以上的程式碼會列印與傳回以下的內容:

str=111
str=11
str=1
str=
true

前面 4 行與最後 1 行分別顯示了每次呼叫 allTrue 的輸入參數的值與最後的執行結果。

累計答案值的遞迴程序


上一單元我們練習了幾個傳回 true 或 false 的程序。這些題目的答案不需要經過累計,例如:累加、累乘、或累積成字串的過程。這個單元我們將練習需要經過累計才能計算出答案的遞迴程序。

假設我們需要撰寫一個能夠計算一個 string 中有幾個 1(true) 的程序。我們將這個程序命名為 countTrue:

countTrue("");     => 0 
countTrue("1101"); => 3


很顯然的,在程序執行的過程中如果遇到 1(true) 則需要將 1 加入結果之中。寫法如下:

public class Recursive {
public static void main(String args[]) {
System.out.println(countTrue("")); // => 0 輸入參數中沒有一個false的值,所以結果是0。
System.out.println(countTrue("1")); // => 1
System.out.println(countTrue("01")); // => 1
System.out.println(countTrue("101")); // => 2
System.out.println(countTrue("1101")); // => 3
}
 
public static int countTrue(String str) {
if (empty(str))
return 0;
else if (first(str) == 1) // 1(true)
return 1 + countTrue(rest(str));
else // 0(false)
return countTrue(rest(str));
}
 
public static boolean empty(String str) {
return (str.equals(""));
}
 
public static int first(String str) {
return Integer.parseInt(str.substring(0, 1));
}
 
public static String rest(String str) {
return str.substring(1);
}


我們可以追蹤這個程式是怎麼執行的:

  countTrue("");         empty("")成立因此傳回0
=> 0
 
CountTrue("1"); (first(str)) == 1)成立
=> 1 + countTrue(""); 因此傳回 1 + countTrue("");
=> 1 + 0
=> 1
 
countTrue("01");
=> countTrue("1");
=> 1
 
countTrue("101"); (first(str) == 1)成立
=> 1 + countTrue("01"); 因此傳回 1 + countTrue("01");
=> 1 + 1
=> 2


接下來,我們可以追蹤將一個有四個資料值的 string 傳入 countTrue 的執行狀況:

countTrue("1101"); 
=> 1 + countTrue("101");
=> 1 + 1 + countTrue("01");
=> 1 + 1 + countTrue("1");
=> 1 + 1 + 1 + countTrue("");
=> 1 + 1 + 1 + 0
=> 3


另外,countTrue 的型態是: String -> int 。

將遞迴程序轉換成迴圈程序


1. 階乘(factorial)是一個常見的遞迴程序的範例。如果「!」代表階乘的符號,則

0! = 1
N! = N * (N – 1)!

2. 加總(sum)是計算一個 string 變數內數字和的範例。

例如:sum("1234") => 10


在這一個單元,我們將以階乘(factorial)與加總(Sum)兩個程式為範例,說明如何將一個遞迴程式逐步的改寫成迴圈程式。

內涵式遞迴


階乘函數可以用 Java 寫成如下的程式碼,我們將之命名為 factorial:

public static int factorial(int n) {
if (n == 0)
return 1;
else
return n * factorial(n - 1);
}
 
factorial(3)
=> return n * factorial(n - 1); // n=3
=> return 3 * factorial(2);
=> return 3 * n * factorial(n - 1); // n=2
=> return 3 * 2 * factorial(1);
=> return 3 * 2 * n * factorial(n - 1); // n=1
=> return 3 * 2 * 1 * factorial(0);
=> return 3 * 2 * 1 * 1; // n=0
=> return 3 * 2 * 1 * 1;
=> 6

觀察以上的執行狀況,呼叫 factorial 的遞迴呼叫是內涵(embedded)在乘法算式 (n * ...) 之內。而程式的執行結果是在最後一個遞迴呼叫 factorial(0) 完成後才依序依照這個有括號的算式由內而外累乘 (3 * (2 * (1 * 1))) 而得。這種類型的遞迴程序稱為內涵式遞迴(embedded recursion)。

加總涵式 sum 的寫法如下:

public class Recursive {
public static void main(String args[]) {
System.out.println(sum("1234"));
}
 
public static int sum(String str) {
if (str.equals(""))
return 0;
else
return Integer.parseInt(str.substring(0, 1)) + sum(str.substring(1));
}
}
 
Sum("1234")
=> return Integer.parseInt(str.substring(0, 1)) + sum(str.substring(1)); // str="1234"
=> return 1 + sum("234");
=> return 1 + (Integer.parseInt(str.substring(0, 1)) + sum(str.substring(1))); // str="234"
=> return 1 + (2 + sum("34"));
=> return 1 + (2 + (Integer.parseInt(str.substring(0, 1)) + sum(str.substring(1)))); // str="34"
=> return 1 + (2 + (3 + sum("4")));
=> return 1 + (2 + (3 + (Integer.parseInt(str.substring(0, 1)) + sum(str.substring(1))))); // str="4"
=> return 1 + (2 + (3 + (4 + sum(""))));
=> return 1 + (2 + (3 + (4 + (Integer.parseInt(str.substring(0, 1)) + sum(str.substring(1))))); // str=""
=> return 1 + (2 + (3 + (4 + 0)));
=> 10

觀察以上的執行狀況,呼叫 sum 的遞迴呼叫也是內涵(embedded)在加總算式(Integer.parseInt(str.substring(0, 1)) + ...)之內。而程式的執行結果是在最後一個遞迴呼叫 sum("") 完成後才依序累加 (1 + (2 + (3 + (4 + 0)))) 而得的。這是內涵式遞迴的另一個例子。

尾端式遞迴


第二種形式的遞迴程序是尾端遞迴(tail recursion)。尾端遞迴的特色是遞迴呼叫沒有內涵在任何一個尚未執行完成的式子內。以下面的例子而言,呼叫 tail-fac 的遞迴呼叫雖然是在 if 之內,但是該 if 的條件判斷式已經執行完畢,所以是尾端遞迴。

public static int tail-fac(int n, int m) {
if (n == 0)
return m;
else
return tail-fac(n - 1, n * m);
}
 
tail-fac(3, 1)
=> tail-fac(2, 3)
=> tail-fac(1, 6)
=> tail-fac(0, 6)
=> 6

以上程序的執行狀況是「平的」。尾端遞迴在執行的過程中不會像內涵式遞迴累積一層又一層如同樓梯般的式子,原因是前面遞迴呼叫時所產生的區域變數區內的變數值,並不需要被保留,因此下一次遞迴呼叫的區域變數區可以直接的覆蓋上一次遞迴呼叫時所使用的區域變數區。

將內涵式遞迴程序轉換成尾端遞迴程序的技巧是增加傳入的參數。以 tail-fac 來說我們增加了一個能夠儲存累乘值的參數 m。增加這個參數後便不需要以累積乘法算式的方式來計算階乘的值了。

而尾端遞迴的加總函式 sum,也可以經由增加一個參數及改變遞迴的回傳值得到:

public class  Recursive {
public static void main(String args[]) {
System.out.println(tail-sum("1234", 0));
}
 
public static int tail-sum(String str, int n) {
if (str.equals(""))
return n;
else
return tail-sum(str.substring(1), (n + Integer.parseInt(str.substring(0, 1))));
}
}
 
tail-sum("1234", 0)
=> tail-sum("234", 1)
=> tail-sum("34", 3)
=> tail-sum("4", 6)
=> tail-sum("", 10)
=> 10

迴圈


第三種計算階乘的方式是使用一般程式語言常常使用的「迴圈」。一個內涵式遞迴程序在轉換成尾端式遞迴程序後,便可以改寫成迴圈程式了。

轉換的方式可以分成下面三個步驟:

  1. 將尾端式遞迴的終止條件內的條件判斷拉到迴圈的條件判斷內,並將它轉換成相反(Not)的條件判斷(加上 ! 或 !=)。
  2. 將尾端式遞迴的參數拉到迴圈內並改寫成設值運算式,但是要注意的是設值運算式的前後次序。
  3. 將尾端式遞迴的終止條件成立時的回傳值拉到迴圈的後面,讓程序結束後將結果值回傳回去。


使用 whlile-loop 將尾端式遞迴轉換成一般的程式語言常常使用的「迴圈」。

public class Recursive {  
public static void main(String args[]){
System.out.println(fac3(3, 1));
}
 
public static int fac3(int n, int m) {
while (n != 0) {
m = m * n;
n = n - 1;
// 注意:上兩行次序不可顛倒。
}
return m;
}
}
 
fac3(3, 1)
=> 6
public class Recursive {  
public static void main(String args[]){
System.out.println(sum3("1234", 0));
}
 
public static int sum3(String str, int n) {
while (!str.equals("")) {
n = n + Integer.parseInt(str.substring(0, 1));
str = str.substring(1);
// 注意:上兩行次序不可顛倒
}
return n;
}
}
 
sum3("1234", 0)
=> 10


同樣也可以使用 Java 語言的 for Loop 來將尾端式遞迴轉換成一般的程式語言常常使用的「迴圈」。

public class Recursive {  
public static void main(String args[]){
System.out.println(fac3(3, 1));
}
 
public static int fac3(int n, int m) {
for (int i=m; i<=n; i++)
m = m * i;
return r;
}
}
 
fac(3, 1)
=> 6
public class Recursive {  
public static void main(String args[]){
System.out.println(sum3("1234", 0));
}
 
public static int sum3(String str, int n) {
for (; !str.equals(""); str = str.substring(1))
n = n + Integer.parseInt(str.substring(0, 1));
return n;
}
}
 
sum3("1234", 0)
=> 10


陣列

陣列(array)是一組有下標(index)的儲存值,這些儲存值稱為元素(element)。一個陣列有一定的長度(length)以及一個型態(t)。陣列下標的範圍是從 0 到 length - 1。儲存在 array 中的值的型態必須是 t 或 t 的 subtype。subtype 的觀念與 subclass(子類別)有關,將在物件導向的單元中介紹。陣列內的儲存值可以是 primitive type 也可以是 reference type。在本節中我們只會以 primitive type 的儲存值當作範例。

例如:

2.4 3.9 4.0 1.3

便是一個長度是 4,下標是 0 - 3 而型態是 double 的陣列。這個陣列可以由以下的程式碼得到:

double[] aDoubleArray;  // 宣告 aDoubleArray 是一個 double array

aDoubleArray = new double[4]; // 使用 new 挪出可以存放 4 個 double 的儲存區
aDoubleArray[0] = 2.4;
aDoubleArray[1] = 3.9;
aDoubleArray[2] = 4.0;
aDoubleArray[3] = 1.3;

以下是另一種寫法:

double[] aDoubleArray = new double[4]; 
aDoubleArray[0] = 2.4;
aDoubleArray[1] = 3.9;
aDoubleArray[2] = 4.0;
aDoubleArray[3] = 1.3;

也可以直接初始化:

double[] aDoubleArray = {2.4, 3.9, 4.0, 1.3};

而以下的程式碼可以印出其值:

for (int i = 0; i < aDoubleArray.length; i++)
    System.out.println(aDoubleArray[i]);

多維陣列

多維陣列是一個陣列的元素本身也是陣列。例如以下這個二維陣列:

String[][] names = {{"Boy", "Girl", "Man"},
                    {"Woman", "Father", "Son"}};
for (int i = 0; i < 2; i++)
    for (int j = 0; j < 3; j++)
        System.out.printf(names[i][j]);

的輸出結果是

Boy
Girl
Man
Woman
Father
Son
Personal tools