# 目錄

| 章節   | 標題                                                                                             |
| ---- | ---------------------------------------------------------------------------------------------- |
| 第零章  | 新手初入門，切記熟練                                                                                     |
|      | [參考書籍](https://java.4-x.tw/newbie/book)                                                        |
|      | [安裝 Java](https://java.4-x.tw/newbie/java-000)                                                 |
|      | [安裝 Eclipse IDE](https://java.4-x.tw/newbie/java-001)                                          |
|      | [首次操作 Eclipse](https://java.4-x.tw/newbie/java-002)                                            |
|      | [初學常見錯誤 & 提高可讀性](https://java.4-x.tw/newbie/java-003)                                          |
|      |                                                                                                |
| 第一章  | 認識 Java                                                                                        |
|      | [第一支程式：新建檔案](https://java.4-x.tw/java-01/createfile)                                           |
|      | [第一支程式：Hello Java](https://java.4-x.tw/java-01/hellojava)                                      |
|      |                                                                                                |
| 第二章  | 簡單 Java 程式解析                                                                                   |
|      | [變數與輸出](https://java.4-x.tw/java-02/java-02-1)                                                 |
|      | [重點整理](https://java.4-x.tw/java-02/java-02-2)                                                  |
|      |                                                                                                |
| 第三章  | 變數與資料型態                                                                                        |
|      | [認識變數與資料型態](https://java.4-x.tw/java-03-1)                                                     |
|      | [溢位](https://java.4-x.tw/java-03-2)                                                            |
|      | [更多資料型態](https://java.4-x.tw/java-03-3)                                                        |
|      | [資料型態的轉換](https://java.4-x.tw/java-03-4)                                                       |
|      |                                                                                                |
| 第四章  | 運算子、運算式與敘述                                                                                     |
|      | [運算子、運算式](https://java.4-x.tw/java-04/java-04-1)                                               |
|      | [程式範例](https://java.4-x.tw/java-04/java-04-2)                                                  |
|      |                                                                                                |
| 第五章  | 選擇性描述與迴圈                                                                                       |
|      | [選擇性描述](https://java.4-x.tw/java-05/java-05-1)                                                 |
|      | [迴圈](https://java.4-x.tw/java-05/java-05-2)                                                    |
|      | [迴圈的跳離](https://java.4-x.tw/java-05/java-05-3)                                                 |
|      |                                                                                                |
| 第六章  | 陣列                                                                                             |
|      | [一維陣列](https://java.4-x.tw/java-06/java-06-1)                                                  |
|      | [二維與多維陣列](https://java.4-x.tw/java-06/java-06-2)                                               |
|      |                                                                                                |
| 第七章  | 函數                                                                                             |
|      | [函數與引數](https://java.4-x.tw/java-07/java-07-1)                                                 |
|      | [函數傳遞陣列](https://java.4-x.tw/java-07/java-07-2)                                                |
|      | [函數多載](https://java.4-x.tw/java-07/java-07-3)                                                  |
|      | [函數 SWAP](https://java.4-x.tw/java-07/java-swap)                                               |
|      |                                                                                                |
| 第八章  | 類別                                                                                             |
|      | [定義類別](https://java.4-x.tw/java-08/java-08-1) / [使用類別](https://java.4-x.tw/java-08/java-08-2)  |
|      | [類別中的函數成員相互呼叫](https://java.4-x.tw/java-08/java-08-3)                                          |
|      | [類別中使用 this](https://java.4-x.tw/java-08/java-08-4)                                            |
| 練習   | [建立圓形類別](https://java.4-x.tw/java-08/java-08-p)                                                |
|      | [傳遞引數與回傳值](https://java.4-x.tw/java-08/java-08-5)                                              |
|      | [多引數](https://java.4-x.tw/java-08/java-08-6)                                                   |
|      | [類別中函數的多載](https://java.4-x.tw/java-08/java-08-7)                                              |
|      | [類別成員中的公有與私有 (Public & Private)](https://java.4-x.tw/java-08/java-08-8)                        |
|      |                                                                                                |
| 第九章  | 建構元                                                                                            |
|      | [認識建構元](https://java.4-x.tw/java-09/java-09-1)                                                 |
|      | [建構元的多載](https://java.4-x.tw/java-09/java-09-2)                                                |
|      | [建構元間的呼叫 - 透過 this](https://java.4-x.tw/java-09/java-09-3)                                     |
|      | [建構元中的公有與私有 (Public & Private)](https://java.4-x.tw/java-09/java-09-4)                         |
|      |                                                                                                |
| 第十章  | 實例變數與函數、類別變數與函數                                                                                |
|      | [實例變數 與 實例函數](https://java.4-x.tw/java-10/java-10-1)                                           |
|      | [類別變數](https://java.4-x.tw/java-10/java-10-2)                                                  |
|      | [類別函數](https://java.4-x.tw/java-10/java-10-3)                                                  |
|      |                                                                                                |
| 第11章 | 類別的繼承                                                                                          |
|      | [認識類別的繼承](https://java.4-x.tw/java-11/java-11-1) / [範例](https://java.4-x.tw/java-11/java-11-2) |
|      | [類別繼承中的建構元呼叫](https://java.4-x.tw/java-11/java-11-3)                                           |
|      | [Protected members](https://java.4-x.tw/java-11/java-protected-members)                        |
|      | [Overriding](https://java.4-x.tw/java-11/java-overriding)                                      |
|      | [super() 與 this()](https://java.4-x.tw/java-11/java-super-and-this)                            |
|      | [設定終止繼承](https://java.4-x.tw/java-11/java-11-4)                                                |
|      |                                                                                                |
| 第12章 | 抽象類別                                                                                           |
|      | [認識抽象類別](https://java.4-x.tw/java-12/java-12-1) / [範例](https://java.4-x.tw/java-12/java-12-2)  |
|      | [使用抽象類別型態的變數建立物件](https://java.4-x.tw/java-12/java-12-3)                                       |
|      |                                                                                                |
| 第13章 | 介面 Interface                                                                                   |
|      | [認識介面](https://java.4-x.tw/java-13/java-13-1) / [範例](https://java.4-x.tw/java-13/java-13-2)    |
|      | [介面中的多重繼承](https://java.4-x.tw/java-13/java-13-3)                                              |
|      | [介面的延伸](https://java.4-x.tw/java-13/java-13-4)                                                 |
|      |                                                                                                |
| 第14章 | 例外 Exception                                                                                   |
|      | [認識例外](https://java.4-x.tw/java-14/java-14-1)                                                  |
|      | [例外處理](https://java.4-x.tw/java-14/java-14-2) / [範例](https://java.4-x.tw/java-14/java-14-3)    |
|      | [認識例外類別](https://java.4-x.tw/java-14/java-14-4)                                                |
|      | [例外類別捕捉多個例外](https://java.4-x.tw/java-14/java-14-5)                                            |
|      | [於程式中拋出例外](https://java.4-x.tw/java-14/java-14-6)                                              |
|      | [於指定函數拋出例外](https://java.4-x.tw/java-14/java-14-7)                                             |
|      | [自定義例外類別](https://java.4-x.tw/java-14/java-14-8)                                               |
|      |                                                                                                |
| 第15章 | 執行緒                                                                                            |
|      | [認識多執行緒](https://java.4-x.tw/java-15/java-15-1)                                                |
|      | [啟動執行緒](https://java.4-x.tw/java-15/java-15-2)                                                 |
|      | [實作 Runnable 介面](https://java.4-x.tw/java-15/java-15-3)                                        |
|      | [執行緒生命週期](https://java.4-x.tw/java-15/java-15-4)                                               |
|      | [凍結執行緒 sleep()](https://java.4-x.tw/java-15/java-15-sleep)                                     |
|      | [凍結執行緒 join()](https://java.4-x.tw/java-15/java-15-join)                                       |
|      | [執行緒的順序](https://java.4-x.tw/java-15/java-15-5)                                                |
|      | [同步處理 Synchronized](https://java.4-x.tw/java-15/java-15-6)                                     |
|      |                                                                                                |
| 習題   | [產生不重複之亂數](https://java.4-x.tw/java-exercise/java-random)                                      |


# 參考書籍

> 俗話說：「工欲善其事，必先利其器」

Java 至今已經走過許多版本，這個學習系列當初使用的書籍是「Java 7 教學手冊 第五版」

博客來：[Java 7 教學手冊 第五版(附光碟)](https://www.books.com.tw/products/0010555220)

![書籍圖片來自博客來](/files/-M4NJLOQfQuDRemI7WR4)

學習系列的章節、進度與範例都會節錄自這本 Java 7 教學手冊。

也會配合當初上課時的課程章節如下表：

| 章節  | 標題                             |
| --- | ------------------------------ |
| 1   | 認識 Java                        |
| 2、3 | <p>簡單的Java程式</p><p>變數與資料型態</p> |
| 4、5 | <p>運算子、運算式</p><p>選擇性敘述與迴圈</p>  |
| 6   | 陣列                             |
| 7   | 函數                             |
| 8   | 類別的基本架構                        |
| 9   | 類別的進階認識                        |
| 10  | 類別的繼承                          |
| 11  | 抽象類別與介面                        |
| 12  | 大型程式的發展與常用的類別庫                 |
| 13  | 例外處理                           |
| 14  | 檔案管理                           |
| 15  | 多執行緒                           |


# 安裝 Java

### 一、下載 JDK

{% hint style="info" %}
Java 官網時常更新，因此看到的畫面有可能與以下教學有差異。

示範為下載 Java SE 8u102
{% endhint %}

進到[下載頁面](https://www.oracle.com/java/technologies/javase-downloads.html)，在Java SE Downloads 中Java Platform (JDK) 8u101 / 8u102\
點選DOWNLOAD。

![](/files/-M4NL69BVb8JTfL50IdL)

點擊後會轉跳到下載頁面，此時找到想要下載的版本號(以下教學為8u102)， 點擊「Accept License Agreement」意指接受許可協議； 點擊後於下方選擇自己的電腦系統相對應位元版本進行下載。 (本教學以Windows x64 為主)。

![](/files/-M4NLqqRw2DaPfFp0bnR)

點擊接受協議許可後則會出現\
Thank you for accepting the Oracle Binary Code License Agreement for Java SE; you may now download this software.

之後在Download的地方點擊下載。

![](/files/-M4NLulT0ixK0RUt7gcA)

### 二、安裝 JDK

執行下載的檔案「jdk-8u102-windows-x64.exe」

![選擇「是」](/files/-M4NM9oL6vcw13a2PDpn)

![選擇「Next」](/files/-M4NMBwCF6a0YgKuYKhG)

![選擇「Next」](/files/-M4NMEfzSKO1JwFWZBRe)

![](/files/-M4NMGlMbqYwPJgnfKmf)

![安裝至預設資料夾即可，「下一步」](/files/-M4NMIq2yqjWy08LKBxf)

![](/files/-M4NMbMwhw3cRA8kcK72)

![「Close」完成安裝](/files/-M4NMdJo7xHAcxx1zyVT)


# 安裝 Eclipse IDE

### 一、下載 eclipse

進入 eclipse [下載頁面](https://www.eclipse.org/downloads/)，點擊DOWNLOAD 64 BIT

![](/files/-M4NMz-kOB7KNwYuxc20)

![](/files/-M4NN0HBcxSabp2YHOI_)

### 二、安裝 eclipse

執行下載的檔案「eclipse-inst-win64.exe」

​

![點擊 「Eclipse IDE for Java Developers」](/files/-M4NNK-oVx2S3qb5LA7D)

![點擊 INSTALL 進行安裝](/files/-M4NNPYPBr2EGynNMSb6)

![使用者條款，點擊「Accept Now」表示接受](/files/-M4NNSu29k30MoXudHGD)

![耐心等待下載安裝](/files/-M4NNWeM-9W53lQY3FcD)

![點擊 LAUNCH 執行 eclipse](/files/-M4NNZmw0IS6QTSqkC9e)

### 三、首次開啟 eclipse

![](/files/-M4NNm-zB9q8qW_AT_h3)

首次開啟會詢問workspace，可以依照自己存放專案的習慣更改目錄

![](/files/-M4NNnXQV7pLn5vTyPdz)

![等候 loading](/files/-M4NNya76F4lrdySSO9e)

![成功進入 eclipse](/files/-M4NO1W3Rpm_LQefNUEE)


# 首次操作 Eclipse

> 在這章節會示範新增 Class，會操作如何將Class 建立在上一次的Project

### 一、查看專案資料夾

![進到畫面後，直接將Welcome 歡迎畫面點擊叉叉](/files/-M4NOnClmYpXlWQhytrQ)

![這時只要到左側的小資料夾展開即可](/files/-M4NOrnA2bPN5-Pp0apa)

![如樹狀即可展開資料夾](/files/-M4NOwoUw2AFNBO9AJJn)

### 二、建立 Class

![點擊２ 綠底白字C：新增Class 即可新增Class](/files/-M4NPHT7coM4o97w948D)

再次檢查上層的Package 、Class name 與下方是否已打勾。 \
(因人偏好而異，下圖為 將class02 建立於ch01這個project下的ch01 package)

![](/files/-M4NPPu725XWEVgu-jS-)

若想好好的將此教學分類好，可按照下方的設定：

在Package 處直接打上ch02，Name的部分打上class01，再勾選下方的「public ststic void main(String\[] args)」，直接點擊Finish 即可直接創立新的package 與該底下的class。

![](/files/-M4NPWcR9p_tbIzaVkdP)

![成功創立](/files/-M4NPYcxv9hQsCMfXWQi)

### 三、更改 Project 名稱

![點擊最外層的ch01](/files/-M4NPiiGUT4rET2kTfhN)

![點擊左上方的File，找到Rename即可更名為自己想要的project名](/files/-M4NPlMi7rIUDmzZQfor)

![此教學更名為java-sc](/files/-M4NPoty1KFR9Fi35lsm)

### 四、讀取 Project

> 假設今天有人傳了一個project給你，如何開啟

![當我們進到歡迎頁面時，點擊「Import existing projects」](/files/-M4NQ0pSckI3e05ftsrQ)

![點擊Browse 選擇project位置後，再點擊Finish即可](/files/-M4NQ3hHs2dxx_AH9Ola)

![這時就會看到樹狀圖上方的「ch01」為剛剛導入的project，而下方的「java-sc」為 上方更名教學的project。](/files/-M4NQ6Tzcp-lUCSxGcud)


# 初學常見錯誤 ＆ 提高可讀性

針對常見的錯誤列出以下幾點讓初學者做參考

### 一、常見錯誤

```
package ch01;

public class class01 {

	public static void main(String[] args) {

		 System.out.println("Hello Java!");

	}
}
```

1. System 的 **S** 要記得大寫
2. 注意 System . out . print 之間是點「 . 」並非逗號「 , 」
3. 程式碼結束會跟著分號「；」
4. ｛｝大括號結尾的，不需要分號 「；」
5. public static void main(String \[]args){}\
   public static void main(String args\[]){}\
   兩者的意思是相同的
6. 注意呼叫的單字拼寫是否正確
7. 注意程式碼英文為半形，並非全形
8. 括號 或 大括號 都是成對出現的
9. 類別class 與函數method 都是使用大括號 ｛｝將其內容包住
10. class 名稱命名方式不包含 「 . 」或「 – 」 的形式出現\
    如：ch01.2  ch01-2\
    建議可以使用底線符號 「 \_ 」\
    如：ch01\_2

### 二、提高可讀性

* 適當的加上註解「 // 」到該行結束，\
  或使用「 /\*」與「 \*/」這兩個符號之間可跨越多行建立註解
* 變數名稱或是類別名稱，命名時盡量使用有意義的名稱，\
  配合該內容作命名
* 盡量一行一個敘述， （一行出現一個分號「；」）\
  若敘述被括號刮著除外，如 for (int i=1; i<10;i++)
* 適當的將同一區塊的程式碼敘述做「縮行」整理
* 必要時，適當的空行也可以提高可讀性


# 第一支程式：新建檔案

> 在這章會學到：
>
> 1. 第一個程式 ：Hello JAVA
> 2. 程式解說

### 一、workspace

![首先開啟eclipse](/files/-M4NQiJSg3oP69I8eDwF)

![workspace為專案儲存目錄，依個人習慣存放](/files/-M4NQlB8f1yId3YpMaCS)

{% hint style="info" %}
一個worksplace可以新增很多package

一個package裡面可以新增很多class

Class就是你程式執行的地方
{% endhint %}

![點擊「Create a new Java project」，意指新增一個Java 專案](/files/-M4NROA7YLLX7a0N0pJ2)

### 二、Project

在「Project name」的地方輸入您的專案名稱，此教學為輸入「ch01」。&#x20;

此時在下方的Location就會顯示這項專案的資料夾建立在何處。 (您的workspace 建立在何處，Project就會建立在該資料夾底下)

![幫專案取名完可直接點擊最下方的「Finish」即可完成](/files/-M4NRSarnp9qd8pjWtJ7)

![幫專案取名完可直接點擊最下方的「Finish」即可完成](/files/-M4NRSarnp9qd8pjWtJ7)

![](/files/-M4NRhlsbbWv9V2gcyJb)

### 三、Package＆Class

{% hint style="info" %}
功能列上： 棕色正方形是新增Package 、綠色 C+ 則是新增Class
{% endhint %}

![先點擊１的部分：New Java Package](/files/-M4NRnuDEEWyHU6o2vhh)

這時會詢問Package 要建立在何處底下 與 Package名稱。 此教學以「ch01」名稱為例。 之後點擊Finish。

![](/files/-M4NS7Abx5qUM4ig0tMQ)

再來我們要新增Class；點擊２：New Java Class

![](/files/-M4NSBFLaDBVzaH-9-Vk)

在Name的地方輸入Class的名稱後，在底下的「public ststic void main(String\[] args)」打勾。

&#x20;此教學的Class名稱為「class01」；輸入完也打勾完後直接點擊最下方的「Finish」完成。

![](/files/-M4NSE42Sv9qWl9GzZ5M)

![紅框處是打程式碼的地方](/files/-M4NSFkuoU7PjFLLErmg)


# 第一支程式：Hello Java

### 一、撰寫程式

```
public static void main(String[] args){
 // TODO Auto-generated method stub
}
```

將 // TODO Auto-generated method stub 取代成以下程式碼（或是撰寫在此行註解的底下）：

```
 System.out.println("Hello Java!");
```

![完整的程式碼](/files/-M4NSZq3qvrXm5O36YhG)

![點擊功能列上的運行](/files/-M4NSbGZv6zN_Fap_3Nr)

![詢問存檔：點擊OK](/files/-M4NSdpDvKHx8reSoZA0)

![成功運行後，在最底下就會顯示結果](/files/-M4NSfyrhKJHbLq7W1kP)

### 二、程式碼說明

```
package ch01;  //第一行是一開始命名的package名稱

public class class01 { //而這行是命名的Class名稱

	public static void main(String[] args) { // 這部分是上方教學要求打勾

		 System.out.println("Hello Java!"); //這部分是Hello Java 程式碼

	}
}
```

我們由一開始的Project、Package 與 Class 先做簡單說明：

Project 就像是一個課本\
package 像是章節\
class 就是每章節的內容

project → package → class

```
public static void main(String[] args)
```

這是程式的起點。\
當程式載入一個 .java 檔，會先去尋找 main 在哪，所以必須先宣告 main 是 公開、共用的 (public)，否則程式將會找不到 main 的位置。

main() 是相當特殊的一個函數，沒有回傳值，因此在main 之前要加上 void。

main() 括號內的String\[ ] args 則是：程式執行時，裡面將會以字串型態的陣列 args\[] 存放。

```
public class class01 {
	public static void main(String[] args) {
}}
```

所以整段程式碼可以解釋為：

> 宣告一個公開的類別 class01， 定義 公開 靜態無傳回值 名稱為main 參數為字串陣列 的方法

```
System.out.println("Hello Java!");
```

System.out.println( ) 的功用是：印出括號內所有包含的文字。

System.out 意指標準輸出；\
後方的 println 則是 print line 的縮寫。


# 變數與輸出

### 一、println 與 變數 使用

在Hello Java 程式中我們有用到println 這個用法：

```
 System.out.println("Hello Java!");
```

本章就先從這部分開始說起～\
以下：

```
public class class01 {

	public static void main(String[] args) {
		int num;
		num=5;
		System.out.println("I have "+num+" books");
		System.out.println("You have "+num+" books, too");

	}

}
```

我們可以先想想，這個的輸出結果會是如何。\
想完之後再上 eclipse 執行看看結果如何。

{% hint style="info" %}
比對兩次的是否相同後，在看以下的程式分析唷！
{% endhint %}

本章的重點如下：

```
int num;
num=5;
	System.out.println("I have "+num+" books");
	System.out.println("You have "+num+" books, too");
```

`int num;`是宣告 num 為一個整數型態的變數。

`num=5;`是把數值5設定給整數變數num存放。

```
System.out.println(“I have “+num+” books”);
System.out.println(“You have “+num+” books, too”);
```

`System.out` 是標準輸出；`println` 則是 print line 的縮寫，意指將後方括號中的內容印到輸出設備，如螢幕。因此印出 I have 5 books 之後會換行也就是把游標移到下一行的開端列印。

![輸出結果](/files/-M4X6ag5ZC4u26hBvZXf)

我們可以發現他會直接印出雙引號內的內容，而num 的部分為數值5。 簡單的對照圖如下：由於已給定num變數為數值5，因此輸出時將會輸出5。

![](/files/-M4X6eUAdeyStRmmQ7TE)

### 二、println 與 print 比較

讓我們改試試以下程式碼：

```
public class class01 {

	public static void main(String[] args) {
		int num;
		num=5;
		System.out.print("I have "+num+" books");
		System.out.println("You have "+num+" books, too");

	}

}
```

與上面的輸出結果有何差別？

![執行結果，會發現You have 5 books, too 緊連著前一句的屁股](/files/-M4X6p2DJ5w0zHAbQgDD)

在這邊補充說明 print 與 println 的小差異：

{% hint style="info" %}
*println 是有換行*\
*print 無換行*
{% endhint %}


# 重點整理

### 一、變數

> 變數是程式語言中最基本的角色。常用來存放資料，使用之前要先宣告它欲儲存的資料型態。 讓我們來簡單的看看變數使用規則。

#### 變數規則 1：變數宣告

如以上的範例使用到 int 來宣告 num為整數變數；如果想同時宣告數個整數變數可以這樣寫：

```
int num1,num2,num3;  // 同時宣告 num1,num2,num3 為整數變數
```

#### 變數規則 2：資料型態

資料不可能只有整數，因此除了整數型態(int) 之外還有：

* 長整數 (long)
* 短整數 (short)
* 浮點數 (float)
* 倍精度浮點數 (double)

除了數值外，還有字元 (char) 字串 (String) 的資料型態；這些將於第三章有詳細的介紹。

#### 變數規則 3：變數名稱

通常變數會以其代表的意義來取名（如 num代表數字、sum代表總和），但不能使用到 Java 的關鍵字。\
另外盡量不要取a,b,c 等簡單的英文字母代表變數，因為如果程式越大，宣告的數量越多，將會增加閱讀及除錯的困難度。

#### 變數規則 4：名稱限制

不能有空白字元，第一個字元不能是數字。\
另外大小寫也有區分：Num 與 num 會被視為不同的變數。

#### 變數使用：變數預設

預設方式為為把等號 (=) 右邊的值設定給左邊的變數存放。

例如 n=20  代表把整數20設定給變數n存放。\
ch=’a’ 代表將字元 ‘a’ 設定給字元變數ch存放。

有三種方式進行設值（視寫程式時的需求來使用）

1. 宣告時設值：

   ```
   int num=5;
   ```
2. 宣告後設值

   ```
   int num,num2; num=1; num=5;
   ```
3. 在程式中適當位置宣告並設值：

```
 for(int num=1; num<=10; num++)
{
  ...
 }
```

### 二、輸出

左右括號之間的內容就是欲顯示在螢幕上的內容，稱為引數，\
引數可以是字元、字串、數值或運算式，引數之間可以以加號區隔。

若要印出字串，必須以一對雙引號 ( ”  ” ) 包住字串。

若要印出數值，則直接將變數填入。

如下方範例：

```
System.out.print("I have "+num+" books");
```

{% hint style="info" %}
另外此處的 加號「＋」是 合併連結 的意思，而不是運算用途。
{% endhint %}

### 三、識別字與關鍵字

#### 1. 識別字 (identifier)

> 變數、類別或者是函數的名稱為識別字。\
> 他是使用者自行定義的文字，由英文大小寫、數字或底線組合而成。

* 識別字名稱不能使用到 Java 關鍵字，第一個字母也必須是英文字母或底線 “\_”
* **空白字元**及**特殊符號**，如 # $ @ 不能出現在識別字內
* 識別字有大小寫之分，Num與num；Pi與pi 會被看成不同變數
* 變數與函數時常為 英文小寫字母開始。
* **類別** 為英文大寫字母開始。
* **常數** 為全部字元皆英文大寫。

#### 2. 關鍵字 (keyword)

> 關鍵字是編譯程式本身所使用的識別字。\
> 我們不能更改或重複定義它們。\
> 因此自行定義的函數或者變數名稱都不能與 Java 的關鍵字相同。

以下為 Java 關鍵字 ([表格來源](https://zh.wikibooks.org/zh-tw/Java/%E5%85%B3%E9%94%AE%E5%AD%97))

| 類別           | 關鍵字              | 說明         |
| ------------ | ---------------- | ---------- |
| 訪問控制         | private          | 私有的        |
| protected    | 受保護的             |            |
| public       | 公共的              |            |
| 類、方法和變量修飾符   | abstract         | 聲明抽象       |
| class        | 類                |            |
| extends      | 擴允,繼承            |            |
| final        | 最終值,不可改變的        |            |
| implements   | 實現（接口）           |            |
| interface    | 接口               |            |
| native       | 本地，原生方法（非Java實現） |            |
| new          | 新,創建             |            |
| static       | 靜態               |            |
| strictfp     | 嚴格,精準            |            |
| synchronized | 執行緒,同步           |            |
| transient    | 短暫               |            |
| volatile     | 易失               |            |
| 程序控制語句       | break            | 跳出循環       |
| case         | 定義一個值以供switch選擇  |            |
| continue     | 繼續               |            |
| default      | 默認               |            |
| do           | 運行               |            |
| else         | 否則               |            |
| for          | 循環               |            |
| if           | 如果               |            |
| instanceof   | 實例               |            |
| return       | 返回               |            |
| switch       | 根據值選擇執行          |            |
| while        | 循環               |            |
| 錯誤處理         | assert           | 斷言表達式是否為真  |
| catch        | 捕捉異常             |            |
| finally      | 有沒有異常都執行         |            |
| throw        | 拋出一個異常對象         |            |
| throws       | 聲明一個異常可能被拋出      |            |
| try          | 捕獲異常             |            |
| 包相關          | import           | 引入         |
| package      | 包                |            |
| 基本類型         | boolean          | 布爾型        |
| byte         | 字節型              |            |
| char         | 字符型              |            |
| double       | 雙精度浮點            |            |
| float        | 單精度浮點            |            |
| int          | 整型               |            |
| long         | 長整型              |            |
| short        | 短整型              |            |
| null         | 空                |            |
| 變量引用         | super            | 父類,超類      |
| this         | 本類               |            |
| void         | 無返回值             |            |
| 保留關鍵字        | goto             | 是關鍵字，但不能使用 |

### 四、提高程式可讀性

* 將程式加上註解
* 有意義的變數名稱
* 適當的空行
* 適當的縮排，使同一層級的敘述對齊

另外註解可以以「 // 」 符號；或者於「/\*    \*/」符號之間。

如以下範例：

```
int num1,num2,num3;  // 同時宣告 num1,num2,num3 為整數變數
char ch;    /* 宣告ch為字元 */
```


# 認識變數與資料型態

> Java 變數是利用宣告方式，將記憶體某個區塊保留下來供程式使用。 可以宣告這個區塊記載的資料為 整數、字元、浮點數或是其他類型的型態，以便作為變數的儲存空間使用。

### 一、資料型態

資料型態可分為「原始資料型態」與 「非原始資料型態」。

原始資料型態也稱基本資料型態：包括整數與浮點數等型態。\
非原始資料型態是以一種特殊方式指向變數的實體；類似於C語言的指標，在 Java 稱之為參考。

如果變數值不會變動，可以直接利用 final 關鍵字將變數宣告成 final，來防止變數在程式其他地方被修改，下方為範例：

```
final 資料型態 變數名稱 = 值
```

{% hint style="info" %}
再次注意：使用 final 設定初值後就不會再改變，我們稱之為常數(constant)
{% endhint %}

舉例來說：計算圓面積，可宣告圓周率 PI=3.1415926 為常數，以確保不小心誤改；

```
final double PI=3.1415926;
```

{% hint style="info" %}
通常我們會以英文大寫字母為常數名稱命名，另外final 也有其他用途；他可以終止繼承。
{% endhint %}

無論在哪一個作業系統，Java 裡相同的資料型態均具有相同的位元組。

| 資料型態    | 位元組 | 初始值      | 範圍                                         |
| ------- | --- | -------- | ------------------------------------------ |
| long    | 8   | 0L       | -9223372036854775808 ～ 9223372036854775807 |
| int     | 4   | 0        | -2147483648 ～ 2147483647                   |
| short   | 2   | 0        | -32768 ～ 32767                             |
| byte    | 1   | 0        | -128 ～ 127                                 |
| char    | 2   | ‘\u0000’ | <p>0～ 65535<br>(‘\u0000’ – ‘\uffff’)</p>   |
| boolean | 1   | false    | ture 或 false                               |
| float   | 4   | 0.0F     | -3.4E38 ～3.4E38                            |
| double  | 8   | 0.0D     | -1.7E308 ～1.7E308                          |

### 二、程式實作

試試看以下的程式碼：

```
package ch03;

public class ch03 {

	public static void main(String[] args) {

		long num=32967359818;
		System.out.println("num= "+num);

	}

}
```

這是一個錯誤的範例，32967359818 超出int 型態，即使我們已經將型態設為long。\
那麼該如何解決呢？

只要在指數值後面加上一個大寫L 即可 (小寫亦可，易與1混淆，因此不建議)

```
long num=32967359818L;
```

這時再次編譯試試，即可成功編譯。


# 溢位

### 一、介紹

Java 提供 long、int、short 及 byte 四種型態最大最小值代碼；\
要使用某個型態的代碼，必須先指定該型態所在的類別庫以及該型態所屬的類別；

以下為長整數最大值的代碼示範：

![java.lang 類別庫太常使用，所以預設會將它載入，可以直接省略](/files/-M4gaMLMtu3IYhcjHwbz)

| <p><br> long</p> | int                  |                   |
| ---------------- | -------------------- | ----------------- |
| 所屬類別             | java.lang.Long       | java.lang.Integer |
| 最大值代碼            | MAX\_VALUE           | MAX\_VALUE        |
| 最大值              | 9223372036854775807  | 2147483647        |
| 最小值代碼            | MIN\_VALUE           | MIN\_VALUE        |
| 最小值              | -9223372036854775808 | -2147483648       |

|       | shore           | byte           |
| ----- | --------------- | -------------- |
| 所屬類別  | java.lang.Short | java.lang.Byte |
| 最大值代碼 | MAX\_VALUE      | MAX\_VALUE     |
| 最大值   | 32767           | 127            |
| 最小值代碼 | MIN\_VALUE      | MIN\_VALUE     |
| 最小值   | -32768          | -128           |

### 二、程式實作

以下範例：分別宣告包含java.lang 與省略，編譯看看結果是否正確。

#### 1. 輸出最大值與最小值

```
package ch03;

public class ch03_2 {

	public static void main(String[] args) {
		long lmax=java.lang.Long.MAX_VALUE;
		int imax=java.lang.Integer.MAX_VALUE;
		short smax=Short.MAX_VALUE;
		byte bmax=Byte.MAX_VALUE;

		System.out.println("Max value of long : "+lmax);
		System.out.println("Max value of int : "+imax);
		System.out.println("Max value of short : "+smax);
		System.out.println("Max value of byte : "+bmax);

//以上為最大值

		long lmin=java.lang.Long.MIN_VALUE;
		int imin=java.lang.Integer.MIN_VALUE;
		short smin=Short.MIN_VALUE;
		byte bmin=Byte.MIN_VALUE;

		System.out.println("Min value of long : "+lmin);
		System.out.println("Min value of int : "+imin);
		System.out.println("Min value of short : "+smin);
		System.out.println("Min value of byte : "+bmin);

//以上為最小值
	}

}
```

![OUTPUT](/files/-M4gacO61dwuLMiSM1Tk)

#### 2. 當輸出值溢位

以下透過上方介紹過的最大值做溢位的示範：

```
public class ch03_3 {

	public static void main(String[] args) {
		int i=Integer.MAX_VALUE;  //在此將i設為整數最大值
		int sum;

		System.out.println("i= "+i);  //先印出i 值

		sum=i+1;
		System.out.println("i+1= "+sum);  //印出 i+1 的值

		sum=i+2;
		System.out.println("i+2= "+sum);  //印出 i+2 的值
	}

}
```

OUTPUT： \
i= 2147483647 \
i+1= -2147483648 \
i+2= -2147483647

![](/files/-M4gapquqOHOEafgb2fW)

{% hint style="info" %}
溢位發生：

當整數的大小超過可以表示的範圍，而程式又沒有做數值範圍的檢查時，這個整數所輸出的值將發生紊亂，且不是預期中的執行結果，稱為「溢位」（overflow）。
{% endhint %}

{% hint style="success" %}
解決方法：加上數值範圍的檢查功能，或使用更大的表示範圍之資料型態。
{% endhint %}

#### 3. 解決溢位發生

```
package ch03;

public class ch03_3_2 {

	public static void main(String[] args) {
		int i=Integer.MAX_VALUE;  //將i設為整數最大值

		System.out.println("i= "+i);  // 印出i 值

		System.out.println("i+1= "+(i+1)); // 會發生溢位

		System.out.println("i+2= "+(i+2L)); //加上大寫L 強制型態轉換
		System.out.println("i+3= "+((long)i+3)); // 強制轉換成長整數型態

	}

}
```

OUTPUT： \
i= 2147483647 \
i+1= -2147483648 //發生溢位 \
i+2= 2147483649 //無溢位 \
i+3= 2147483650 //無溢位


# 更多資料型態

> 此篇介紹字元型態、浮點數與倍精度浮點數型態、布林型態。

### 一、字元型態

在電腦的世界裡，所有的文字、數值都只是一連串的 0 與 1 所組成。\
Java 所使用的 Unicode 標準萬國碼，就是一種可以解決不同語系之間，資訊交流時所發生問題的一種技術。

舉例來說，Unicode 中的大寫 ‘G’ 編碼為 71。\
我們利用不同的方法印出字元’G’：

```
package ch03;

public class ch03_4 {

	public static void main(String[] args) {
		char ch1=71;  //設定字元變數ch1 等於編碼71的字元
		char ch2='G'; //設定字元變數ch2 等於'G'
		char ch3='\u0047'; //以16進位值設定字元變數ch3

		System.out.println("ch1= "+ch1);
		System.out.println("ch2= "+ch2);
		System.out.println("ch3= "+ch3);

	}

}

```

### 二、設定字元

OUTPUT：\
ch1= G\
ch2= G\
ch3= G

設定變數為某個字元時，字元要以一對單引號 ( ‘ ) 括起來。

在特定的英文字母前，加上反斜線「 \ 」，極為跳脫序列；而反斜線稱為跳脫字元。

| 跳脫序列 | 所代表的意義 | 跳脫序列   | 所代表的意義        |
| ---- | ------ | ------ | ------------- |
| \f   | 換頁     | \\\\   | 反斜線           |
| \b   | 倒退一格   | \’     | 單引號           |
| \n   | 換行     | \”     | 雙引號           |
| \r   | 歸位     | \uxxxx | 16進位unicode字元 |
| \t   | 跳格     | \ddd   | 8進位unicode字元  |

實際帶做個範例：

```
package ch03;

public class ch03_4_2 {

	public static void main(String[] args) {
		char ch1='\"';      //將ch1 設為雙引號
		char ch2='\74';     //以8進位設定字元變數
		char ch3='\u003e';  //以16進位設定字元變數

		System.out.println(ch1+"Example"+ch1);
		System.out.println("\"Example\"");  //無使用變數，直接帶入跳脫序列
		System.out.println(ch2+"Time is money!"+ch3);


		char ch4=074;
		char ch5=0x3e;

		System.out.println(ch4+"Time is money!"+ch5);

	}

}
```

![OUTPUT](/files/-M4gdZ0pjVpUsldsjml6)

變數ch2與ch4效果同；ch3與ch5效果同。\
為變數設值時，若在數值前加上數字 0 ，表示該變數為以八進位值設值；\
若在數值前加上數字 0 與英文字母 x （大小寫均可），則是以16進位值設值。

{% hint style="info" %}
用 0x 表示的16進位值，沒有限制一定要以四個數字的方式輸出。
{% endhint %}

### 三、浮點數

> 帶有小數點的數值稱為實數，在 Java 這種資料型態稱為浮點數。
>
> 若浮點數需要以指數的形式來表示時，可用字母E或e來代表10的乘幂。\
> 例如 6.2e5 代表6.2 x 10^5

| <p><br>float</p> | double          |                        |
| ---------------- | --------------- | ---------------------- |
| 所屬類別             | java.lang.Float | java.lang.Double       |
| 最大值代碼            | MAX\_VALUE      | MAX\_VALUE             |
| 最大值              | 3.4028235E38    | 1.7976931348623157E308 |
| 最小值代碼            | MIN\_VALUE      | MIN\_VALUE             |
| 最小值              | 1.4E-45         | 4.9E-324               |

```
float num1=2.63e64;   //已超過float範圍
float num2=6.3;       //編譯錯誤，浮點數預設型態為double
double num3=6.3;      //編譯正確
```

### 四、布林型態

布林型態只有 ture 和 false，不能用0或1表示。

```
package ch03;

public class ch03_4_3 {

	public static void main(String[] args) {
		boolean status=false;
		System.out.println("status= "+status);

	}

}
```

OUTPUT：\
status= false


# 資料型態的轉換

資料型態的轉換可分為「自動型態轉換」及「強制型態轉換」。

### 一、自動型態轉換

* 轉換前的資料型態與轉換後的型態相容
* 轉換後的資料型態之表示範圍比轉換前的型態大
* 型態的轉換只限該行敘述，不影響原先變數的型態定義
* 保證資料的精確度
* 這種轉換方式稱為「擴大轉換」

字元與整數可以使用自動型態轉換；整數與浮點數亦相容；

boolean 布林型態只能存放 true 或 false，因此與整數及字元不相容，不能轉換。

```
package ch03;

public class ch03_4_4 {

	public static void main(String[] args) {
		int a=45;
		float b=2.3f;

		System.out.println("a= "+a+" ,b= "+b);
		System.out.println("a/b= "+(a/b));

	}

}

```

OUTPUT：\
a= 45 ,b= 2.3\
a/b= 19.565218

{% hint style="info" %}
當兩個數值中有一個為浮點數時，運算結果會直接轉換為浮點數。

**當運算式中變數的型態不同時，會自動把較少的表示範圍轉換成較大的表示範圍後再做運算。**
{% endhint %}

### 二、強制型態轉換

強制型態轉換是直接撰寫在程式碼中，所以也稱為「顯性轉換」。

> 資料型態的強制性轉換語法：
>
> ```
> （欲轉換的資料型態）變數名稱 ;
> ```

```
package ch03;

public class ch03_4_5 {

	public static void main(String[] args) {
		int a=25;
		int b=9;

		System.out.println("a= "+a+ ",b= "+b);
		System.out.println("a/b= "+a/b);

                // 將a轉換成浮點數之後，再除以b
		System.out.println("(float)a/b= "+(float)a/b);

	}

}

```

OUTPUT：\
a= 25,b= 9\
a/b= 2\
(float)a/b= 2.7777777

> 除了 (float)a/b 的寫法之外，以下兩種寫法也成立：
>
> (1)  a / (float) b   //將整數b強制轉換成浮點數，再以整數a除之。
>
> (2)  (float) a / (float) b  //將整數a,b 同時強制轉換成浮點數後運算。

{% hint style="info" %}
若是將一個能表示較大範圍之資料型態的變數，設給範圍較小之資料型態的變數時，稱為「縮小轉換」。

在縮小轉換的過程可能會漏失資料的準確性。
{% endhint %}

### 三、由鍵盤輸入資料 Scanner

我們先了解撰寫的基本架構：

```
import java.util.Scanner;
public class class_name   //類別名稱
{
	public static void main(String[] args)
        {
	   	Scanner 物件名稱=new Scnner (System.in); //宣告 Scanner 類別的變數
		資料型態 變數名稱 a;                      //宣告變數
                ... ...
		變數名稱=物件名稱.相對應型態的Scanner 類別函數; //讀入資料至變數
		... ...

	}

}
```

以下我們實例，並結合先前講到的print與println的差異：

```
package ch03;

import java.util.Scanner;

public class ch03_5 {

	public static void main(String[] args) {
		Scanner sc=new Scanner (System.in);  //宣告 Scanner 類別的物件名為：sc
		System.out.print("請輸入姓名");
		String str;                          //宣告變數str
                str=sc.nextLine();                   //輸入字串，給str存放
		System.out.println("歡迎 "+str);

	}

}

```

OUTPUT：\
請輸入姓名 ***4x***   // ***斜線粗體***&#x70BA;輸入的字串\
歡迎 4x

{% hint style="info" %}
在使用Scanner 類別時，我們必須先建立Scanner 類別的物件，再利用所建立的物件呼叫相對應的函數。
{% endhint %}

常用的相對應型態之函數

| 資料型態 | 相對應的函數 | 資料型態 | 相對應的函數 |
| ---- | ------ | ---- | ------ |

| byte | nextByte() | float | nextFloat() |
| ---- | ---------- | ----- | ----------- |

| short | nextShort() | double | nextDouble() |
| ----- | ----------- | ------ | ------------ |

| int | nextInt() | String | next() |
| --- | --------- | ------ | ------ |

| long | nextLong() | nextLine() |
| ---- | ---------- | ---------- |

### 四、Scanner 小技巧

輸入多項資料時，可以只宣告一個Scanner 類別的物件，再用Scanner 類別提供的成員函數，將鍵盤中輸入的資料指定給不同型態的變數存放，不需要宣告多個Scanner 類別物件。\
如以下片段範例：

```
Ssanner scn=new Scanner(System.in);  //宣告Scanner類別的物件
int i;                               //宣告整數變數
double d;                            //宣告倍精度浮點數
i=scn.nextInt();                     //輸入整數數值，給i存放
d=scn.nextDouble();                  //輸入倍精度浮點數數值，給d存放
```

### 五、next()與nextLine()

{% hint style="info" %}
在輸入字串時，可以使用 next() 或 nextLine() 函數，由於Scanner 物件遇到空白字元即會傳回空白字元之前的資料，因此若是字串中不確定會帶有空白字元的情況下，建議使用 nextLine()，反則輸入的資料將被截斷
{% endhint %}

```
package ch03;
import java.util.Scanner;
public class ch03_5_2 {

	public static void main(String[] args) {
		Scanner scn=new Scanner (System.in);  //宣告 Scanner 類別的物件名為：scn
		String name;
		int age;

		System.out.print("請輸入姓名");
		name=scn.nextLine();                   //輸入字串，給name存放

                System.out.print("請輸入年齡");
                age=scn.nextInt();                   //輸入整數，給age存放

		System.out.print("歡迎， "+name+"， 您目前 ");
		System.out.println(age+" 歲。");

	}

}
```

OUTPUT：\
請輸入姓&#x540D;***scheng***  // ***斜線粗體***&#x70BA;輸入的字串\
請輸入年&#x9F61;***19***\
歡迎， scheng， 您目前 19 歲。

### 六、charAt()

{% hint style="info" %}
Scanner類別並沒有提供字元輸入相對應型態的函數，可以利用 next() 取得字串後，再利用 charAt(0) 取出字串中第0個字元即可得到字元。
{% endhint %}

```
package ch03;
import java.util.Scanner;
public class ch03_5_3 {

	public static void main(String[] args) {
		Scanner scn=new Scanner (System.in);  //宣告 Scanner 類別的物件名為：scn

		System.out.print("請輸入姓名");
		String str;                          //宣告變數str
                str=scn.nextLine();                  //輸入字串，給str存放

                char ch;
                ch=str.charAt(2);                    //取出字串第2個字元
                System.out.print(ch);                //印出第二個字元

	}

}
```

OUTPUT： \
請輸入姓&#x540D;***scheng*** // ***斜線粗體***&#x70BA;輸入的字串 \
h //第0個字元為 ‘s’ ；第1個字元為 ‘c’ 以此類推。


# 運算子、運算式

> 程式是由許多敘述組成的，而敘述的基本單位是運算元與運算子。 透過本章學習如何進行運算式之資料型態的轉換。

### 一、認識運算式

* 運算式由運算元（operand）與運算子（operator）組成
* 運算元可以是變數或是常數
* 運算子就是數學上的運算符號
* 如「+」、「-」、「\*」、「/」等

![根據運算子所使用的類別可分為：設定、算數、關係、邏輯、條件、逗號以及遞增與遞減運算子。](/files/-M4ggw8FVfjwM6VEJfPz)

![將變數設值可使用設定運算子](/files/-M4gh27sXM6FUyR0AFVf)

{% hint style="info" %}
等號（=）並不是「等於」，而是「設定」
{% endhint %}

### 二、一元運算子

一元運算子只需要一個運算元

```
+6; // 表示正6
~a; // 表示取a的1補數
x=-y; // 表示負y的值設定給變數x存放
!a; // a的NOT運算，若a為0，則!a為1，若a不為0，則!a為0
```

使用！運算元必須為 boolean 型態

| 一元運算子 | 意義      |
| ----- | ------- |
| +     | 正號      |
| –     | 負號      |
| !     | NOT 否定  |
| \~    | 取 1 的補數 |

### 三、算術運算子

| 算術運算子 | 意義 |
| ----- | -- |

| + | 加法 |
| - | -- |

| – | 減法 |
| - | -- |

| \* | 乘法 |
| -- | -- |

| / | 除法 |
| - | -- |

| % | 取餘數 |
| - | --- |

{% hint style="info" %}
在除法的部分，要注意資料型態的變化。
{% endhint %}

### 四、關係運算子與 if 敘述

如果括號中的條件判斷成立，就會執行後面的敘述；若是條件判斷不成立時，則後面的敘述就不會執行。

| 關係運算子 | 意義   |
| ----- | ---- |
| >     | 大於   |
| <     | 小於   |
| >=    | 大於等於 |
| <=    | 小於等於 |
| ==    | 等於   |
| !=    | 不等於  |

### 五、遞增與遞減運算子

善用遞增與遞減運算子可提高城市的簡潔程度。

| 遞增與遞減運算子 | 意義       |
| -------- | -------- |
| ++       | 遞增，變數值加一 |
| —        | 遞減，變數值減一 |

> a++ 會先執行整個敘述後再將a的值加 1
>
> ++a則先把a的值加 1 後再執行整個敘述

### 六、邏輯運算子

&& ：運算子前後的兩個運算元之傳回直接為真，運算的結果才會為真。

|| ：運算子前後只要一個為真，運算的結果就會為真。

```
a>0 && b>0 // 兩個運算元皆為真，運算結果才為真
a>0 || b>0 // 兩個運算元只要一個為真，運算結果就為真
```

| 邏輯運算子 | 意義    |
| ----- | ----- |
| &&    | AND 且 |
| \|\|  | OR 或  |

AND ：兩者都要為真，運算結果才會為真。

| AND | T | F |
| --- | - | - |
| T   | T | F |
| F   | F | F |

OR ：其中一個為真，運算結果就為真。

| OR | T | F |
| -- | - | - |
| T  | T | T |
| F  | T | F |

### 七、括號運算子

( ) 可以提高括號內運算式的優先處理順序

![優先順序數字愈小，表示優先順序愈高。](/files/-M4gi60soO9GHLehlTJK)

![範例](/files/-M4gi9k93lRWWcxIlwzA)

### 八、運算式

運算式可以由數值、變數或是其他運算元與運算子所組合而成。

| 運算子 | 用法    | 說明          | 意義     |
| --- | ----- | ----------- | ------ |
| +=  | a+=b  | a+b 的值放到 a  | a=a+b  |
| -=  | a-=b  | a-b 的值放到 a  | a=a-b  |
| \*= | a\*=b | a\*b 的值放到 a | a=a\*b |
| /=  | a/=b  | a/b 的值放到 a  | a=a/b  |
| %=  | a%=b  | a%b 的值放到 a  | a=a%b  |

### 九、運算式的型態轉換

允許資料型態暫時轉換成其他型態的情況發生，以不流失資料為前提，即可做不同型態轉換。

根據以下規則來處理型態的轉換：

* 佔用位元組較少的型態轉換成位元組較多的型態
* 字元型態會轉換成short型態（字元會取其unicode碼）
* short型態（2 bytes）遇上int型態（4 bytes），會轉換成int型態
* int型態會轉換成float型態
* 運算式中若某個運算元的型態為double，則另一個運算元也會 轉換成double型態
* 布林型態不能轉換成其它的型態


# 程式範例

> 程式是由許多敘述組成的，而敘述的基本單位是運算元與運算子。 透過本章學習如何進行運算式之資料型態的轉換。

### 一、認識運算式

* 運算式由運算元（operand）與運算子（operator）組成
* 運算元可以是變數或是常數
* 運算子就是數學上的運算符號
* 如「+」、「-」、「\*」、「/」等

```
public class ch04_1 {

	public static void main(String[] args) {
		int apple=19;

		System.out.println("計算之前, apple= "+apple);  //首先印出apple的值
		apple=apple+1;   // 將apple的值加一再設定給apple存放
		System.out.println("計算之後, apple= "+apple); //印出計算後的apple值

	}
```

OUTPUT：\
計算之前, apple= 19\
計算之後, apple= 20

### 二、一元運算子

一元運算子只需要一個運算元\
使用！運算元必須為 boolean 型態

```
public class ch04_2 {

	public static void main(String[] args) {
		boolean a=true;  //宣告布林變數a為真
		byte b=Byte.MIN_VALUE;  //宣告變數b為最小值

		System.out.println("a= "+a+",!a= "+(!a)); //分別印出a與!a 的值
		System.out.println("b= "+b+",~b= "+(~b)); //分別印出b與~b 的值

	}
```

OUTPUT：\
a= true,!a= false\
b= -128,\~b= 127

### 三、關係運算子與 if 敘述

如果括號中的條件判斷成立，就會執行後面的敘述；若是條件判斷不成立時，則後面的敘述就不會執行。

```
public class ch04_3 {

	public static void main(String[] args) {
		if(19>18)
			System.out.println("19>18成立");
		if(true)
			System.out.println("判斷為真，代表印得出字串");
		if(false)
			System.out.println("判斷為假，代表無法印出字串");
		if(19==18)
			System.out.println("如果數值19等於18 才會印出這個字串");
	}
```

OUTPUT：\
19>18成立\
判斷為真，代表印得出字串

### 四、遞增與遞減運算子

> a++ 會先執行整個敘述後再將a的值加 1
>
> ++a則先把a的值加 1 後再執行整個敘述

```
public class ch04_4 {

	public static void main(String[] args) {
		int a=5,b=5,c;

		System.out.println("a= "+a+", b= "+b); //首先印出a與b

		c=a++;   //相當於 c=a;a=a+1;
		System.out.println("c= "+c+", a= "+a); //印出a++

		c=++b;   //相當於 b=b+1;c=b;
		System.out.println("c= "+c+", b= "+b); //印出++b

	}
```

OUTPUT：\
a= 5, b= 5\
c= 5, a= 6\
c= 6, b= 6

### 五、邏輯運算子

{% hint style="info" %}
a>0 && b>0 // 兩個運算元皆為真，運算結果才為真&#x20;

a>0 || b>0 // 兩個運算元只要一個為真，運算結果就為真
{% endhint %}

```
public class ch04_5 {

	public static void main(String[] args) {
		int a=59;

		if((a<0) || (a>100))
			System.out.println("輸入成績錯誤");

		if((a<60) && (a>49))
			System.out.println("需要補考");

	}
```

OUTPUT：\
需要補考

### 六、運算式

運算式可以由數值、變數或是其他運算元與運算子所組合而成。

```
public class ch04_6 {

	public static void main(String[] args) {
		int a=2,b=6;

		System.out.println("計算 a+=b 前, a= "+a+", b= "+b);
		a+=b;
		System.out.println("計算 a+=b 後, a= "+a+", b= "+b);
	}
```

OUTPUT：\
計算 a+=b 前, a= 2, b= 6\
計算 a+=b 後, a= 8, b= 6

### 七、運算式的型態轉換

* 佔用位元組較少的型態轉換成位元組較多的型態
* 字元型態會轉換成short型態（字元會取其unicode碼）
* short型態（2 bytes）遇上int型態（4 bytes），會轉換成int型態
* int型態會轉換成float型態
* 運算式中若某個運算元的型態為double，則另一個運算元也會 轉換成double型態
* 布林型態不能轉換成其它的型態

```
public class ch04_7 {

	public static void main(String[] args) {
		char ch='X';
		short s=-5;
		int i=6;
		float f=9.7f;
		double d=1.76;

		System.out.print("(s*ch)-(d/f)*(i+f)= ");
		System.out.println((s*ch)-(d/f)*(i+f));
	}

```

OUTPUT：\
(s\*ch)-(d/f)\*(i+f)= -442.8486598152212

![運算式的型態轉換圖解](/files/-M4gj8lHHpaOOMjrFeQp)


# 選擇性敘述

### 一、程式的結構

* 循序性結構（sequence structure）：由上至下 （top to down）的敘述方式。
* 選擇性結構（selection structure）：先判斷條件是否成立，再決定執行哪些敘述。
* 重複性結構（iteration structure）：判斷條件是否成立，並決定程式段落的執行次數。

{% hint style="info" %}
三者的共通點為：只有一個進入點，也只有一個出口。
{% endhint %}

{% hint style="info" %}
選擇性結構包括 if、if-else、switch
{% endhint %}

### 二、if 敘述

i如果主體中只有一個敘述，則可以省略左右大括號。

當條件成立(true)，就會逐一執行大括號內的敘述。

```
if(判斷條件)
{
     敘述主體;
}
```

![](/files/-M4gjzPexWHHz56hD7sT)

### 三、if-else 敘述

當條件成立(true)，就會執行敘述主體；不成立時，則執行else 的敘述主體。以下為格式

```
 if(判斷條件)
{
     敘述主體A;
}
else
{
     敘述主體B;
}
```

![](/files/-M4gk5r9vwNrWLGcyZ-s)

```
public class ch05_1 {

	public static void main(String[] args) {
		int a=10;

		if (a%2==0)  // 如果可被2整除，判斷奇偶數
			System.out.println(a+" 為偶數");
		else
			System.out.println(a+" 為奇數");

	}
```

OUTPUT：\
10 為偶數

### 四、巢狀 if 敘述

if 敘述中又包含其它 if 敘述。以下為格式

```
if(判斷條件1)
{
  if(判斷條件2)
  {
     敘述主體;
  }
  ...
  其它敘述;
}
```

當判斷條件1為真，就會執行他敘述中的 判斷條件2；\
若判斷條件2也為真時，就會執行判斷條件2 的敘述主體。<br>

![](/files/-M4gkGbm0W9QQzNQVSrk)

```
public class ch05_2 { //使用 ch05_1 做延伸

	public static void main(String[] args) {
		int a=10;
		if(a>0)
		{
			if (a%2==0)  // 如果可被2整除，判斷奇偶數
				System.out.println(a+" 為大於0的偶數");
			else
				System.out.println(a+" 為大於0的奇數");
		}
	}
```

OUTPUT：\
10 為大於0的偶數

{% hint style="info" %}
過多的巢狀 if 反而會造成邏輯判斷的混淆，拖累執行的速度
{% endhint %}

### 五、條件運算子

進階用法，直接透過運算子可以代替if-else敘述

```
整數 ＝ 判斷條件 ？ 運算式1 ： 運算式2
```

```
public class ch05_3 {

	public static void main(String[] args) {
		int a=19,b=10,max;

		max=(a>b)?a:b;  // 當a>b時，max=a，否則 max=b

		System.out.println("a= "+a+", b= "+b);
		System.out.println(max+"為最大的數");

	}
```

OUTPUT：\
a= 19, b= 10\
19為最大的數

{% hint style="info" %}
使用條件運算子可以使程式碼較簡潔，執行速度也較有效率
{% endhint %}


# 迴圈

> 迴圈可以重複執行相同程式的片段，還可以讓程式結構化。\
> &#x20;這章學習如何利用不同的結構，撰寫更靈活，操作更方便。

迴圈包括 for、while、do while

### 一、for 迴圈

for迴圈：知道執行次數。

```
for(設定迴圈初值; 判斷條件; 設定增減量)
{
     迴圈主體;
}
```

![](/files/-M4glFQOF6P5Sr8vefek)

```
public class ch05_4 {

	public static void main(String[] args) {
		int i,sum=0;

		for(i=1; i<=10; i++)
			sum+=i;
		System.out.println("1+2+...+10= "+sum);

	}
```

OUTPUT：\
1+2+…+10= 55

#### 補充：在迴圈裡宣告的變數只是區域變數，跳出迴圈後，這個變數將不再使用。 透夠以下範例認識：

```
public static void main(String[] args) {
		int sum=0;

		for(int i=1; i<=3; i++) //在迴圈內宣告變數i
		{
			sum+=i;
			System.out.println("i="+i+", sum="+sum);
		}
	}
```

OUTPUT：\
i=1, sum=1\
i=2, sum=3\
i=3, sum=6

也可以同時宣告數個變數，並且給予初值；\
要注意的是，這些變數必須為相同的型態；\
換句話說：關鍵字只能出現一次。

```
for (int i=0,j=0; 判斷條件; 設定增減量)
```

### 二、while 迴圈

while 迴圈：無事先得知執行次數。

```
設定迴圈初值; 
while(判斷條件)
{
     迴圈主體;
     設定增減量;
}
```

{% hint style="info" %}
在while 迴圈中，通常判斷條件為邏輯運算子的運算式。
{% endhint %}

![](/files/-M4glduRsgNPgUujlnnl)

```
public class ch05_6 {

	public static void main(String[] args) {
		int n=0,sum=0;
		while(sum<20)
		{
			System.out.println("n="+n+", sum="+sum);
			n++;
			sum+=n;
		}

	}
```

OUTPUT：\
n=0, sum=0\
n=1, sum=1\
n=2, sum=3\
n=3, sum=6\
n=4, sum=10\
n=5, sum=15

### 三、do while 迴圈

**與while 不同在於，while會先判斷條件真假，而do while 是先執行後判斷真假**。\
因此至少會執行一次迴圈。

```
設定迴圈初值;
do
{
   迴圈主體;
   設定增減量;
}while(判斷條件)
```

![](/files/-M4glnH9knSLkLyybZlP)

```
import java.util.Scanner;
public class ch05_7 {

	public static void main(String[] args) {
		Scanner scn=new Scanner (System.in);
		int n,i=1,sum=0;

		do{
			System.out.print("請輸入值");
			n=scn.nextInt();
		}while (n<1); //輸入值要為大於0，否則將會一直印字串
		do
			sum+=i++; // 計算sum=sum+i, i值再加1
		while(i<=n);

		System.out.println("1+2+...+"+n+"="+sum);

	}
```

OUTPUT：\
請輸入值-1\
請輸入值-3\
請輸入值0  // 輸入 -1 -3 0 皆小於1 所以重複要求輸入\
請輸入值5  //5>1 條件正確\
1+2+…+5=15

### 四、巢狀迴圈

迴圈敘述中又有其他迴圈敘述。

#### 99乘法表

```
public static void main(String[] args) {
		int i,j;
		for (i=1; i<=9; i++)  //外迴圈
		{
			for (j=1; j<=9; j++)  //內迴圈
				System.out.print(i+"*"+j+"="+(i*j)+"\t");
			System.out.println();
		}

	}
```

![OUTPUT](/files/-M4glzH63wbj_105rhhB)


# 迴圈的跳離

{% hint style="info" %}
跳離敘述會增加除錯與閱讀的困難，不得已情況下才使用。
{% endhint %}

### **一、break 敘述**

可以強迫跳離迴圈。

```
for(初值設定; 判斷條件; 設增減量)
{
    敘述1;
    敘述2;
     ...
    break; //僅執行到此行
     ...
    敘述3; //此敘述不會被執行
}
     ...
```

```
public class ch05_9 {

	public static void main(String[] args) {
		int i;

		for (i=1; i<=10; i++)
		{
			if(i%3==0)
				break;
			System.out.println("i="+i);  //此行執行結果並無印出
		}
		System.out.println("當迴圈中斷,i="+i);

	}
```

OUTPUT：\
i=1\
i=2\
當迴圈中斷,i=3

### **二、continue 敘述**

可以強迫跳到迴圈開頭。

```
for(初值設定; 判斷條件; 設增減量)
{
    敘述1;
    敘述2;
     ...
    continue; //僅執行到此行
     ...
    敘述3; //此敘述不會被執行
}
     ...
```

#### 試比較 **break** 與 continue 執行結果：

```
public class ch05_10 {

	public static void main(String[] args) {
		int i;

		for (i=1; i<=10; i++)
		{
			if(i%3==0)
				continue;
			System.out.println("i="+i);
		}
		System.out.println("當迴圈中斷,i="+i);

	}
```

OUTPUT：\
i=1\
i=2\
i=4\
i=5\
i=7\
i=8\
i=10\
當迴圈中斷,i=11

#### 三、switch敘述

```
switch(運算式)
{
   case 選擇值1:
        敘述主體1;
        break;
   case 選擇值2:
        敘述主體2;
        break;
         ...
   case 選擇值n:
        敘述主體n;
        break;
   default:     //若上述case 皆不適合，則會執行default的敘述
        敘述主體;
}
```

{% hint style="info" %}
選擇值可為 字元、字串或是整數

如果沒有加上break，則會一直執行到switch 敘述的尾端。
{% endhint %}

![](/files/-M4gn7WjHV-AnMpl76iP)

```
import java.util.Scanner;
public class ch05_11 {

	public static void main(String[] args) {
		Scanner scn=new Scanner (System.in);
		int a=50,b=20;

		System.out.print("a="+a+", b="+b+", 請輸入運算符號: ");
		char oper=scn.next().charAt(0); //取得第一個字元

		switch (oper)
		{
		case '+':
			System.out.println(a+"+"+b+"="+(a+b));
			break;
		case '-':
			System.out.println(a+"-"+b+"="+(a-b));
			break;
		case '*':
			System.out.println(a+"*"+b+"="+(a*b));
			break;
		case '/':
			System.out.println(a+"/"+b+"="+(a/b));
			break;
		default:
			System.out.println("不清楚此運算符號");
		}

	}
```

OUTPUT：\
a=50, b=20, 請輸入運算符號: +  // 試著輸入其他運算符號\
50+20=70


# 一維陣列

> 陣列可以存放相同的資料型態資料，並利用索引值存取其內容。

### 一、宣告方式

必須先宣告陣列後配置記憶體位置給陣列。

```
資料型態 陣列名稱[];
陣列名稱＝new 資料型態[數量];
```

```
int score[];   //宣告整數陣列score
score=new int[5]; //使陣列score 可存放5個整數的記憶體空間
```

#### 較簡潔的方式：

```
資料型態 陣列名稱[]＝new 資料型態[數量];
```

&#x20;`int score[ ];` 還有另外的宣告方式：

```
int[] score;
int  []score;
```

### 二、取得陣列長度 length

```
public class ch06_1 {

	public static void main(String[] args) {
		int i;
		int a[];
		a=new int[3];
		a[0]=9;
		a[1]=6;

		for(i=0; i<a.length; i++)
			System.out.print("a["+i+"]="+a[i]+",\t");
		System.out.println("字串長度："+a.length);


	}
```

OUTPUT：\
a\[0]=9, a\[1]=6, a\[2]=0, 字串長度：3

### 三、陣列設初值

```
陣列名稱[]＝{1,2,3};
```

```
public class ch06_2 {

	public static void main(String[] args) {
		int i;
		int a[]={10,20,30}; //自動依序指定給第0,第1,第2個元素


		for(i=0; i<a.length; i++)
			System.out.println("a["+i+"]="+a[i]);
		System.out.println("字串長度："+a.length);

	}
```

OUTPUT：\
a\[0]=10\
a\[1]=20\
a\[2]=30\
字串長度：3

### 四、尋找陣列中最大最小值

```
public class ch06_3 {

	public static void main(String[] args) {
		int i,min,max;
		int a[]={22,15,89,74,3};  //給定陣列初值
		min=max=a[0];   //先將min,max 分別設為陣列第一個元素
		for(i=0; i<a.length; i++)
                {
                    System.out.print(a[i]+" "); //印出陣列
                    if(a[i]>max) //如果第i個元素 比max大(一開始將max設為第0個元素)
			max=a[i]; // 則將a[i]設為max
		    if(a[i]<min)
			min=a[i];  //min 同理
		}
		System.out.println("\nmax= "+max);
		System.out.println("min= "+min);

	}
```

OUTPUT：\
22 15 89 74 3\
max= 89\
min= 3


# 二維與多維陣列

### 一、二維陣列

二維陣列更方便處理較複雜的運算。

```
資料型態 陣列名稱[][];
陣列名稱＝new 資料型態[列的個數][行的個數];  //而列的個數一定要填
```

```
int score[][];   //宣告整數陣列score
score=new int[2][4]; //使陣列score 可存放2列4行的記憶體空間
```

#### 較簡潔的方式：

```
資料型態 陣列名稱[][]＝new 資料型態[列個數][行個數]; //列的個數一定要填
```

int score \[2]\[4] 的圖表示意圖：

| <p>(0,0)<br>１５</p> | <p>(0,1)<br>２５</p> | <p>(0,2)<br>３０</p> | <p>(0,3)<br>１７</p> |
| ------------------ | ------------------ | ------------------ | ------------------ |
| <p>(1,0)<br>２２</p> | <p>(1,1)<br>５２</p> | <p>(1,2)<br>１０</p> | <p>(1,3)<br>８</p>  |

若要直接給陣列初值可以這麼做：（搭配以上圖表做示範）

```
int score[][]=
{   
  {15,25,30,17},
  {22,52,10,8}
};
```

### 二、取得二維陣列行數 length

> 陣列名稱.length&#x20;
>
> 陣列名稱\[列].length //可以取得陣列某列的個數

```
public class ch06_4 {

	public static void main(String[] args) {
		int i,j;
		int score[][]={{15,25,30,17},{22,52,10,8}};

		for(i=0; i<score.length; i++)
		{
		   for(j=0; j<score[i].length; j++) //這此運用到取得第i列的元素個數
		   {
		      System.out.println("score["+i+"]["+j+"]="+score[i][j]);
		   }
		}
		System.out.println("陣列行數："+score.length);
	}
```

OUTPUT：\
score\[0]\[0]=15\
score\[0]\[1]=25\
score\[0]\[2]=30\
score\[0]\[3]=17\
score\[1]\[0]=22\
score\[1]\[1]=52\
score\[1]\[2]=10\
score\[1]\[3]=8\
陣列行數：2

### 三、多維陣列

> 多維陣列如同一個立方體

#### 運用一維與二維的基礎，試撰寫從多維陣列中找出最大值與最小值：

```
public class ch06_5 {

	public static void main(String[] args) {
		int a[][][]={  //設定2x4x3的陣列
			     {
			        {15,85,36},
			        {30,14,37},
			        {47,23,96},
			        {19,39,51}
			      },
			      {
			        {22,16,51},
			        {97,30,12},
			        {68,77,26},
			         57,32,76}
			       }
			      };
		int i,j,k;
		int max=a[0][0][0],min=a[0][0][0];

		for(i=0; i<a.length; i++)
		  for(j=0; j<a[i].length; j++)
		    for(k=0; k<a[i][j].length; k++)
                     {
                        System.out.println("score["+i+"]["+j+"]["+k+"]="+a[i][j][k]);
                        if(max>a[i][j][k])
			 max=a[i][j][k];
			if(min<a[i][j][k])
			 min=a[i][j][k];
		      }
		System.out.println("max= "+max);
		System.out.println("min= "+min);

	}
```

![OUTPUT](/files/-M4gp_GRc9LDnlkA_RAq)


# 函數與引數

> 函數可以簡化主程式的結構，節省撰寫相同的程式碼
>
> method 譯為「函數」，亦有作者譯為「方法」

### 一、函數設定

```
public static 傳回值型態 函數名稱 (型態 引數1, 型態 引數2,...)
{
   程式敘述;         // 函數主體
   return 運算式;    // 函數主體
}
```

若不需傳遞引數到函數中，小括號內可省略；若無回傳值，則可省略return。

```
public static 傳回值型態 函數名稱 ()
{
   程式敘述;         // 函數主體
}
```

```
public class ch07_1 {

	public static void main(String[] args)
	{
		star();    //呼叫star函數
		System.out.println("使用函數印星星");
		star();    //呼叫star函數

	}

	public static void star()  //star函數
	{
		for(int i=0; i<20; i++)
			System.out.print("*");
		System.out.print("\n");
	}
```

OUTPUT：\
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\
使用函數印星星\
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*

### 二、引數資料型態

> 若有回傳值，必須指定回傳的資料型態；若有引數要回傳到函數內，括號內需填上其資料型態。

```
public class ch07_2 {

	public static void main(String[] args)
	{
		int i;
		i=star(9);
		System.out.println(i+"個星星，使用函數印出");
	}

	public static int star(int n)  //注意資料型態
	{
		for(int i=0; i<2*n; i++)
			System.out.print("*");
		System.out.print("\n");
		return 2*n;
	}
```

OUTPUT：\
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\
18個星星，使用函數印出

### 三、傳遞多個引數

> 傳遞一個以上的引數，只要在括號內填上即可

```
public class ch07_3 {

	public static void main(String[] args) {
		double num;
		num=show(8,4);
		System.out.println("長度= "+num);
	}

	public static double show(int m, int n)
	{
		return Math.sqrt(m*m+n*n);
	}
```

OUTPUT：\
長度= 8.94427190999916

{% hint style="info" %}
sqrt(n) 的用法是將引數n開根號
{% endhint %}

### 四、函數傳值（pass by value）

> 所有基本資料型態變數，傳遞到函數的方法都是「傳值（pass by value）」

```
public class ch07_4 {

	public static void main(String[] args) {
		int num=5;
		add10(num);
		System.out.println("在主程式 main(), num= "+num);
	}
	public static void add10(int value)
	{
		value+=10;
		System.out.println("在函數add10(), 值為 "+value);
	}
```

OUTPUT：\
在函數add10(), 值為 15\
在主程式 main(), num= 5

{% hint style="info" %}
若是傳遞陣列，或由類別建立的物件時，必須以「傳參照（pass by reference）」方式
{% endhint %}


# 函數傳遞陣列

### 一、傳遞一維陣列

> 要傳遞一維陣列到函數裡，只要指名傳入的引數是一個陣列即可。

```
public class ch07_5 {

	public static void main(String[] args) {
		int score[]={9,14,6,18,2,10};
		largest(score);
	}

	public static void largest(int arr[]){
		int max=arr[0];
		for(int i=0; i<arr.length; i++)
			if(max<arr[i])
				max=arr[i];
		System.out.println("largest num = "+max);

	}
}
```

OUTPUT：\
largest num = 18

{% hint style="info" %}
如果要傳遞陣列到函數裡，只要在函數內填上陣列的名稱即可。

如以上範例的第五行：

```
largest(score);
```

{% endhint %}

### 二、傳遞二維陣列

```
public class ch07_6 {

	public static void main(String[] args) {
		int score[][]={{9,14,6,18,2,10},{18,32,65,27,30,17}};
		largest(score);
	}

	public static void largest(int arr[][]){
		int max=arr[0][0];
		for(int i=0; i<arr.length; i++)
			  for(int j=0; j<arr[i].length; j++)
				  if(max<arr[i][j])
					  max=arr[i][j];
		System.out.println("largest num = "+max);

	}
}
```

OUTPUT：\
largest num = 65

在主程式main 當中，score函數改為二維函數；\
而相同在函數內填上陣列名稱；

在函數largest 中，記得改為接收二維陣列 （本範例為整數陣列，固宣告二維整數陣列）。

### 三、函數資料型態注意

{% hint style="info" %}
傳回陣列函數該注意的是：

如果為整數陣列，必須在函數前加上int\[]&#x20;

若是二維陣列則加上int\[]\[]，以此類推。
{% endhint %}

```
public class ch07_7 {

	public static void main(String[] args) {
		int a[]={9,14,6,18,2,10};
		int b[];
		b=add5(a);
		for(int i=0; i<a.length; i++)
			System.out.print(b[i]+" ");
		System.out.println();
	}

	public static int[] add5(int arr[]){
		for(int i=0; i<arr.length; i++)
			arr[i]+=5;
		return arr;
	}

}
```

OUTPUT： \
14 19 11 23 7 15


# 函數多載

多載 (overloading)

1. 函數名稱相同，引數個數不同
2. 引數型態不同，引數個數相同

相似功能的函數，以相同的名稱來命名之；透過不同的引數個數，或是不同的型態，\
來執行相對應的功能。

### 一、引數型態不同，引數個數相同

首先在主程式分別宣告不同資料型態的變數a、b、c、d

```
int a=2, b[]={1,2,3};
		float c=3;
		double d=3.14;
```

建立四個相同名稱的函數 show，引數個數同為一個，而型態不同

```
public static void show(int i)
public static void show(int arr[])
public static void show(float i)
public static void show(double i)
```

因此當在主程式呼叫函數時，將會執行相對應之型態的函數；\
舉例來說：

show(a) ; a 為整數型態 int ，因此將會對應到引數為整數型態的函數

```
public static void show(int i)
```

show(b) ; b為整數型態的陣列，因此對應到引數為陣列整數型態的函數

```
public static void show(int arr[])
```

#### 完整程式碼：

```
public class ch07_9 {

	public static void main(String[] args) {
		int a=2, b[]={1,2,3};
		float c=3;
		double d=3.14;
		show(a);
		show(b);
		show(c);
		show(d);

	}
	public static void show(int i){
		System.out.println("值(a)＝"+i);
	}
	public static void show(int arr[]){
		System.out.print("值(b)=");
		for(int i=0;i<arr.length;i++)
		System.out.print(arr[i]+" ");
	}
	public static void show(float i){
		System.out.println("\n值(c)＝"+i);
	}
	public static void show(double i){
		System.out.println("值(d)＝"+i);
	}

}
```

### 二、函數名稱相同，引數個數不同

這次在主程式直接呼叫函數，並設值。

```
show();
		show(3);
		show('A',5);
```

在函數的部分，擁有相同名稱卻有不同個數的引數：

```
public static void show( )   //無引數
public static void show(int n)  //一個引數
public static void show(char ch,int n)  //兩個引數
```

透過引數個數的不同，對應到應該呼叫的函數

#### 完整程式碼：

```
public class ch07_9_2 {

	public static void main(String[] args) {
		show();
		show(3);
		show('A',5);

	}
	public static void show(){
		System.out.println("no");
	}
	public static void show(int n){
		for(int i=0;i<n;i++)
			System.out.print("*");
		System.out.println("");
	}
	public static void show(char ch,int n){
		for(int i=0;i<n;i++)
			System.out.print(ch);
	}

}
```


# 函數 SWAP

若在學習Java之前有先碰過C 或C++ ，呼叫方法傳遞引數給參數時，有傳值（Call by value）或傳參考（Call by reference）的方式。

> 參考是什麼？
>
> 參考的概念很像指標，兩者同樣保存「存取某資料」的資訊（像指標中所指向的記憶體位址）；但並不保存「該筆資料」本身。

> 指標是什麼？
>
> 指標就是記憶體「位址（address）」；記憶體位址就好像門牌號碼。那麼 Java 有指標嗎？本章將對這部分淺談，並舉範例。

{% hint style="info" %}
Java 參數傳遞方式只有一種：傳值。

將實際參數值的複製品傳入方法（函數）中，而參數本身不會受到任何影響。
{% endhint %}

### 範例一

```
public class swap_ex {
	public static void main(String args[]){
		int a=6;
		int b=9;
		swap(a,b);
		System.out.println("交換後，a="+a+" ,b="+b);
		}

	public static void swap(int a,int b){
		int temp=a;
		a=b;
		b=temp;
		System.out.println("swap函數中，a="+a+" ,b="+b);
		}
```

OUTPUT:\
swap函數中，a=9 ,b=6 \
交換後，a=6 ,b=9

從結果可以看出，main裡面的變數a和b，並不是swap方法裡面的a和b。也就是說swap方法的a和b只是main方法裡面a和b的複製品。

從main開始執行，定義了a、b兩個區域變數，當程式執行swap時，將main方法中的a、b變數作為參數值傳入swap，傳入swap的只是a和b的複製品，而不是a、b本身，進到swap後系統產生了4個變數，main 區域的a和b 以及 swap 區域 a和b。

程式分別為 main和swap分配兩個區塊，用來保存main和swap的區域變數。看似 main中的a、b變數值傳入swap，實際上是 swap 區域中重新產生了兩個變數a、b，並將main中的a、b 值給swap 中a、b參數

此時，系統存有兩個a 變數，兩個b變數，只是存在不同的函數區域中。

程式只是改變swap 中的a、b，而 main 中的a、b並沒有改變。這就是傳值。

### 範例二

```
class intswap{
	public int a;
	public int b;
	}

public class swap_ex2 {
	public static void main(String[] args) {
		intswap sp=new intswap();
		sp.a=10;
		sp.b=2;
		swap(sp);
		System.out.println("交換後，a="+sp.a+" ,b="+sp.b);
		System.out.println(sp);

	}

	public static void swap(intswap s){
		int tmp=s.a;
		s.a=s.b;
		s.b=tmp;
		System.out.println(s);
		System.out.println("swap，a="+s.a+" ,b="+s.b);
    }
```

OUTPUT：\
ch07.intswap\@7852e922\
swap，a=2 ,b=10\
交換後，a=2 ,b=10\
ch07.intswap\@7852e922<br>

從結果來看，swap 和main 中的a、b兩個值都被交換了。

程式從 main 開始執行，main 創建了intswap 物件，並定義sp 指向 intswap。

main 開始跑到swap函數時，main 尚未結束，系統會分別空出 main和swap兩個區域，用來存放main和swap的區域變數。

執行 swap函數時，同樣是傳值：把main裡sp的值 給swap裡的s。

main 的sp 是一個引用，保存了intswap 的地址，當把sp的值 給swap的s後，也會讓 swap 中的s 也保存這個地址，換句話說，也會引用到記憶體中的intswap。

當程式在swap 時，由於s只是一個引用變數，實際運作的還是記憶體中的intswap。

不管是操作main裡的sp，還是swap的s，都是執行他們所引用的 intswap。因此，當swap 中交換s 所引用的intswap 的a、b 時，可以看到 main方法中 sp 變量所引用的intswap 的a、b兩個值也被交換了。

Java 以id紀錄記憶體位址，從結果也可以發現兩者位址是相同的。

###

### 參考資料

{% embed url="<https://openhome.cc/Gossip/Java/PassByValue.html>" %}

{% embed url="<https://myleslittlewolf.pixnet.net/blog/post/23623695-java沒有指標?-－-淺談參考(reference)與物件的複>" %}


# 定義類別

## 如何定義

每個 Java程式都會有一個或一個以上的類別。

> 類別是由「資料成員 field」與「函數 method」封裝而成。

```
class 類別名稱
{
   資料型態 field 名稱;
   ...
   傳回值的資料型態 函數名稱(引數1,引數2,...)
   {
     程式敘述;
     return 運算式;
   }
}
```

讓我們從第一個程式：Hello Java （[詳情請見](/java-01/hellojava)）帶入初次的解說吧\~

```
public class class01 {

	public static void main(String[] args) {

		 System.out.println("Hello Java!");

	}
}
```

看到 class class01 的地方，這邊就是一個類別！ 而其中的 main 就是他的(函數)成員唷。

### 實際範例

讓我們帶入實際的例子再做一次複習的說明：

一個矩形有「寬」與「高」，可以看成資料成員；而計算舉行面積或周長的函式，可以看成函數成員。

讓我們設計一個類別為矩形類別吧！

```
class CRectangle {  //定義矩形類別
	//資料成員
	int width;  //寬
	int height; //長

	//函數成員
	int area(){   //計算面積
		return width*height;
	}
	int perimeter(){  //計算周長
		return 2*(width+height);
	}
}
```

以上範例我們就成功定義了一個矩形的類別。

## 補充

在類別中，若要使用資料成員，可以直接使用資料成員中的變數名稱

如以上範例的矩形類別：直接使用資料成員中的寬與長。

```
//函數成員
	int area(){   //計算面積
		return width*height;
	}
	int perimeter(){  //計算周長
		return 2*(width+height);
	}
```


# 使用類別

## 範例類別

```
class CRectangle {  //定義矩形類別
	//資料成員
	int width;  //寬
	int height; //長

	//函數成員
	int area(){   //計算面積
		return width*height;
	}
	int perimeter(){  //計算周長
		return 2*(width+height);
	}
}
```

以上範例為一個矩形的類別。

類別就好像你統整出來的一個盒子，盒子裡面有各式各樣的東西（資料成員與函數成員）。\
而若要使用，就必須先拿出這個盒子才可以使用裡面的東西。

### 資料成員

讓我們先初次帶入這個觀念到程式碼中：

```
class CRectangle {  //定義矩形類別
	//資料成員
	int width;  //寬
	int height; //長
}
public class ch08_1 {

	public static void main(String[] args) {
		CRectangle book;   //宣告CRectangle類別的變數 book
		book= new CRectangle(); //建立物件

		book.width=10;  //給寬一個值
		book.height=5;  //給長一個值

		System.out.println("寬＝ "+book.width);  //印出結果
		System.out.println("長＝ "+book.height); //印出結果

	}
}
```

在上述範例的矩形類別只用了資料成員。

想要計算一本書的寬與長，\
在主程式的地方我們宣告一個變數名稱為book，而這個變數的資料型態就是這個類別。

```
CRectangle book;
```

我們宣告了這個名稱之後，要賦予它功能，\
這時候利用 new 建立新的物件，並且讓book 指向這個類別。

```
book= new CRectangle();
```

如此一來就成功的建立這個類別的物件(instance)。

> ```
> CRectangle book;
> book= new CRectangle();
> ```
>
> 也可以寫成一行
>
> ```
> CRectangle book=new CRectangle();
> ```

知道如何宣告類別變數以及建立物件後，\
接下來就是要存取物件的內容。

```
物件名稱 . 資料成員 ;
```

如程式碼中：

```
book.width=10;  //存取物件的寬，並給他一個值
book.height=5;  //存取物件的長，並給他一個值
```

### 函數成員

再來我們將類別中的函數成員也加入：\
以下完整的程式碼

```
class CRectangle {  //定義矩形類別
	//資料成員
	int width;  //寬
	int height; //長
	//函數成員
	int area(){   //計算面積
		return width*height;
		}
	int perimeter(){  //計算周長
		return 2*(width+height);
		}
}
public class ch08_1 {

	public static void main(String[] args) {
		CRectangle book;   //宣告CRectangle類別的變數 book
		book= new CRectangle(); //建立物件

		book.width=10;  //給寬一個值
		book.height=5;  //給長一個值

		System.out.println("寬＝ "+book.width);  //印出結果
		System.out.println("長＝ "+book.height); //印出結果

		System.out.println("面積＝ "+book.area()); //印出面積
		System.out.println("面積＝ "+book.perimeter()); //印出周長
	}
}
```

如何呼叫類別裡的函數：

```
物件 . 函數名稱(引數1,引數2,...)
```

在本次的範例函數中，並沒有相關的引數，所以括號中沒有東西。\
呼叫函數的方式與資料成員意思相同。

## 補充：如何建立多個物件

```
package ch08;
class CRectangle {
	int width;
	int height;
}
public class ch08_2 {

	public static void main(String[] args) {
		CRectangle book,paper;   //宣告CRectangle類別的變數 book與 paper
		book= new CRectangle(); //建立物件
		paper= new CRectangle(); //建立物件

		book.width=10;  //給物件book的寬一個值
		book.height=5;  //給物件book的長一個值

		paper.width=5;  //給物件paper的寬一個值
		paper.height=20; //給物件paper的長一個值

		System.out.println("寬＝ "+book.width);  //印出結果
		System.out.println("長＝ "+book.height); //印出結果

		System.out.println("寬＝ "+paper.width);  //印出結果
		System.out.println("長＝ "+paper.height); //印出結果
	}
}
```

要建立多個物件也相當的簡單；\
在此範例多了新增一個paper的物件，\
於宣告時多宣告一個paper的變數，\
並且一樣使用new 來建立物件。

存取的方式也是一樣的：物件名稱 . 資料成員


# 類別中的函數成員相互呼叫

以[上一篇](/java-08/java-08-2)中的範例做示範與修改

```
class CRectangle {  //定義矩形類別
	//資料成員
	int width;  //寬
	int height; //長
	//函數成員
	int area(){   //計算面積
		System.out.println("面積＝ "+width*height);
		}
	int perimeter(){  //計算周長
		System.out.println("周長＝ "+2*(width+height);
		}
        int show_all(){  //在show_all 函數中 呼叫計算面積的函數與計算周長的函數
                System.out.println("寬＝ "+width+" ,長＝ "+height);
                area();  //呼叫計算面積的函數
                perimeter(); //呼叫計算周長的函數
                }

}
public class ch08_3 {

	public static void main(String[] args) {
		CRectangle book;   //宣告CRectangle類別的變數 book
		book= new CRectangle(); //建立物件

		book.width=10;  //給寬一個值
		book.height=5;  //給長一個值
		book.show_all(); //呼叫show_all()
	}
}
```

輸出結果：\
寬＝ 10 ,長＝ 5\
面積＝ 50\
面積＝ 30

在類別中函數成員的程式碼敘述當中，直接加上類別中其他函數成員的函數名稱即可呼叫。


# 類別中使用 this

加上this 的用途是強調，於程式碼當中可以這麼做：

```
this . 資料成員名稱 ;
this . 函數成員名稱 ;
```

使用[上一篇](/java-08/java-08-3)修改後的範例作為this 的示範：

```
class CRectangle {  //定義矩形類別
	//資料成員
	int width;  //寬
	int height; //長
	//函數成員
	int area(){   //計算面積
		System.out.println("面積＝ "+this.width*this.height);
		}
	int perimeter(){  //計算周長
		System.out.println("周長＝ "+2*(this.width+this.height);
		}
        int show_all(){  //在show_all 函數中 呼叫計算面積的函數與計算周長的函數
                System.out.println("寬＝ "+this.width+" ,長＝ "+this.height);
                this.area();  //呼叫計算面積的函數
                this.perimeter(); //呼叫計算周長的函數
                }

}
public class ch08_4 {

	public static void main(String[] args) {
		CRectangle book;   //宣告CRectangle類別的變數 book
		book= new CRectangle(); //建立物件

		book.width=10;  //給寬一個值
		book.height=5;  //給長一個值
		book.show_all(); //呼叫show_all()
	}
}
```

this 代表 book

那麼一定會疑惑，那除了強調之外，沒有其他用途嗎？

在這邊舉另外一個簡單例子：

```
name(String book){
book=book;
}
```

如果在程式中，屬性的值與傳入的值都相同的話；兩者都會被認為是參數，所以book的值並不會被改變。

若改為以下則可以正常運作了：

```
name(String book){
this.book=book;
}
```


# 練習：建立圓形類別

## 題目1

試著做一個圓形的類別，類別名稱為 Circle，\
資料成員包含 pi 為3.14 與半徑 radius；\
函數成員為一個可以印出計算圓面積的函數show\_area

圓面積公式為：半徑平方 \* pi

### 本題練習重點

1. 類別的建立
2. 主程式中物件的建立
3. 存取物件的內容
4. 呼叫類別中的函數

## 題目2

試著修改題目1的程式，多新增一個物件moon2（總共兩個物件），\
並且設定兩個物件的資料成員半徑值皆為相同，\
再將剛新增的物件moon2 資料成員 pi 設為 3。

最後於主程式呼叫類別中的函數。

### 本題練習重點

1. 同時建立多個物件
2. 了解不同物件的資料成員\
   在記憶體中配置是分開的\
   本題中的moon2.pi

## 參考解答

### 題目一

```
class Circle{  //定義類別Circle
	double pi=3.14;
	double radius;

	void show_area()
	{
		System.out.println("面積＝ "+pi*radius*radius);
	}
}
public class ch08_3 {

	public static void main(String[] args) {
		Circle moon=new Circle();  //建立 moon 物件
		moon.radius=2;
		moon.show_area();
	}
}
```

### 題目二

```
class Circle{
	double pi=3.14;
	double radius;

	void show_area()
	{
		System.out.println("面積＝ "+pi*radius*radius);
	}
}
public class ch08_3 {

	public static void main(String[] args) {
		Circle moon=new Circle();
		Circle moon2=new Circle();

		moon.radius=moon2.radius=2;
		moon2.pi=3;

		moon.show_area();
		moon2.show_area();
	}

}
```

下表為moon的記憶體位置對應

| pi     | 3.14 |
| ------ | ---- |
| radius | 2    |

下表為moon2的記憶體位置對應

| pi     | 3 |
| ------ | - |
| radius | 2 |


# 傳遞引數與回傳值

於[上一篇 練習](/java-08/java-08-p) 帶入本次的傳遞引數以及回傳值

在以下範例，將示範關於引數傳遞。

```
class Circle{
	double pi=3.14;
	double radius;

	void show(){
		System.out.println("半徑＝ "+radius);
                System.out.println("面積＝ "+pi*radius*radius);
	}
        void set_r(double r){   //set_r 函數，用來設定半徑
	        radius=r;    //此處的r為區域變數，因此只會在此set_r函數中使用，離開將無效
	}
}
public class ch08_3 {

	public static void main(String[] args) {
		Circle moon=new Circle();
		moon.set_r(2);
		moon.show();
	}

}
```

看到類別中 函數成員set\_r

```
void set_r(double r){   //宣告一個變數r為double型態 作為引數
	radius=r;    //再設定radius 的值為引數r
}
```

於主程式中，呼叫類別函數

```
moon.set_r(2);   //設定moon 的半徑為 2
```

主程式中，設定半徑為2 會將此數值，\
傳到類別中的函數成員 set\_r() 的引數 r。

而在函數成員 set\_r 中，\
程式敘述又設定 radius 的值 為引數 r = 2

當主程式呼叫類別函數成員 show() 時，\
就會印出剛剛在函數成員 set\_r 設定給 radius 的值；\
以及利用設定後的radius算出 面積 並且印出。


# 多引數

在本篇做一個延伸的範例與 相關細節可以注意的地方。

在以下範例 修改自[上一篇](/java-08/java-08-5)，將示範傳遞多個引數。

```
class Circle{
	double pi=3.14;
	double radius;

	void show(){
		System.out.println("半徑＝ "+radius+"pi＝"+pi);
                System.out.println("面積＝ "+pi*radius*radius);
	}
        void set_circle(double p,double r){   //set_r 函數，用來設定pi與半徑
                pi=p;
	        radius=r;
	}
}
public class ch08_3 {

	public static void main(String[] args) {
		Circle moon=new Circle();
		moon.set_circle(3,2);
		moon.show();
	}

}
```

看到類別中 函數成員set\_circle

```
void set_circle(double p,double r){   //宣告兩個變數p與r為double型態 作為引數
                pi=p;     //再設定pi 的值為引數p
	        radius=r; //再設定radius 的值為引數r
	}
```

於主程式中，呼叫類別函數

```
moon.set_circle(3,2);   //設定moon 的pi為3，半徑為 2
```

主程式中，設定pi為3，半徑為2 會將兩數值，\
傳到類別中的函數成員 set\_circle() 的引數 p 與 r。

而在函數成員 set\_circle 中，\
程式敘述又設定 pi 的值 為引數 p = 3\
設定 radius 的值 為引數 r = 2

當主程式呼叫類別函數成員 show() 時，\
就會印出剛剛在函數成員 set\_circle 設定給 pi 與 radius 的值；\
以及利用設定後的radius算出 面積 並且印出。

## 補充：函數如果不是回傳值

必須在函數名稱前方加個 void

```
void show(){
		System.out.println("半徑＝ "+radius+"pi＝"+pi);
                System.out.println("面積＝ "+pi*radius*radius);
	}
void set_circle(double p,double r){   //set_r 函數，用來設定pi與半徑
                pi=p;
	        radius=r;
	}
```

以下介紹 有回傳值的函數：

同樣修改本篇範例

```
class Circle{
	double pi=3.14;
	double radius;

	double get_r(){
		return radius;
	}
        void set_circle(double p,double r){   //set_r 函數，用來設定pi與半徑
                pi=p;
	        radius=r;
	}
}
public class ch08_3 {

	public static void main(String[] args) {
		Circle moon=new Circle();
		moon.set_circle(3,2);
		System.out.println("半徑＝ "+moon.get_r());
	}

}
```

看到範例中函數成員 get\_r

```
double get_r(){
	return radius;
}
```

先看到程式敘述的部分：

此函數用來回傳物件的半徑，因此多一個 return可以傳回半徑

而在函數的型態部分：

因為半徑想要讓他回傳double 型態，\
因此在函數名稱之前放上 double。

最後讓我們看到主程式的部分：

```
moon.set_circle(3,2);
System.out.println("半徑＝ "+moon.get_r());
```

set\_circle 已經將pi 與radius 設值為 3 , 2\
因此在get\_r 函數中的 radius 為 2。\
在主程式呼叫函數時，就會取得該函數並且回傳剛剛存放的值

因此輸出將會是 半徑＝2.0 (因為double型態)


# 類別中函數的多載

在類別中，函數成員相同可以進行多載。

## 複習多載

多載 (overloading)

1. 函數名稱相同，引數個數不同
2. 引數型態不同，引數個數相同

> 相似功能的函數，以相同的名稱來命名之；透過不同的引數個數，或是不同的型態，\
> 來執行相對應的功能。

## 函數多載範例

以下範例為一個函數利用多載的技術所撰寫的：

```
class CCircle{
	String color;
	double pi=3.14;
	double radius;

	void setColor(String str){
		color=str;
		System.out.println("顏色＝ "+color);
	}
	void setRadius(double r){
		radius=r;
		System.out.println("半徑＝ "+radius);
	}
	void setAll(String str, double r){
		color=str;
		radius=r;
	}
	void show(){
		System.out.println("顏色＝ "+color+", 半徑＝ "+radius);
		System.out.println("面積＝ "+pi*radius*radius);
	}
}
public class ch08_4 {

	public static void main(String[] args) {
		CCircle cake=new CCircle();

		cake.setColor("粉紅");
		cake.setRadius(5);
		cake.setAll("藍色", 2);
		cake.show();
	}
}
```

雖然在主程式當中，呼叫類別函數成員時可以由名稱大略得知該函數的功能；\
但因為部分功能相近，我們可以透過多載的技術來整理：

將上面的範例中部分的函數拿出來整理，這三者共通點都為一個設定的條件（設定顏色、半徑、pi）

```
void setColor(String str)
void setRadius(double r)
void setAll(String str, double r)
```

改成相同的函數名稱；\
而在引數部分，除了型態不同之外，也有個數不同的引數

```
void setCircle(String str)
void setCircle(double r)
void setCircle(String str, double r)
```

如此一來只要在主程式呼叫函數時，就不需要記太多函數名稱，\
直接透過引數的型態不同，或是個數不同，\
就能呼叫相對應的類別函數成員。

```
cake.setCircle("粉紅");
cake.setCircle(5);
cake.setCircle("藍色", 2);
```

## 完整範例程式碼

```
class CCircle2{
	String color;
	double pi=3.14;
	double radius;

	void setCircle(String str){
		color=str;
		System.out.println("顏色＝ "+color);
	}
	void setCircle(double r){
		radius=r;
		System.out.println("半徑＝ "+radius);
	}
	void setCircle(String str, double r){
		color=str;
		radius=r;
	}
	void show(){
		System.out.println("顏色＝ "+color+", 半徑＝ "+radius);
		System.out.println("面積＝ "+pi*radius*radius);
	}
}
public class ch08_4_2 {

	public static void main(String[] args) {
		CCircle2 cake=new CCircle2();

		cake.setCircle("粉紅");
		cake.setCircle(5);
		cake.setCircle("藍色", 2);
		cake.show();
	}
}
```

## 注意

不能引數的個數與型態相同，只有函數成員的回傳型態不同。

ex.

```
void setCircle(double r)
int setCircle(double r)
```


# 類別成員中的公有與私有

public & private

為了保護資料成員不被任意的修改，在此篇將會談論到透過一個公有私有的機制，來防範這個漏洞。

因此先從私有資料成員開始談論起。

以[本章練習](/java-08/java-08-p)的範例來舉例：

```
class Circle{  //定義類別Circle
	double pi=3.14;
	double radius;

	void show_area()
	{
		System.out.println("面積＝ "+pi*radius*radius);
	}
}
public class ch08_3 {

	public static void main(String[] args) {
		Circle moon=new Circle();  //建立 moon 物件
		moon.pi=3.1415;
		moon.show_area();
	}
}
```

在資料成員的部分，我們有 pi與 radius；\
因為這是一個計算圓面積的程式，在主程式設值給資料成員 radius 聽起來很正常；\
雖然主程式並無設值給 pi，但 pi 很有可能因為程式的規模一大，而導致值會有所變更；\
同理， radius 也有可能在此時設值為 2 ，但又可能遭到修改。

為了防範此類事情發生，有了 公有與私有 ( public & private) 的機制。

## 私有

```
class Circle{  //定義類別Circle
	private double pi=3.14;
	private double radius;

	．．．．．
}
```

直接在宣告最前方，個別加上 private ，如此一來將無法從類別以外的地方 設定或直接讀取。\
進而達到保護的效果。

```
class Circle{  //定義類別Circle
	private double pi=3.14;
	private double radius;

	void show_area()
	{
		System.out.println("面積＝ "+pi*radius*radius);
	}
}
```

在類別函數成員的部分，是可以正常讀取到私有成員 pi 與 radius 的。

```
public static void main(String[] args) {
		Circle moon=new Circle();  //建立 moon 物件
		moon.radius=-3;
		moon.show_area();
	}
```

但在主程式的部分，就無法設值給 radius 了。

## 公有

> 既然有 私有 private 就有 公有 public

剛剛的程式碼，設定 private 之後，也無法設值計算圓面積，那到底保護了資料成員又有何作用？\
將上述的程式做一個修改，成一個較完善的程式，就會慢慢清楚囉～：

```
class Circle{  //定義類別Circle
	private double pi=3.14;
	private double radius;
	// 設定資料成員為私有
	private double area()
	{  //設定計算面積的函數為私有
	    return pi*radius*radius;
	}

        public void show_area()
        {   //設定列印面積的函數為公有
            System.out.println("area="+ area());
        }

        public void setRadius(double r)
        {   //設定一個設定半徑的函數為公有
            if (r>0)
            {
               radius=r;
               System.out.println("radius= "+radius);
            }
            else System.out.println("半徑輸入錯誤");
        }
}
public class ch08_3 {

	public static void main(String[] args) {
		Circle moon=new Circle();  //建立 moon 物件
		moon.setRadius(3);
		moon.show_area();
	}
}
```

以下來一步步講解：

首先是類別的部分，設為私有的除了資料成員外，也設定函數成員area 為私有

```
private double pi=3.14;
private double radius;
// 設定資料成員為私有
private double area()
{  //設定計算面積的函數為私有
    return pi*radius*radius;
}
```

可以注意到，area 函數 單純為一個計算值的函數。

再來看到設定公有的函數：

分別有 呼叫列印面積的函數

```
public void show_area()
{   //設定列印面積的函數為公有
            System.out.println("area="+ area());
}
```

以及設定半徑的函數

```
public void setRadius(double r)
{   //設定一個設定半徑的函數為公有
     if (r > 0)
     {
        radius=r;
        System.out.println("radius= "+radius);
     }
     else System.out.println("半徑輸入錯誤");
}
```

主程式方面，由於無法呼叫私有的函數或直接設值給私有資料成員；\
因此在此是呼叫公有函數

```
public static void main(String[] args) {
	Circle moon=new Circle();  //建立 moon 物件
	moon.setRadius(3);
	moon.show_area();
}
```

這樣有什麼好處呢？除了可以判斷輸入的值是否正確之外，還可以有效保護資料成員不被程式任意的更改。

呼叫設值函數時：會傳進 if else 做判斷，若半徑大於零 就設定 剛剛傳進的值 r 給 radius；\
若設定的值小於零，那麼就直接印出輸入錯誤的訊息

```
moon.setRadius(3);
```

## 注意

初次學程式雖然都是寫單一的小程式練習；\
但日後一定會有大型的專案撰寫，除了學習相關的觀念，同時也是替日後打下強大的基礎。

> 本章的 公有與私有 ( public & private) 除了上述示範是在同一支程式底下運作外，在此也要給一個觀念：
>
> 1. 若冠上 public 公有的成員，可以被 其他程式自由的存取
> 2. 若冠上 private 私有的成員，除了 package 其他程式無法存取外，自身的程式也無法存取；運作的範圍只在該類別當中
> 3. 若省略 公有與私有 ( public & private) 都不使用，那麼成員只會在同一 package 存取使用


# 認識 建構元

什麼是建構元，他的作用是什麼？

1. 建構元是一個類別中的功能，他會與類別名稱相同
2. 建立物件時，建構元將自動執行
3. 目的是用來設定初值

在[使用類別](/java-08/java-08-2)篇章提到如何建立類別的物件

> ```
> CRectangle book;
> book= new CRectangle();
> ```
>
> 也可以寫成一行
>
> ```
> CRectangle book=new CRectangle();
> ```

透過建構元的技術，我們可以直接在建立物件時設值

```
CRectangle book=new CRectangle(5);
```

以下為一簡單範例：

```
class CCircle
{
   private double pi=3.14;
   private double radius;

   public CCircle(double r)      // 在此定義建構元CCircle()
   {
      radius=r;
   }
   public void show()
   {
       System.out.println("radius="+radius+", 面積="+pi*radius*radius);
   }
}
public class class_constructor_1
{
   public static void main(String args[])
   {
      CCircle cir1=new CCircle(4.0); // 建立物件的同時，呼叫建構元設值
      cir1.show();
   }
}
```

建立建構元的方式很簡單，名稱與該類別相同即可

```
public CCircle(double r)      // 在此定義建構元CCircle()
{
   radius=r;
}
```

在主程式部分，建立物件的同時，也在呼叫建構元。

```
CCircle cir1=new CCircle(4.0);
```

此刻便可直接設值給建構元

> 我們可以給建構元一個定義的格式：
>
> ```
> public/private 類別名稱(型態 引數)
> {
>   程式敘述;
>   ．．．．
> }
> ```
>
> 值得注意的是，建構元無回傳值


# 建構元的多載

先前提過函數的多載、類別函數成員的多載，相信也都要熟悉什麼是多載。\
在此再次說明多載是什麼：

> 多載 (overloading)
>
> 1. 函數名稱相同，引數個數不同
> 2. 引數型態不同，引數個數相同
>
> 相似功能的函數，以相同的名稱來命名之；透過不同的引數個數，或是不同的型態，\
> 來執行相對應的功能。

以下範例為建構元利用多載的技術所撰寫的：

```
class CCircle
{
   private String color;
   private double pi=3.14;
   private double radius;

   public CCircle()                    // 這是一個沒有引數的建構元
   {
      System.out.println("constructor CCircle() called");
      color="Green";
      radius=1.0;
   }
   public CCircle(String str, double r)      // 這是一個有兩個引數的建構元
   {
      System.out.println("constructor CCircle(String,double) called");
      color=str;
      radius=r;
   }
   public void show()
   {
      System.out.println("color="+color+", Radius="+radius);
      System.out.println("area="+pi*radius*radius);
   }
}
public class class_constructor_2
{
   public static void main(String args[])
   {
      CCircle cir1=new CCircle();         // 建立物件
      cir1.show();

      CCircle cir2=new CCircle("Blue",4.0);  // 建立物件，並設兩個值
      cir2.show();
   }
}
```

在主程式方面可以了解以下事情：

若在建立物件的過程中：無設值，將會自動對應到沒有引數的建構元

```
CCircle cir1=new CCircle();
```

若在建立物件的過程中：設定兩個值，將會對應到有兩個引數的建構元

```
CCircle cir2=new CCircle("Blue",4.0);
```

在[使用類別](/java-08/java-08-2)篇章提到如何建立類別的物件；

同樣的概念：建立物件時，會呼叫建構元；若想要呼叫沒有引數的建構元，則不需設值；\
若想呼叫有引數的建構元，在建立的過程中 也必須設初值。

這就是為什麼建構元擁有設定初值的功能。

(詳見：[認識 建構元](/java-09/java-09-1))


# 建構元間的呼叫 - 透過this

在建構元也可以運用 this 達到建構元間的呼叫。

以下範例為建構元利用this的技術來相互呼叫：

```
class CCircle
{
   private String color;
   private double pi=3.14;
   private double radius;

   public CCircle()           //這是一個沒有引數建構元
   {
      this("Green",1.0);      //這行將會呼叫有兩個引數的建構元
      System.out.println("CCircle() 呼叫");
   }
   public CCircle(String str, double r)    //這是一個有兩個引數的建構元
   {
      System.out.println("CCircle(String,double) 呼叫");
      color=str;
      radius=r;
   }
   public void show()
   {
      System.out.println("color="+color+", Radius="+radius);
      System.out.println("area="+pi*radius*radius);
   }
}
public class class_constructor_3
{
   public static void main(String args[])
   {
      CCircle cir1=new CCircle();
      cir1.show();
   }
}
```

以下針對這程式解說一下：

主程式部分，直接呼叫沒有引數的建構元

```
CCircle cir1=new CCircle();
```

讓我們看到沒有引數的建構元

```
public CCircle()           //這是一個沒有引數建構元
   {
      this("Green",1.0);      //這行將會呼叫有兩個引數的建構元
      System.out.println("CCircle() 呼叫");
   }
```

讓我們再看到有兩個引數的建構元

```
public CCircle(String str, double r)    //這是一個有兩個引數的建構元
   {
      System.out.println("CCircle(String,double) 呼叫");
      color=str;
      radius=r;
   }
```

剛剛在沒有引數的建構元當中：

```
this("Green",1.0);
```

把color 設為Green，radius設為1.0；\
因為要呼叫下方 擁有兩個引數的建構元，\
因此以this() 來呼叫。

## 注意

this ( )  要寫在建構元的敘述第一行；以下情況是不行的：

```
public CCircle()           //這是一個沒有引數建構元
   {
      System.out.println("CCircle() 呼叫");
      this("Green",1.0);     //這行寫法是錯誤的
   }
```

正確寫法為：

```
public CCircle()           //這是一個沒有引數建構元
   {
      this("Green",1.0);      //這行將會呼叫有兩個引數的建構元
      System.out.println("CCircle() 呼叫");
   }
```


# 建構元中的公有與私有

讓我們再次複習一次上次帶入的觀念

> 公有與私有 ( public & private)：
>
> 1. 若冠上 public 公有的成員，可以被 其他程式自由的存取
> 2. 若冠上 private 私有的成員，除了 package 其他程式無法存取外，自身的程式也無法存取；運作的範圍只在該類別當中
> 3. 若省略 公有與私有 ( public & private) 都不使用，那麼成員只會在同一 package 存取使用

那麼建構元的公有私有：

同理，公有建構元可以在程式任何一處被呼叫；\
私有建構元無法在他所處的類別 之外的地方被呼叫。

讓我們看看 建構元的公有私有如何使用的：

```
class CCircle
{
   private String color;
   private double pi=3.14;
   private double radius;

   private CCircle()                   //私有建構元 無引數
   {
      System.out.println("私有建構元被呼叫");
   }
   public CCircle(String str, double r)   //公有建構元 兩個引數
   {
      this();
      color=str;
      radius=r;
   }
   public void show()
   {
      System.out.println("color="+color+", Radius="+radius);
      System.out.println("area="+pi*radius*radius);
   }
}
public class class_pp_2{
   public static void main(String args[])
   {
      CCircle cir1=new CCircle("Blue",1.0);
      cir1.show();
   }
}
```

讓我們開始解說以上的程式：

主程式呼叫了 擁有兩個引數的建構元

```
CCircle cir1=new CCircle("Blue",1.0);
```

而這個擁有兩個引數的建構元 為一個公有建構元，因此可以被呼叫

```
public CCircle(String str, double r)   //公有建構元 兩個引數
   {
      this();
      color=str;
      radius=r;
   }
```

看到這個公有建構元內，運用到了this()

可以參考： [建構元間的呼叫](/java-09/java-09-3) 這篇 this 的使用；

透過公有的建構元，使用this 來呼叫 私有的建構元：\
因此OUTPUT：

私有建構元被呼叫\
color=Blue, Radius=1.0\
area=3.14

## 注意

因為有兩個引數的建構元為公有的，因此以下寫法是正確的：

```
CCircle cir1=new CCircle("Blue",1.0);
```

但若是以下寫法就是錯誤的：

```
CCircle cir1=new CCircle();
```

因為呼叫一個私有的建構元 （沒有引數的建構元），私有的建構元是無法在類別之外被呼叫的！


# 實例變數 與 實例函數

類別變數 與 類別函數 他們 和 實例變數 實例函數   聽起來沒很像是類似的東西，但功能上卻大不同。

> 實例變數：獨立，存在於不同記憶體
>
> 實例函數：必須透過物件來呼叫

將會帶入 [公有與私有 ( public & private) – 類別成員篇](/java-08/java-08-8) 其中的內容

```
class CCircle
{
   private double pi=3.14;
   private double radius;

   public CCircle(double r)   //建構元
   {
      radius=r;
   }
   public void show()
   {
      System.out.println("area="+pi*radius*radius);
   }
}
public class instance_vs_class_01
{
   public static void main(String args[])
   {
      CCircle cir1=new CCircle(1.0);
      cir1.show();         //透過物件來呼叫show
      CCircle cir2=new CCircle(2.0);
      cir2.show();         //透過物件來呼叫show
   }
}
```

在主程式部分： 可參考 [**認識 建構元**](/java-09/java-09-1)

建立物件 cir1 與 cir2 因此他們擁有自己存資料的記憶體空間，\
就像 cir1 有自己的一間房間，而 cir2 也有屬於自己的房間；\
兩者的房間不共用。

```
CCircle cir1=new CCircle(1.0);
cir1.show();         //透過物件來呼叫show
CCircle cir2=new CCircle(2.0);
cir2.show();         //透過物件來呼叫show
```

截至目前（若按照本站文章進度的話，認識了類別三樣元件：資料成員、函數成員、建構元）；\
而在先前的範例都是使用到實例變數與實例函數。

讓我們再次複習一次 實例變數 與 實例函數 是什麼：

> 實例變數：獨立，存在於不同記憶體
>
> 實例函數：必須透過物件來呼叫

接下來我們要帶入 類別變數 與 類別函數 來與剛剛複習的 實例變數 與 實例函數 做比較


# 類別變數

> 類別變數：物件共享，佔用在同一個記憶體
>
> 類別函數：可以直接呼叫

如何宣告為類別變數：

加上 static 就可以定義為類別變數

帶入範例：

```
class CCircle
{
   private static int count=0;         //宣告為類別變數
   private static double pi=3.14;      //宣告為類別變數
   private double radius;

   public CCircle()        // 建構元 沒有引數
   {
      this(1.0);           // 呼叫一個引數的建構元 並給值
   }
   public CCircle(double r)   // 建構元 一個引數
   {
      radius=r;
      count ++;               // 呼叫到此建構元就加一
   }
   public void show()
   {
      System.out.println("area="+pi*radius*radius);
   }
   public void show_count()   // 用來顯示建立物件的個數
   {
      System.out.println(count+" 個物件被建立");
   }
}
public class instance_vs_class_02
{
   public static void main(String args[])
   {
      CCircle cir1=new CCircle();      //呼叫沒有引數的建構元
      cir1.show_count();     // 呼叫顯示個數的函數
      CCircle cir2=new CCircle(2.0);   //呼叫一個引數的建構元
      CCircle cir3=new CCircle(4.3);   //呼叫一個引數的建構元
      //以下使用物件呼叫顯示個數的函數
      cir1.show_count();
      cir2.show_count();
      cir3.show_count();
   }
}
```

宣告類別變數

```
private static int count=0;         //宣告為類別變數
private static double pi=3.14;      //宣告為類別變數
```

在主程式呼叫沒有引數的建構元，並顯示目前的物件個數

```
CCircle cir1=new CCircle();      //呼叫沒有引數的建構元
cir1.show_count();     // 呼叫顯示個數的函數
```

各別再建立物件 cir2 和 cir3，呼叫有一個引數的建構元：

```
CCircle cir2=new CCircle(2.0);   //呼叫一個引數的建構元
CCircle cir3=new CCircle(4.3);   //呼叫一個引數的建構元
```

最後再呼叫顯示個數的函數，做測試：

```
cir1.show_count();
cir2.show_count();
cir3.show_count();
```

再OUTPUT 部分：\
第一次因為只有建立一個 cir1 的物件，因此呼叫顯示個數的函數會顯示：1個物件被建立；

各別又建立物件 cir2 和 cir3 之後，\
呼叫顯示個數的函數皆會顯示 3個物件被建立。

因為 count 宣告為類別變數，因此三個物件將會共同使用它

```
private static int count=0;
```


# 類別函數

> 類別變數：物件共享，佔用在同一個記憶體
>
> 類別函數：可以直接呼叫

類別函數的定義方式也是相同的：

加上 static 就可以定義為類別函數

```
public static void example()
   {
      ....
   }
```

另外在使用上，可以直接透過類別名稱呼叫。

```
類別名稱.example();
```

帶入以下完整範例： 結合 [類別變數](/java-10/java-10-2)

```
class CCircle
{
   private static int count=0;      // 宣告為類別變數
   private static double pi=3.14;   // 宣告為類別變數
   private double radius;

   public CCircle()           // 建構元 無引數
   {
      this(1.0);              // 呼叫一個引數的建構元
   }
   public CCircle(double r)      // 建構元 一個引數
   {
      radius=r;
      count++;                   // 呼叫此建構元 則加一
   }
   public void show()
   {
      System.out.println("area="+pi*radius*radius);
   }
   public static void show_count()  // 宣告為類別函數
   {
      System.out.println(count+" object(s) created");
   }
}
public class instance_vs_class_03
{
   public static void main(String args[])
   {
      CCircle.show_count();      //直接使用類別來呼叫類別函數
      CCircle cir1=new CCircle();      //呼叫建構元
      CCircle.show_count();      //直接使用類別來呼叫類別函數
      CCircle cir2=new CCircle(2.0);   //呼叫建構元
      CCircle cir3=new CCircle(4.3);   //呼叫建構元
      cir3.show_count();         //亦可使用物件來呼叫類別函數
   }
}
```

在此範例完整整合了 類別變數與類別函數：

類別變數部分：

```
private static int count=0;      // 宣告為類別變數
private static double pi=3.14;   // 宣告為類別變數
```

類別函數部分：

```
public static void show_count()  // 宣告為類別函數
{
   System.out.println(count+" object(s) created");
}
```

在主程式可以見到：直接透過類別來呼叫類別函數

```
CCircle.show_count();      //直接使用類別來呼叫類別函數
```

另外亦可透過物件來呼叫類別函數：

```
CCircle cir3=new CCircle(4.3);   //呼叫建構元
cir3.show_count();         //亦可使用物件來呼叫類別函數
```

另外也還可以發現 主程式也是一個使用 static 的函數

```
public static void main(String args[])
```

因為要直接由類別 instance\_vs\_class\_03 呼叫，因此宣告為 static


# 認識類別的繼承

繼承可說是類別的精華，有程式碼再利用的概念。

> 繼承：可保有原先的程式碼，並加以擴增；再利用的程式碼可以重複使用。

每個類別只能有一個父類別，而父類別可擁有一個以上的子類別。

![](/files/-MPaEpMASwOJ64SjcIEg)

子類別除了自己的成員外，也包含父類別的幾樣成員是可以直接繼承的。

父類別與子類別 能繼承之關係圖如下：

![](/files/-MPaEu6IQelN8FeL4f11)

其中較特殊的如 private 成員，必續透過父類別的函數才可以做存取。

另外 父類別中的 建構元 以及 設為終止繼承的成員也是無法被子類別繼承的！

> *繼承的寫法是使用 extends 關鍵字，將父類別繼承給子類別。*\
> *做法很簡單，先寫子類別名稱 再加extends 關鍵字， 後方再填上父類別名稱 即可！*
>
> ```
> class 父類別
> {
>   ...
> } 
>
> class 子類別 extends 父類別
> {
>  ...
> }
> ```

於下篇文章，將會帶入範例進一步了解繼承的使用。


# 範例：類別的繼承

繼承可說是類別的精華，有程式碼再利用的概念。

> 繼承：可保有原先的程式碼，並加以擴增；再利用的程式碼可以重複使用。

每個類別只能有一個父類別，而父類別可擁有一個以上的子類別。

## 範例 1

類別的繼承以 extends 來將父類別繼承給子類別：以下為一簡單範例

```
class Caaa{ //父類別Caaa
	public int num1;
	public int num2;
        public void show(){
		System.out.println("num1="+num1);
		System.out.println("num2="+num2);
	}
}
class Cbbb extends Caaa{ //繼承父類別Caaa 的子類別Cbbb
	public void set_num(int a,int b){
		num1=a;
		num2=b;
	}
}
public class Class01 {

	public static void main(String[] args) {
		Cbbb bb=new Cbbb(); //利用子類別建立 bb物件
		bb.set_num(5, 10); //呼叫父類別中的函數
		bb.show();
	}
}
```

由此範例示範繼承來自父類別的資料成員 num1, num2 與show 函數，\
用來顯示num1, num2的值

因此可以見到：子類別繼承了父類別的public 成員，並使用自己的成員來設值使用。

## 範例 2

再一完整的範例來更加了解繼承的概念：

```
class CCircle        // 父類別 CCircle
{
   private static double pi=3.14;
   private double radius;

   public CCircle()     // CCircle建構元
   {
      System.out.println("CCircle() 被呼叫 ");
   }
   public void setRadius(double r)
   {
      radius=r;
      System.out.println("radius="+radius);
   }
   public void show()
   {
      System.out.println("area="+pi*radius*radius);
   }
}
class CCoin extends CCircle      // 繼承父類別CCircle 的子類別CCoin
{
   private int value;            // 子類別資料成員

   public CCoin()          // 子類別建構元
   {
      System.out.println("CCoin() 被呼叫 ");
   }
   public void setValue(int t)         // 子類別函數
   {
      value=t;
      System.out.println("value="+value);
   }
}
public class class02
{
   public static void main(String args[])
   {
      CCoin coin=new CCoin(); // 建立 coin 物件
      coin.setRadius(2.0);    // 呼叫父類別中的函數
      coin.show();         // 呼叫父類別中的函數
      coin.setValue(5);       // 呼叫子類別中的函數
   }
}
```

值得注意的是，這次父類別有 private 成員 以及建構元，這些是子類別無法直接繼承的。

private 成員部分： 必須透過父類別中的setRadius( ) , show( ) 函數才能在子類別存取到。

讓我們看到 main 中，建立 coin 物件時，看似呼叫子類別中的建構元；但在程式執行的結果卻發現 父類別的建構元會被先執行後，才輪到子類別的建構元。

其實這是 Java 繼承的機制，目的是來將繼承來自父類別的成員做初始化動作。

利用 coin 物件呼叫父類別繼承而來的兩個函數，\
最後利用 coin 物件呼叫子類別的函數。

## 整理

由此範例我們可以得知：

* 透過關鍵字 extends 繼承來自父類別的成員。
* 若父類別有private 成員，可透過繼承過來的函數做存取。
* 執行子類別之建構元之前，會先呼叫父類別之建構元，目的是為了初始化。


# 類別繼承中的建構元呼叫

在上一篇提過執行子類別建構元之前，會先執行父類別的建構元。\
那麼如果父類別有多個建構元（不同引數個數），那該如何呼叫？

> 這時要用到 super( ) 來呼叫

讓我們先從流程圖先了解一下，再帶入範例：

![](/files/-MPaFckydWAvotEamiZ6)

透過 [上一篇](/java-11/java-11-2#fan-li-2) 的範例 2 來作本次建構元呼叫的範例：

```
class CCircle   //父類別CCircle
{
   private static double pi=3.14;
   private double radius;

   public CCircle()    // 無引數建構元
   {
      System.out.println("CCircle() 被呼叫");
   }
   public CCircle(double r)   // 一個引數的建構元
   {
      System.out.println("CCircle(double r) 被呼叫");
   }
}
class CCoin extends CCircle  // 子類別CCoin
{
   private int value;
   public CCoin()      // 無引數的建構元
   {
      System.out.println("CCoin() 被呼叫");
   }
   public CCoin(double r, int v) // 兩個引數的建構元
   {
      super(r);   // 呼叫父類別有一個引數的建構元
      System.out.println("CCoin(double r, int v) 被呼叫");
   }
}
public class class03
{
   public static void main(String args[])
   {
      CCoin coin1=new CCoin();         // 建立物件 coin1
      CCoin coin2=new CCoin(2,10);   // 建立物件 coin2
   }
}
```

main() 中，建立第一個物件 coin1 時：

會在呼叫子類別 無引數的建構元之前，先呼叫父類別無引數的建構元；\
因此程式執行結果會是

```
CCircle() 被呼叫
CCoin() 被呼叫
```

而main() 中，建立第二個物件 coin2 時：

這次給了(2,10) 的值，因此會呼叫子類別有兩個引數的建構元；\
而 CCoin(double r, int v) 此建構元當中，運用了本次要使用的 super() 敘述

這時 父類別中的建構元（一個引數）會先執行

```
public CCircle(double r)   // 一個引數的建構元
   {
      System.out.println("CCircle(double r) 被呼叫");
      radius=r;
   }
```

執行完父類別有引數的建構元後，才會執行CCoin(double r, int v) 此建構元 super() 之後的敘述。

因此程式執行結果為

```
CCircle(double r) 被呼叫
CCoin(double r, int v) 被呼叫
```

* 在本次範例中，如果子類別的建構元沒有寫上 super()，也是會執行父類別中「沒有引數的建構元」。
* super() 必須寫在建構元中的第一行敘述。
* super() 也有多載的技術：\
  會依照super括號中的引數個數去對應父類別建構元引數的個數。

## 注意

若父類別只有「有引數的建構元」，而子類別又沒透過 super 來呼叫特定的父類別建構元的話，\
程式編譯時將會出錯！

因為無法找到父類別「沒有引數的建構元」。


# Protected members

protected members 保護成員，在先前的範例皆使用過公有public、私有private的成員，而在本篇將帶入 保護 protected 使用方式。

> *把成員宣告為 protected 的用處以及好處為：*
>
> * *父類別與子類別皆能使用*
> * *除 父、子類別之外就無法存取*
> * *不像 private 無法直接繼承至子類別，或透過公開函數來繼承*
> * *利用繼承，父類別中的 protected 可直接繼承至子類別*

改寫先前的範例來做示範：

先前 CCircle 父類別使用的為私有成員，因此子類別需要透過公有public 來存取private成員；

```
private static double pi=3.14;
private double radius;
```

改寫後如下：

```
class CCircle
{
   protected static double pi=3.14;   // 宣告為protected
   protected double radius;   // 宣告為protected

   public void show()
   {
      System.out.println("area="+pi*radius*radius);
   }
}
class CCoin extends CCircle   // 子類別 CCoin
{
   private int value;

   public CCoin(double r, int v)
   {
      radius=r;      // 直接取用父類別的protected成員
      value=v;
      System.out.println("radius="+radius+", value="+value);
   }
}
public class class04
{
   public static void main(String args[])
   {
      CCoin coin=new CCoin(2,10);
      coin.show();
   }
}
```

當main() 中，建立 coin 物件時，傳值呼叫子類別的「有引數的建構元」：

並直接設值至父類別的 radius 成員。

因此執行結果會先印出 radius=2, value=10

而呼叫 show 時，將會印出 area=12.56

我想，透過這樣的範例 protected 與 private 最大的差異就是其便利性了。

兩者同樣有不被外界任意更改與存取的用途；\
而在父子類別之間的運用，使用 protected 不僅安全也便利。


# Overriding

改寫為 Java polymorphism 多型技術之一，同為多型技術技術的有之前提過的多載，\
讓我們先瞧瞧多載與改寫的比較。

> *多載 overloading：名稱相同，透過不同引數、型態，來呼叫相對應的函數。*
>
> *改寫 overriding：於子類別中，定義與父類別均相同的函數（名稱相同，引數個數相同）。*

讓我們先透過一簡單範例了解 改寫的意思以及其用法：\
透過上一篇 protected 的範例做本次範例

```
class CCircle              // 父類別 CCircle
{
   public CCircle(){}
   public void show()         // 父類別的 show() 函數
   {
      System.out.println("父類別的show()");
   }
}

class CCoin extends CCircle   // 子類別 CCoin
{
   public CCoin(){}
   public void show()         // 子類別的 show() 函數
   {
      System.out.println("子類別的show()");
   }
}

public class overriding
{
   public static void main(String args[])
   {
      CCoin coin=new CCoin();
      coin.show();         // 呼叫 show() 函數
   }
}
```

由此範例主要想表達的問題為：

於main() 呼叫show 函數時，是呼叫子類別中的show() 還是父類別中的show()

由程式執行的結果：子類別的show()

這種情況就是改寫 (overriding) ，子類別的show() 取代了父類別的show()

另外 也可以宣告父類別變數來存取子類別的物件：

```
CCoin coin=new CCoin();
```

將上述寫法 改成下方寫法：

```
CCircle coin=new CCoin();
```

在以上的成立的條件為：僅限於有「改寫」的情況發生下，才可以透過宣告父類別變數來存取子類別物件。

此時 改寫的好處就在這邊！如果有好幾個子類別繼承一個父類別，\
那麼僅需要建立一個子類別物件，並宣告父類別變數來存取之。

我們可以看一下以下程式的截圖：

![](/files/-MPaGCn9YfkEv6NH2exB)

左邊為分別宣告子類別變數來指到建立的物件。\
右邊為宣告父類別變數來分別指向這些物件。

透過改寫的技術，可以讓程式碼更有一致性：\
（下方左程式碼）在呼叫area() 函數時必須一個一個去呼叫；\
但（下方右程式碼）只需要套一個迴圈，並可分別呼叫相對應的 area()函數。

![](/files/-MPaGFhNYiDPTYSjzMn1)


# super() 與 this()

super() 與 this() 兩者的功能有點類似，簡單比較一下兩者：

> *this：存取 本身類別 的成員（資料成員、函數成員、建構元）*
>
> *super：存取 父類別 的成員（資料成員、函數成員、建構元）*

在先前提到的 super() 是透過括號內引數來呼叫對應的父類別建構元；\
super 除了可以這麼做之外，super 也可以加上成員 的方式來呼叫。見以下範例：

```
class Caaa
{
   protected int num;

   public void show()
   {
      System.out.println("Caaa_num="+num);
   }
}
class Cbbb extends Caaa
{
   int num=10;

   public void show()
   {
      super.num=20;
      System.out.println("Cbbb_num="+num);
      super.show();
   }
}

public class supervsthis
{
   public static void main(String args[])
   {
      Cbbb b=new Cbbb();
      b.show();
   }
}
```

透過「 super . 父類別成員名稱」這樣的格式，就能呼叫父類別的成員。\
如以上範例中：

```
super.num=20;
super.show();
```

在main() 中，呼叫了子類別的函數 show()

* 透過super.num=20 設20給父類別的資料成員num
* 透過super.show() 來呼叫父類別的函數show()

因此本範例的執行結果為：\
Cbbb\_num=10\
Caaa\_num=20

[先前](https://scheng.tk/blog/1219/java-class-constructor-03)提過的 this 也是只有說明建構元間互相呼叫，\
this 也是可以透過 「 this . 本身類別的成員名稱」這樣的格式，來呼叫本身類別的成員。

將剛剛的範例加上 this 的示範：

```
class Caaa
{
   protected int num;

   public void show()
   {
      System.out.println("Caaa_num="+num);
   }
}
class Cbbb extends Caaa
{
   int num=10;

   public void show()
   {
      this.num=20;
      super.num=20;
      System.out.println("Cbbb_num="+num);
      super.show();
   }
}

public class supervsthis
{
   public static void main(String args[])
   {
      Cbbb b=new Cbbb();
      b.show();
   }
}
```

以上範例，多加了一個：

```
this.num=20;
```

在子類別中，有宣告一個資料成員 num=10，

透過 this.num 就會呼叫自己類別中的資料成員；\
所以本次範例的執行結果為：\
Cbbb\_num=20\
Caaa\_num=20

透過本篇，對 super 與 this 又多了新的認識，與使用方法～！


# 設定終止繼承

在父類別中，有不希望被子類別繼承的成員 可以加上final 來終止繼承。\
若父類別中的函數成員加上 final ，子類別也就無法改寫（overriding）。

* 宣告為 final 後的變數，如同常數一般，無法再被更改
* 函數宣告 final後，無法改寫（overriding）
* 類別亦可宣告 final，如此一來就沒有子類別可以繼承

修改 [super與this 文章中](/java-11/java-super-and-this)的範例，來做本篇的示範：

```
final class Cfinal
{
	public void show()
	{
		System.out.println("Cfinal 父類別");
	}
}
class Ctest extend Cfinal
{
	public void show()
	{
		System.out.println("Ctest 子類別");
	}
}

class Caaa
{
   protected final int num=10;

   public final void show()
   {
      System.out.println("Caaa_num="+num);
   }
}
class Cbbb extends Caaa
{
   int num=10;

   public void show()
   {
      super.num=20;
      System.out.println("Cbbb_num="+num);
      super.show();
   }
}

public class supervsthis
{
   public static void main(String args[])
   {
      Cbbb b=new Cbbb();
      b.show();
   }
}
```

於本次範例有幾個點注意的部分：

* 將父類別 Cfinal 經過 final 宣告，因此底下的子類別 Ctest 是無法繼承父類別的

  ```
  class Ctest extend Cfinal
  ```
* 再看到另一個父類別 Caaa 中，

  ```
  protected final int num=10;

  public final void show()
  {
        System.out.println("Caaa_num="+num);
  }
  ```

  分別有 資料成員 num 與函數show() 經過 final 宣告，\
  因此在子類別 Cbbb 就無法正常繼承來自父類別的 num 或是 改寫父類別的 show() 函數
* 另外在 num 部分，若一變數想要經過 final 宣告，必須給訂一個值；\
  使之變成像常數一般，無法更改。

  ```
  protected final int num=10;
  ```

  常見範例如：

  ```
  protected final double PI=3.1415;
  ```

  如此一來，程式任何一處將不會更改到 PI 值。


# 認識抽象類別

抽象類別如同父類別一般，透過本篇了解 抽象類別的用法 及 抽象類別與父類別的不同處。

抽象類別 abstract class 的用法像「範本」一樣，我們可以依照其格式修改後運用之；\
如範本一般，因此無法直接使用：也就是說，我們不能直接透過 抽象類別 來建立物件。

一個簡單的整理：

* 抽象類別 不能用來建立物件
* 抽象函數 不定義其處理方式

進入範例之前，先了解一下 抽象類別 的格式：

```
abstract class 類別名稱
{
  宣告資料成員;
  /*
  傳回值資料型態 函數名稱(引數...)
  {
  ...
  } 定義一般的函數*/

  修飾子 abstract 傳回值資料型態 函數名稱(引數...);
  //抽象函數並無定義處理方式
}
```

有了以上基礎觀念後，先簡單帶入例子了解；\
先前提過改寫 overriding 的觀念，是用來改寫父類別中的函數。\
抽象函數也是這樣的動作，不過這次是直接不敘述函數的處理方式。

有許多種幾何形狀，都有顏色的屬性 並且都需要有一個show 函數 印出該顏色與面積；\
那麼我們可以設計一個抽象類別為 「形狀」，並且有一般函數用來設定顏色，\
一個只定義名稱的 抽象函數 來供子類別繼承使用：

```
abstract class CShape               // 定義抽象類別 CShape
{
   protected String color;
   public void setColor(String str)    // 一般的函數
   {
      color=str;
   }
   public abstract void show(); // 只定義函數名稱的 抽象函數
}
```

以上成功定義好了抽象類別後，再看看下方 子類別的部分：

```
class CRectangle extends CShape    // 子類別 矩形 CRectangle
{
   protected int width,height;
   public CRectangle(int w,int h)
   {
      width=w;
      height=h;
   }
   public void show()      // 定義繼承而來的抽象函數 show() 的處理方式
   {
      System.out.print("color="+color+",  ");
      System.out.println("area="+width*height);
   }
}
```

CShape 被定義為一個抽象類別，其中有一個設定顏色的一般函數，與一個 抽象函數 show()。

CRectangle 為一矩形 子類別，除了擁有自己的資料成員 長寬 之外，\
也有一個建構元用來設定長與寬；

重點是： 有一個繼承來自 父類別的抽象函數 show()，並且有明確定義其敘述。

```
System.out.print("color="+color+",  ");
System.out.println("area="+width*height);
```

下一篇將透過完整範例做更加詳盡的解說，並加深對抽象類別 抽象函數 的使用。<br>


# 範例：抽象類別

抽象類別如同父類別一般，透過本篇了解 抽象類別的用法 及 抽象類別與父類別的不同處。

一個簡單的整理：

* 抽象類別 不能直接建立物件
* 抽象函數 不定義其處理方式

有了前一篇的基本概念後，再透過以下更加完整的實例來了解 abstract 的用法：

```
abstract class CShape               // 定義抽象類別 CShape
{
   protected String color;
   public void setColor(String str)    // 一般的函數
   {
      color=str;
   }
   public abstract void show(); // 只定義函數名稱的 抽象函數
}

class CRectangle extends CShape    // 子類別 矩形 CRectangle
{
   protected int width,height;
   public CRectangle(int w,int h)  //建構元
   {
      width=w;
      height=h;
   }
   public void show()      // 定義繼承而來的抽象函數 show() 的處理方式
   {
      System.out.print("color="+color+",  ");
      System.out.println("area="+width*height);
   }
}
class CCircle extends CShape     // 子類別 圓形CCircle
{
   protected double radius;
   public CCircle(double r)   //建構元
   {
      radius=r;
   }
   public void show()      // 定義繼承而來的抽象函數 show() 的處理方式
   {
      System.out.print("color="+color+",  ");
      System.out.println("area="+3.14*radius*radius);
   }
}
public class abstract_01
{
   public static void main(String args[])
   {
      CRectangle rect=new CRectangle(5,10);  //透過子類別建立物件
      rect.setColor("Yellow");   // 呼叫父類別函數 setColor()
      rect.show();         // 呼叫子類別函數 show() method

      CCircle cir=new CCircle(2.0);
      cir.setColor("Green");     // 呼叫父類別函數 setColor()
      cir.show();         // 呼叫子類別函數 show()
   }
}
```

在每個幾何圖形的類別，設計用來顯示顏色與面積的函數；\
但因為每種形狀的面積計算公式不同，因此在父類別也無法很恰當的將處理方式描述出。

```
public abstract void show(); // 只定義函數名稱的 抽象函數
```

因為每種幾何圖形 都會繼承來自父類別的函數，所以我們直接在父類別定義名稱後，\
直接將處理方式分別寫在子類別繼承的函數中即可。

```
矩形：
public void show()      // 定義繼承而來的抽象函數 show() 的處理方式
   {
      System.out.print("color="+color+",  ");
      System.out.println("area="+width*height);
   }
```

```
圓形：
public void show()      // 定義繼承而來的抽象函數 show() 的處理方式
   {
      System.out.print("color="+color+",  ");
      System.out.println("area="+3.14*radius*radius);
   }
```

再來注意到 main() 中，前面提到 抽象類別不能建立物件，因此透過子類別來建立：

```
CRectangle rect=new CRectangle(5,10);
CCircle cir=new CCircle(2.0);
```

再使用建立的物件 呼叫父類別一般函數 setColor() 設定顏色：

```
rect.setColor("Yellow");
cir.setColor("Green");
```

最後是呼叫子類別中改寫後的函數 show() 用來印出顏色以及面積：

```
rect.show();
cir.show();
```

程式執行結果：\
color=Yellow, area=50\
color=Green, area=12.56

## 整理

* 抽象類別 可定義「一般函數」與「抽象函數」
* 抽象類別 不能用來建立物件
* 抽象函數 不定義其處理方式
* 子類別繼承而來的抽象函數 與改寫overriding 技術相同
* 針對子類別會使用到的條件，可以在父類別中定義抽象函數來使用

## 注意

抽象函數 只能宣告為 公有public 或 保護protected，\
因為子類別必須取用，所以不能宣告成 私有private！


# 使用抽象類別型態的變數建立物件

在基本概念篇提到過，無法使用抽象類別直接建立物件，那麼是否有想過 有沒有其他方式呢？\
這篇就是要來談談這點，並且再提抽象類別的注意事項。

在 [overriding 篇](/java-11/java-overriding) 有提過，父類別變數可以用來存取子類別物件成員；\
在此處 抽象類別 一樣適用。

因此我們將上一篇 [範例：抽象類別](/java-12/java-12-2) 中的範例 main() 部分改成

```
public static void main(String args[])
   {
      CShape shape1=new CRectangle(5,10);
      shape1.setColor("Yellow");
      shape1.show();

      CShape shape2=new CCircle(2.0);
      shape2.setColor("Green");
      shape2.show();
   }
```

程式的執行結果也是會與上一篇的範例相同的：\
color=Yellow, area=50\
color=Green, area=12.56

以下為完整程式碼：

```
abstract class CShape               // 定義抽象類別 CShape
{
   protected String color;
   public void setColor(String str)    // 一般的函數
   {
      color=str;
   }
   public abstract void show(); // 只定義函數名稱的 抽象函數
}

class CRectangle extends CShape    // 子類別 矩形 CRectangle
{
   protected int width,height;
   public CRectangle(int w,int h)  //建構元
   {
      width=w;
      height=h;
   }
   public void show()      // 定義繼承而來的抽象函數 show() 的處理方式
   {
      System.out.print("color="+color+",  ");
      System.out.println("area="+width*height);
   }
}
class CCircle extends CShape     // 子類別 圓形CCircle
{
   protected double radius;
   public CCircle(double r)   //建構元
   {
      radius=r;
   }
   public void show()      // 定義繼承而來的抽象函數 show() 的處理方式
   {
      System.out.print("color="+color+",  ");
      System.out.println("area="+3.14*radius*radius);
   }
}
public class abstract_02
{
   public static void main(String args[])
   {
      CShape shape1=new CRectangle(5,10);
      shape1.setColor("Yellow");
      shape1.show();

      CShape shape2=new CCircle(2.0);
      shape2.setColor("Green");
      shape2.show();
   }
}
```

在 [overriding 篇](/java-11/java-overriding) 的文末，示範了使用父類別的陣列變數來存取子類別物件；\
我們可以將剛剛的abstract\_02範例，

```
CShape shape1=new CRectangle(5,10);
CShape shape2=new CCircle(2.0);
```

改成透過父類別陣列變數來存取：

```
CShape shape[];   //宣告陣列變數
shape=new CShape[2];
```

改成這樣有個好處是，如果物件數量一多，可以讓程式碼更有一致性：

![](/files/-MPaHP-UXZHnanCmDljg)

## 注意

最後要再次提醒，本篇是透過父類別變數方式來存取子類別物件成員；

抽象類別是不能直接建立物件的！

其原因也在於，抽象類別只定義名稱，並無定義處理方式；\
所以建立的物件會不曉得如何使用抽象類別中的抽象函數。

抽象類別中的抽象函數，子類別一定要改寫（overriding）之，\
其原因是定義後處理方式，物件才知道該如何運作。


# 認識介面

介面（interface）具有資料成員 與 抽象函數；那麼 他與「抽象類別」有何不同之處？

* 介面中的資料成員必須設定初值
* 介面沒有一般函數，只有抽象函數

進入範例之前，先了解一下 介面 的格式：

```
interface 介面名稱
{
  final 資料型態 成員名稱=常數;

  public abstract 傳回值資料型態 函數名稱(引數...);
  //抽象函數並無定義處理方式
}
```

因為介面只有抽象函數，abstract 的關鍵字可以省略；\
而資料成員部分：因為此值不會被更改，final 也是可以省略的。

抽象函數的修飾子也只能使用公有 public (或不宣告)，\
為了都能取用到，所以不能宣告 私有private或保護 protected 。

再次強調一次重點：

* 介面中的資料成員必須設定初值
* 介面沒有一般函數，只有抽象函數

知道介面只含有抽象函數，不禁也讓我們聯想到：\
抽象函數只定義其名稱，而沒有處理方式，因此介面跟抽象類別一樣無法直接建立物件。

Ｑ：那麼我們該如何透過定義好的介面來造一個類別，並透過之來建立物件？

Ａ：使用 介面 a 來打造 類別 b ，我們稱為介面的實作（implementation）、以類別b實作介面a

介面的實作（implementation）格式也很簡單：

```
class 類別名稱 implements 介面名稱
{
   ...
}
```

下一篇將透過完整範例做更加詳盡的解說，加深對介面實作 的使用。


# 範例：介面

## 複習

在進入範例之前，再複習一次介面的重點：

* 介面中的資料成員必須設定初值
* 介面沒有一般函數，只有抽象函數

沿用一直以來喜歡使用的例子，CShape圖形、 CCircle圓形、 CRectangle矩形：\
套到本次範例 以類別CCircle圓形、 CRectangle矩形 實作CShape圖形介面

## 範例

### 定義介面

```
interface CShape               // 定義介面 CShape
{
   final double PI=3.14;        //final 可省略
   public abstract void show(); //public abstract 可省略
}
```

在剛剛定義了 CShape 介面，現在示範 CCircle 圓形類別 ：

```
class CCircle implementation CShape
{
   double radius;
   public CCircle(double r)   //CCircle建構元
   {
     radius=r;
   }
   public void show()         //改寫介面中抽象函數
   {
     System.out.println("面積＝"+PI*radius*radius);
   }
}
```

同理，撰寫 CRectangle 矩形類別

```
class CRectangle implements CShape
{
   int width,height;
   public CRectangle(int w,int h)  //CRectangle建構元
   {
      width=w;
      height=h;
   }
   public void show()
   {
      System.out.println("area="+width*height);
   }
}
```

### 主程式

到目前，我們已經學會如何定義介面，並以類別實作介面；接下來撰寫 main()

```
public static void main(String args[])
{
      CRectangle rect=new CRectangle(5,10);
      rect.show();

      CCircle cir=new CCircle(2.0);
      cir.show();
}
```

除了透過實作介面的類別建立物件的方式之外，我們也當然要談談 既然無法直接透過介面來建物件：

我們可以使用介面來宣告變數，甚至物件數量一多也可以宣告陣列：

```
public static void main(String args[])
{
      CShape shap01,shap02;    //透過介面宣告變數
      shap01=new CRectangle(5,10);
      shap01.show();

      shap02=new CCircle(2.0);
      shap02.show();
}
```

我們可以看到，透過介面型態的變數 shap01 shap02 來指向新建立的物件。\
不管main()中是使用何種方式，其執行結果皆為：\
area=50\
area=12.56

### 完整範例程式碼

```
interface CShape               // 定義介面 CShape
{
   final double PI=3.14;        //final 可省略
   public abstract void show(); //public abstract 可省略
}

class CCircle implementation CShape
{
   double radius;
   public CCircle(double r)   //CCircle建構元
   {
     radius=r;
   }
   public void show()         //改寫介面中抽象函數
   {
     System.out.println("面積＝"+PI*radius*radius);
   }
}
class CRectangle implements CShape
{
   int width,height;
   public CRectangle(int w,int h)  //CRectangle建構元
   {
      width=w;
      height=h;
   }
   public void show()
   {
      System.out.println("area="+width*height);
   }
}
public class interface01
{
   public static void main(String args[])
   {
      CShape shap01,shap02;    //透過介面宣告變數
      shap01=new CRectangle(5,10);
      shap01.show();

      shap02=new CCircle(2.0);
      shap02.show();
   }
}
```


# 介面中的多重繼承

本篇的用意是，許多人會希望同時繼承多個父類別，如此一來便能使用到每個父類別的功能。\
但是 Java 卻又不允許這樣。

不過！透過 介面 就能達到多重繼承。

在先前的範例中，只有使用類別來實作一個介面，但其實類別可以實作兩個以上的介面。

做法很簡單：

```
class 類別名稱 implements 介面1名稱,介面2名稱,介面3名稱...
{
   ...
}
```

帶入實際的範例來更明白本篇的描述：取自上一篇［Java］介面 interface（基本範例解說）中範例\
CCircle 這個類別想要實作兩個介面，分別是CShape 與 CColor；一個是可用來計算面積、一個是設定顏色。

我們先定義這兩個介面（本範例僅定義兩個介面做示範）

```
interface CShape            // 定義介面 CShape
{
   final double PI=3.14;
   public abstract void show();
}
```

```
interface CColor              // 定義介面 CColor
{
  abstract void setColor();
}
```

在上方我們定義了兩個介面，CShape 與 CColor；接下來要實作這兩個介面的寫法也很簡單：\
（以下撰寫程式的順序不為絕對方式，這是我自己的方式 僅供參考）

可先將大概的格式先撰寫出：

```
class CCircle implements CShape,CColor
{
   public void setColor()
   {
      ...
   }
   public void show()
   {
      ...
   }
}
```

因為分別繼承了來自兩個的抽象函數，因此我們也必須在子類別當中處理這兩樣函數的定義。

再來是為了設半徑 radius 給圓，撰寫一個建構元：

```
public CCircle(double r)
{
   radius=r;
}
```

為了讓在 main() 中可以正常呼叫 setColor 函數並設一個顏色（String），與呼叫show() 來顯示面積\
所以我們開始撰寫繼承來的兩個函數功能：

```
public void setColor(String str)
{
   color=str;
   System.out.println("color="+color);
}
public void show()
{
   System.out.println("area="+PI*radius*radius);
}
```

以下就能完成實作的程式碼了！

```
class CCircle implements CShape,CColor
{
   double radius;
   String color;
   public CCircle(double r)
   {
      radius=r;
   }
   public void setColor(String str)
   {
      color=str;
      System.out.println("color="+color);
   }
   public void show()
   {
      System.out.println("area="+PI*radius*radius);
   }
}
```

最後來到 main() 的部分：

```
public static void main(String args[])
{
    CCircle moon=new CCircle(5.0);
    moon.setColor("yellow");
    moon.show();
}
```

經過以上簡單的小程式演示了，使用類別來繼承多個介面 來達到多重繼承的目的。

最後附上完整程式碼：

```
interface CShape            // 定義介面 CShape
{
   final double PI=3.14;
   public abstract void show();
}
interface CColor              // 定義介面 CColor
{
  abstract void setColor();
}
class CCircle implements CShape,CColor
{
   double radius;
   String color;
   public CCircle(double r)
   {
      radius=r;
   }
   public void setColor(String str)
   {
      color=str;
      System.out.println("color="+color);
   }
   public void show()
   {
      System.out.println("area="+PI*radius*radius);
   }
}
public class interface02
{
    public static void main(String args[])
    {
      CCircle moon=new CCircle(5.0);
      moon.setColor("yellow");
      moon.show();
    }
}
```


# 介面的延伸

前三篇從 帶入介面的觀念，到使用介面來達到多重繼承；而本篇則是要再將父子關係帶入介面！

介面也可以跟類別一樣，有 子介面 繼承 父介面。\
還有一點相同的是：也是使用 extends 關鍵字 來做介面的繼承。

與類別不同的是，介面可以繼承多個介面！

## 複習

介面 的格式：

```
interface 介面名稱
{
  final 資料型態 成員名稱=常數;

  public abstract 傳回值資料型態 函數名稱(引數...);
  //抽象函數並無定義處理方式
}
```

而介面的繼承也很簡單：

```
interface 子介面名稱 extends 父介面1名稱,父介面2名稱,...
{
  ...
}
```

讓我們透過實例了解本篇內容吧：將上一篇 [多重繼承](/java-13/java-13-3) 中的範例拿來本次改寫使用：

而這次 CShape 這個父介面的功能有資料成員PI 以及設定顏色的抽象函數 setColor()；\
CShape2D 則是用來顯示面積的用途。

![](/files/-MPaIw74smUz4r6WjH0n)

## 定義

### 父介面

```
interface CShape              //父介面 CShape
{
   final double PI=3.14;
   abstract void setColor(String str);
}
```

### 子介面繼承自父介面

```
interface CShape2D extends CShape  //CShape2D繼承父介面CShape
{
   abstract void area();
}
```

上方的介面繼承示意圖當中也有註明 extends 與 implements ，提醒實作介面時是使用implements。

另外在定義介面時，往往程式碼也是蠻簡潔的，\
因為重點都是寫在最後一個類別實作介面的敘述當中。

```
class CCircle implements CShape2D  //實作 CShape2D子介面
{
   double radius;
   String color;

   public CCircle(double r)
   {
      radius=r;
   }
   public void setColor(String str)
   {
      color=str;
      System.out.println("color="+color);
   }
   public void show()
   {
      System.out.println("area="+PI*radius*radius);
   }
}
```

另外可以注意到，CCircle 類別可以取用到 資料成員PI。\
原因是因為 CShape2D 繼承來自父介面的成員，因此類別實作子介面時也能取用到。

## 完整程式碼

```
interface CShape              //父介面 CShape
{
   final double PI=3.14;
   abstract void setColor(String str);
}

interface CShape2D extends CShape  //CShape2D繼承父介面CShape
{
   abstract void area();
}

class CCircle implements CShape2D  //實作CShape2D介面
{
   double radius;
   String color;

   public CCircle(double r)
   {
      radius=r;
   }
   public void setColor(String str)
   {
      color=str;
      System.out.println("color="+color);
   }
   public void show()
   {
      System.out.println("area="+PI*radius*radius);
   }
}
public class interface03
{
   public static void main(String args[])
   {
      CCircle moon=new CCircle(5.0);
      moon.setColor("yellow");
      moon.show();
   }
}
```


# 認識例外

程式在編譯時沒有錯誤訊息，卻在程式執行時發生錯誤。在 Java 中稱為「例外 exception」。

常見例外如：

* 整數除以0
* 輸入時型態的衝突 （例：希望使用者輸入數字，但卻輸入字母）
* 陣列索引值超出容許範圍
* .. 等）

除了使用冗長的 if else 或switch 敘述做判斷，Java 例外處理的機制不僅能提高效率、且增加程式穩定性。

試查看以下程式碼：

```
public class exception01 {

	public static void main(String[] args) {
		int arr[]=new int[5];
	      arr[10]=7;
	      System.out.println("例外範例1");
	}
}
```

以上範例程式是可以正常編譯的，但卻在執行時會顯示錯誤訊息：

![](/files/-MPaJvUNB0KCR8ennF6K)

> Exception in thread “main” java.lang.ArrayIndexOutOfBoundsException: 10
>
> at test.exception01.main(exception01.java:7)

從錯誤訊息當中，我們可以得知：第7行因為索引值超過容許範圍。\
Java 發現這個錯誤，並拋出例外；

其中，常串的英文錯誤可從字母大寫開頭做拆開整理：\
Array Index Out Of Bounds Exception : 10\
意指「 陣列 索引職 超出範圍例外：10 」。

經過以上簡單範例，得知 Java 預設的例外處理會 「拋出例外」 並且「停止程式執行」。

在 Java 當中，例外以類別的型態存在；而在 Java 那也含有預設的例外類別 來做處理（如以上範例）。\
不過 除了內建的例外處理之外，也允許自行定義例外類別的。

下篇將會帶入如何例外處理（exception handing）


# 例外處理

在上一篇中的範例，因為在程式第7行就已把例外拋出，因此從第8行開始就不被執行了。

![](/files/-MPaJvUNB0KCR8ennF6K)

但如果能夠捕捉(catch) 例外的程式碼，就能妥善的處理；這樣的方式稱為例外處理（exception handing）。

## 例外處理基本格式

```
try
{
    想要檢查的敘述;
}
catch()
{
    當例外發生，處理的敘述;
}
finally
{
    一定被執行的敘述;
}
```

將上述語法的步驟繪成以下流程圖，進一步了解運作方式：

![](/files/-MPaKEtDV_u3UTHH3r7N)

1. 若 try 區塊有例外發生，程式會中斷並拋出例外類別產生的物件。
2. 若拋出的物件是 catch 想要捕捉的例外，將會執行catch中的敘述。
3. 無論 try 有例外發生，或是catch 有無捕捉到例外，皆會執行 finally 中的敘述。
4. 最後就會執行 try catch finally 以外的程式敘述。

> 注意：格式中的大括號 {} ，即使敘述只有一行也不能省略！

下篇將會開始帶入如何例外處理（exception handing）的實作範例。<br>


# 範例：例外處理

［Java］例外處理 exception handing（基本概念）中提到「例外類別」，指的是 拋出的物件所屬類別。

![](/files/-MPaJvUNB0KCR8ennF6K)

> Array Index Out Of Bounds Exception 就是例外類別的一種

我們將以上的範例做修改，加入try catch finally 的敘述：

```
public static void main(String args[])
{
   try
   {
      int arr[]=new int[5];
      arr[10]=7;
   }
   catch(ArrayIndexOutOfBoundsException e)
   {
      System.out.println("索引值超出範圍");
   }
   finally        // 此區塊那敘述一定會被執行
   {
   System.out.println("finally 中敘述");
   }
   System.out.println("try-catch-finally 敘述之外");
}
```

讓我們再看一次流程圖：

![](/files/-MPaKEtDV_u3UTHH3r7N)

因此 try 中敘述將檢查是否有例外發生

```
try
{
   int arr[]=new int[5];
   arr[10]=7;
}
```

如果有例外發生，而且這個例外又是ArrayIndexOutOfBoundsException 類別，\
就會印出 索引值超出範圍

```
catch(ArrayIndexOutOfBoundsException e)
{
      System.out.println("索引值超出範圍");
}
```

最後不管以上例外是否發生與捕捉，finally 中敘述都會被執行，\
因此會印出 finally 中敘述

```
finally        // 此區塊那敘述一定會被執行
{
   System.out.println("finally 中敘述");
}

```

> 透過例外的機制，除了能夠讓程式執行到最後，而且還能加上錯誤訊息的提示。

那麼， 在例外類別後方又加上一個 e 是什麼意思呢？

```
catch(ArrayIndexOutOfBoundsException e)
```

e 是一個可以自己命名的變數名稱，也就是說 這是用例外類別建立的類別變數。\
我們可以透過這個變數來顯示例外的相關訊息：

```
catch(ArrayIndexOutOfBoundsException e)
{
   System.out.println("索引值超出範圍");
   System.out.println("訊息："+e);
}
```

如此一來，如果有例外發生，而且這個例外又是ArrayIndexOutOfBoundsException 類別，\
就會印出 索引值超出範圍 且能夠印出 訊息：Array Index Out Of Bounds Exception : 10


# 認識例外類別

於「例外」該主題開始，使用的範例中提到 java.lang.ArrayIndexOutOfBoundsException\
例外類別，那麼本篇將列出較常見的一些例外類別：

繼承來自java.lang.Throwable 類別的 兩大類， java.lang.Error 與 java.lang.Exception

Error 是用來處理會嚴重影響程式的錯誤，所以我們不會去捕捉這類的錯誤。\
Exception 為一些較一般的例外，而且我們可以做適當的處理使程式繼續執行。

![](/files/-MPaKml_nWpGgZ5bc9N3)

有趣的是，如果拋出的例外是 RuntiomeException 類別，或是其下方的子類別，\
就算不撰寫例外處理的程式敘述，依然是可以 編譯成功。 （如「例外」該主題開始，使用的範例）

相反的，如果拋出為 IOException 是必須撰寫例外處理的程式敘述。

所以當例外發生，catch() 接收 Throwable 子類別產生的物件。\
在 [上篇例外處理的範例](/java-14/java-14-3) 文末提到了

```
catch(ArrayIndexOutOfBoundsException e)
```

catch() 括號內只會接收子類別產生的物件，其他物件是不會接收的。\
而這個 e 我們從上方的流程關係圖可以得知，\
Throwable -> Exception -> RuntimeException\
-> IndexOutOfBoundsException -> ArrayIndexOutOfBoundsException 所建立的物件。

於下篇將會談，那麼 catch() 括號內只接收一種類別，如何捕捉多個例外？


# 例外類別捕捉多個例外

於 [認識例外類別](/java-14/java-14-4) 文末提到了，catch() 括號內只接收一種類別，\
那麼如何捕捉多個例外？

本篇將會介紹：

* 捕捉一種以上的例外
* 捕捉所有例外

在 [例外處理](/java-14/java-14-2) 了解例外處理的基本格式：

```
try
{
    想要檢查的敘述;
}
catch()
{
    當例外發生，處理的敘述;
}
finally
{
    一定被執行的敘述;
}
```

想要捕捉一種以上的例外很簡單！我們只需要：

```
try
{
    想要檢查的敘述;
}
catch(第一種例外類別 e)
{
    當例外發生，處理的敘述;
}
catch(第二種例外類別 e)
{
    當例外發生，處理的敘述;
}

finally
{
    一定被執行的敘述;
}
```

透過多個 catch() 就可以捕捉一種一上的例外囉！ （該不會遭毆 σ(´∀｀\*)

那麼如何捕捉所有例外！？

在 [認識例外類別](/java-14/java-14-4) 提過的流程圖：

![](/files/-MPaKml_nWpGgZ5bc9N3)

我們只需要在類別部分，直接加上最上頭的 Excepiton 就可以捕捉所有的例外了！

```
catch(Exception e)
{
    當例外發生，處理的敘述;
}
```

當然要注意，提到的 RuntiomeException 類別，與 IOException類別；\
所以如果使用 Excepiton 的話，是必須撰寫例外處理的程式敘述。

最後還是有一點是需要更多加注意的：\
當我們捕捉多個例外時，範圍小的例外要先撰寫，而範圍大的例外排在之後。

如上方提到 Excepiton，因為是範圍較大的例外，因此必須排在範圍較小的後面，參考底下：

```
catch(ArrayIndexOutOfBoundsException e)
{
 當例外發生，處理的敘述;
}

catch(Exception e)
{
 當例外發生，處理的敘述;
}
```

流程圖中，ArrayIndexOutOfBoundsException 是範圍較小的例外，\
因此優先撰寫，而 Excepiton 排在範圍較小的後面。

> 如果把範圍大的先寫在前，那麼不管何種例外都會被捕捉，\
> 會有這樣的寫法，我的想法是 先透過範圍小的例外做過濾；如果有未過濾到的部分就可透過大範圍的例外來捕捉。


# 於程式中拋出例外

在例外的章節中，使用了 try-catch-finally，本篇解說拋出例外並由 catch接收例外。

> 拋出例外會使用到 throw 這個關鍵字。
>
> ```
> throw 例外類別產生之物件
> ```

而拋出例外時， throw 敘述應使用 new 關鍵字產生物件。

以下範例：令 a,b兩變數，其中b為0 來測試以上的說法。

```
int a=100,b=0;

try
{
  if(b==0)
  throw new ArithmeticException();
  else
  System.out.println(a+"/"+b+"="+a/b);
}
catch(ArithmeticException e)
{
  System.out.println(e+" 被拋出");
}
```

如果當b等於0，就拋出 ArithmeticException 這個例外；\
如果b 不等於0，那麼就計算 a/b。

在 [認識例外](/java-14/java-14-1) 中提過一個常見例外：整數除以0

因此此範例執行後，會印出

```
java.lang.ArithmeticException 被拋出
```

可能會有疑問，即使沒有撰寫 throw new ArithmeticException(); 程式也會拋出內建的例外。

本篇的用意在於，若有自己寫的例外類別 程式是無法自動拋出的，\
因此必須透過本篇的介紹 來拋出。


# 於指定函數拋出例外

在上篇 [於程式中拋出例外](/java-14/java-14-6)  介紹了如何在程式中拋出例外，\
那麼若遇到函數中的程式碼發生例外，卻沒有 try-catch-finally 區塊來進行捕捉例外；\
因此，

> 如果函數會拋出例外，那麼可以將函數的 呼叫 撰寫在 try-catch-finally 區塊。
>
> ```
> 函數名稱() throws 例外類別
> {
>    函數中之程式碼
> }
> ```

與上篇不同在於 throw 是否有加 s，兩者關鍵字是有不同的。

* 函數內部拋出例外，使用 throw
* 指定函數拋出例外，使用 throws

讓我們將上篇範例修改一下：

將 a/b 寫成一個函數

```
public static void abc(int a,int b) throws ArithmeticException
{
   int ans;
   ans=a/b;
   System.out.println(a+"/"+b+"="+ans);
}
```

因此在 try-catch-finally 區塊就可以直接呼叫

```
try
{
   abc(100,0);
}
catch(ArithmeticException e)
{
   System.out.println(e+" 被拋出");
}
```

當呼叫abc(100,0) 時，b為0 因此會拋出 ArithmeticException 例外。\
而本範例是撰寫在同個 class 內，因此可以直接呼叫abc() 函數

```
public static void abc(int a,int b) throws ArithmeticException
{
   int ans;
   ans=a/b;
   System.out.println(a+"/"+b+"="+ans);
}

public static void main(String args[])
{
   try
   {
      abc(100,0);
   }
   catch(ArithmeticException e)
   {
      System.out.println(e+" 被拋出");
   }
}
```

若在不同類別，想要拋出例外；\
我們只要稍微改一下：

函數是在 test 類別中，在main() 中撰寫try-catch-finally 區塊來處理例外；\
這時只需要在呼叫函數時，加上函數所在的類別名稱即可。

以下範例：

```
class test
{
    public static void abc(int a,int b) throws ArithmeticException
    {
       int c=a/b;
       System.out.println(a+"/"+b+"="+c);
    }
}

public class throws_ex
{
   public static void main(String args[])
   {
      try
      {
         test.abc(100,0);
      }
      catch(ArithmeticException e)
      {
         System.out.println(e+" throwed");
      }
   }
}
```


# 自定義例外類別

## 複習

![](/files/-MPaKml_nWpGgZ5bc9N3)

所有能夠處理的例外都是繼承 Exception 類別，因此我們自行設計的例外類別不外乎就是以下格式：

```
class 例外類別名稱 extends Exception
{
   //自行定義
}
```

## 範例

首先自行設計一圓形例外，因為內建的 Exception 擁有極豐富的函數，因此甚至我們能不撰寫敘述也能使用

```
class CircleException extends Exception
{
}
```

在圓形類別當中，指定函數拋出例外（觀念複習 throws , new）

```
class CCircle
{
   private double radius;
   public void setRadius(double r) throws CCircleException
   {
      if(r<0)
      {
         throw new CircleException();
      }
      else
         radius=r;
   }

   public void show()
   {
      System.out.println("面積="+3.14*radius*radius);
   }
}
```

並在main() 中撰寫 try-catch-finally 區塊（觀念複習 不同類別拋出例外）

```
try
{
   CCircle.setRadius(-100.0);
}
catch(CircleException e)
{
   System.out.println(e+" 被拋出");
}
```

## 完整程式碼範例

```
class CircleException extends Exception
{
}
class CCircle
{
   private double radius;
   public void setRadius(double r) throws CircleException
   {
      if(r<0)
      {
         throw new CircleException();
      }
      else
         radius=r;
   }

   public void show()
   {
      System.out.println("面積="+3.14*radius*radius);
   }
}

public class exception_ex
{
   public static void main(String args[])
   {
      CCircle cir=new CCircle();
      try
      {
         cir.setRadius(-100.0);
      }
      catch(CircleException e)
      {
         System.out.println(e+" 被拋出");
      }
      cir.show();
   }
}
```

1. 自行撰寫例外類別。
2. CCircle 類別為一個有判斷半徑的setRadius函數以及計算圓面積的函數\
   在判斷半徑的setRadius函數，我們指定之拋出例外；因此若半徑小於0時 將會拋出例外。
3. 在main() 函數中，撰寫了 try-catch-finally 區塊 用來接收例外。\
   （從不同類別的函數拋出例外）

在此範例，呼叫CCircle 中的setRadius 函數，傳入-100 的值\
因此會拋出例外。\
這時 catch 捕捉後會印出CircleException 被拋出，再執行最後的 cir.show();

此範例的輸出：\
CCircleException 被拋出\
area=0.0

## 整理

> Ｑ：為什麼面積會是零呢？
>
> Ａ：因為例外拋出後，就沒有再做後續的處理；因此面積沒有被計算到 所以就為0


# 認識多執行緒

多執行緒可以讓程式不必跟著一定的流程執行，或一次只能執行一個區塊。

Ｑ：那麼執行緒是什麼？\
Ａ：執行緒 （thread） 意指程式的執行流程。

有些迴圈的執行緒需要花較多的時間，此刻我們可以啟動另一個執行緒來執行。

> 多執行緒 （multi-thread）就是可以同時執行多個程式區塊。

讓我們先看看 如果有個空迴圈執行緒較長，的一個不好的範例：

```
class multi_Test
{
   private String name;
   public multi_Test(String str)
   {
      name=str;
   }
   public void show()
   {
      for(int i=0;i<3;i++)
      {
         for(int j=0;j<100000000;j++);
         System.out.println(name);
      }
   }
}

public class multi_thread_1
{
   public static void main(String args[])
   {
      multi_Test apple=new multi_Test("apple");
      multi_Test pen=new multi_Test("pen");
      apple.show();
      pen.show();
   }
}
```

在上方的範例當中，故意加了一行空的迴圈 來拖慢下一行的執行速度

```
for(int j=0;j<100000000;j++);
```

另外，apple.show(); 執行後 才會輪到pen.show();\
這是單一執行緒的限制；如果啟動多個執行緒，我們便可以讓他們同時執行。

在下一篇將會介紹如何啟動執行緒。


# 啟動執行緒

在上篇 [認識多執行緒](/java-15/java-15-1) 當中，提了一個不好的範例做示範。\
那我們該如何啟動多個執行緒？

* 建立延伸自 Thread 類別的子類別
* 執行緒必須寫在 run() 函數中

滿足以上兩點便可啟動執行緒。那麼一定會有疑惑：\
建立子類別？ run() 函數？

Thread 就是執行緒的意思，他存放在 java.lang 類別庫當中，平時會自動載入。\
而 run() 是Thread類別當中的一個函數。

那麼瞭解了這些，我們該怎麼啟動執行緒呢？

```
class 類別名稱 extends Thread
{
   資料成員;
   函數;
   run()
   {
     //執行緒
   }
}
```

會建立子類別的目的，是我們可以進行 override 改寫的動作，改寫來自Thread類別當中的 run() 函數。

讓我們修改在［Java］多執行緒（基本觀念）當中不好的範例做示範：

首先建立子類別：

```
class multi_Test extends Thread
{
   private String name;
   public CTest(String str)
   {
      name=str;
   }
   public void run()
   {
      //執行緒
   }
}
```

並將執行緒寫在run()函數中：

```
public void run()
   {
      for(int i=0;i<3;i++)
      {
         for(int j=0;j<100000000;j++);
         System.out.println(name);
      }
   }
```

在main() 中：使用 start來呼叫。

```
multi_Test apple=new multi_Test("apple");
multi_Test pen=new multi_Test("pen");
apple.start();
pen.start();
```

這時疑問來了，為什麼要使用 start() ？\
如果直接呼叫 run() ，這樣並無法啟動執行緒，只是再將執行緒執行一遍而已；\
因此結果只是會與在［Java］多執行緒（基本觀念）當中，不好的範例相同。

因為我們繼承了來自 Thread 類別，也會繼承當中的 start() 。\
而這個 start() 會在 scheduler 排程器中登入執行緒，當執行緒開始執行 run() 才被呼叫。

## 完整程式碼

```
class multi_Test extends Thread
{
   private String name;
   public CTest(String str)
   {
      name=str;
   }
   public void run()
   {
      for(int i=0;i<3;i++)
      {
         for(int j=0;j<100000000;j++);
         System.out.println(name);
      }
   }
}

public class multi_thread_2
{
   public static void main(String args[])
   {
      multi_Test apple=new multi_Test("apple");
      multi_Test pen=new multi_Test("pen");
      apple.start();
      pen.start();
   }
}
```


# 實作 RUNNABLE 介面

若本身類別已經繼承了其他父類別，但又想繼承 Thread 類別，這時就會遇到 Java 無法多重繼承。\
想到多重繼承就會想到 介面，在［JAVA］多重繼承中提到：透過 介面 就能達到多重繼承。

Java 提供了 Runnable 介面，有抽象函數 run() ；因此我們只需要定義 run() 即可。

改寫 [啟動執行緒](/java-15/java-15-2#wan-zheng-cheng-shi-ma) 中的範例：

讓我們先實作介面

```
class multi_Test implements Runnable
{
   private String name;
   public multi_Test(String str)
   {
      name=str;
   }
   public void run()
   {
   }
}
```

且詳細定義 run函數

```
public void run()
   {
      for(int i=0;i<4;i++)
      {
         for(int j=0;j<100000000;j++);
         System.out.println(id+" is running..");
      }
   }
```

而在main() 中：我們建立物件後，再分別產生 Thread 類別的物件 來呼叫 start() 啟動執行緒

```
multi_Test apple=new multi_Test("apple");
multi_Test pen=new multi_Test("pen");
Thread m1=new Thread(apple);
Thread m2=new Thread(pen);
m1.start();
m2.start();
```

## 完整程式碼

```
class multi_Test implements Runnable
{
   private String name;
   public multi_Test(String str)
   {
      name=str;
   }
   public void run()
   {
      for(int i=0;i<3;i++)
      {
         for(int j=0;j<100000000;j++);
         System.out.println(id+" is running..");
      }
   }
}

public class multi_thread_3
{
   public static void main(String args[])
   {
      multi_Test apple=new multi_Test("apple");
      multi_Test pen=new multi_Test("pen");
      Thread m1=new Thread(apple);
      Thread m2=new Thread(pen);
      m1.start();
      m2.start();
   }
}

```

## 整理

Ｑ：那麼該使用 Thread類別 還是 Runnable介面呢？\
Ａ：其實都可以，了解該類別是否有繼承其他類別。因為類別只能繼承一個類別。

那如果使用多執行緒的類別已經繼承其他類別，就必須實作。\
我們可以這麼寫：

```
class multi_Test extends m_test implements Runnable
{
  ...
}
```

以上意思：multi\_Test 類別繼承 m\_test 類別 並實作 Runnable 介面\
這麼一來multi\_Test 可以使用多執行緒，又能繼承 m\_test 類別。<br>


# 執行緒生命週期

既然是有多個執行緒在執行，管理每個執行緒的工作相當的重要。\
管理好也可以增加執行效率；如哪些執行緒該先睡(sleep) 哪些該暫緩 等等..

而每個執行緒都有他們的生命週期：

## 關係圖

![](/files/-MPaNPw7x-HTxl4H2SzI)

## 生命週期五狀態

* 新產生的 newly created<br>

  > 使用new Thread() 建立物件時，就會進入此狀態。（此時系統不會配置資源）
* 可執行的 runable<br>

  > 當 start() 啟動執行緒，最先搶到資源的先執行 run()；未搶到的將會在 queue 佇列中等待爭取。
* 執行中的 running<br>

  > 先搶到資源的執行緒就會進入這個狀態。（一次僅有一個執行緒會在此狀態）
* 被凍結的 blocked<br>

  > 以下另外討論
* 銷毀的 dead<br>

  > 當 run() 結束，或呼叫 stop() 時就會進入此狀態。（進入此狀態就無法再次啟動）

### 被凍結

被凍結的狀態 我們另外在此討論：碰到以下三點時，就會進入凍結狀態。

1. 呼叫 wait()
2. 呼叫 sleep()
3. 與另一個執行緒 join() 一起時：當有其他執行緒呼叫 join()，原來正執行的執行緒（或程式碼）會先暫停

那麼當執行緒被凍結，就會停止 run() 的執行，等到凍結的因素消失就會回到可執行狀態（排隊搶資源）。\
以下是凍結消失的因素：

* 若執行緒是因為呼叫物件 wait() 而凍結，當物件的 notify() 被呼叫時就可以解凍結。
* 進入睡眠 sleep 狀態，而指定時間已到。

## 附註

sleep() 函數，可用來設定睡眠時間，即 sleep(long millis)。\
毫秒（千分之一秒）。

notify 有「告知」之意。


# 凍結執行緒 sleep()

在 [認識多執行緒](/java-15/java-15-1) 中的範例，故意加了一個拖慢執行緒的迴圈；\
那透過讓執行緒小睡片刻，如此便可稍緩執行緒的執行。

```
for(int i=0;i<3;i++)
{
    for(int j=0;j<100000000;j++);
    System.out.println(name);
}
```

我們可以在迴圈內加個 sleep()，在［Java］執行緒生命週期（介紹）附註中提到：

> sleep() 函數，可用來設定睡眠時間，即 sleep(long millis)。\
> 毫秒（千分之一秒）。

例如 sleep(1000) 就代表睡1秒鐘(1秒後就會醒來)。

因此將迴圈改成：

```
for(int i=0;i<3;i++)
      {
         try
         {
            sleep((int)(1000*Math.random()));
         }
         catch(InterruptedException e){}
         System.out.println(name);
      }
```

sleep() 可能會拋出 InterruptedException  例外，所以我們將 sleep() 撰寫在 try-catch 區塊內。

而 sleep() 的引數 (int)(1000\*Math.random()) 部分：\
我們透過 Math.random() 來產生 0～1的浮點數亂數，乘上1000 即 0\~1000 之間的浮點數亂數；\
最後在前面強制轉型成整數，所以加上(int)。

## 完整的程式碼

```
class multi_Test
{
   private String name;
   public multi_Test(String str)
   {
      name=str;
   }
   public void show()
   {
      for(int i=0;i<3;i++)
      {
         try
         {
            sleep((int)(1000*Math.random()));
         }
         catch(InterruptedException e){}
         System.out.println(name);
      }
   }
}

public class multi_thread_3
{
   public static void main(String args[])
   {
      multi_Test apple=new multi_Test("apple");
      multi_Test pen=new multi_Test("pen");
      apple.start();
      pen.start();
   }
}
```


# 凍結執行緒 join()

在 [執行緒生命週期](/java-15/java-15-4) 中，被凍結的狀態提到的 sleep() 已在 [凍結執行緒 sleep()](/java-15/java-15-sleep) 說明了用法，本篇將說明 join() 的用法。

在 [凍結執行緒 sleep()](/java-15/java-15-sleep) 中的範例，兩個執行緒（apple, pen）可說是同時啟動的，那如果再啟動的下方再加上一行印出 main() 結束 的程式碼

```
apple.show();
pen.show();
System.out.println("main() 結束");
```

## 完整程式碼(錯誤)

```
class multi_Test
{
   private String name;
   public multi_Test(String str)
   {
      name=str;
   }
   public void show()
   {
      for(int i=0;i<3;i++)
      {
         try
         {
            sleep((int)(1000*Math.random()));
         }
         catch(InterruptedException e){}
         System.out.println(name);
      }
   }
}

public class multi_thread_3
{
   public static void main(String args[])
   {
      multi_Test apple=new multi_Test("apple");
      multi_Test pen=new multi_Test("pen");
      apple.start();
      pen.start();
      System.out.println("main() 結束");
   }
}
```

當我們執行的結果會發現，會先印出「main() 結束」。

> 記得 main() 本身其實也是個執行緒，一樣是看誰先搶到資源。\
> 但因為僅印出字串，不需要經過執行緒的啟動，因此通常較先搶到資源 先執行。

這麼一來若是先印出「 main() 結束」 就不是我們想要的目的；\
我們想先apple 執行緒完成後再執行pen 執行緒，\
等到兩個執行緒都結束才正式main() 結束（因此印出字串）。

想要達到這樣的結果，就必須使用到 join() 來處理執行緒的排程。

在 [執行緒生命週期](/java-15/java-15-4) 中提到的觀念：

> 執行緒與另一個執行緒 join() 一起時：\
> 當有其他執行緒呼叫 join()，原來正執行的執行緒（或程式碼）會先暫停

join() 必須寫在 try-catch 區塊內，因此我們可以將上述錯誤的程式這樣改寫：

當 apple 執行緒啟動 便進到try 後，apple執行緒結束後就會往下執行 啟動pen執行緒，\
當pen 執行緒結束後 才會再往下執行 印出字串的程式。

```
apple.start();
try
{
   apple.join();
   pen.start();
   pen.join();
}
catch(){}
System.out.println("main() 結束");
```

在啟動 apple 執行緒後會先停留在 apple.join() 等待該執行緒完成，才會執行下一行程式（啟動pen執行緒）；\
同理 pen 執行緒會停留在 pen.join() ，等到執行緒結束後就會將字串「main() 結束」字串印出。

還有一點要注意的是， join() 會拋出 InterruptedException 例外\
因此才必須將 join() 寫在 try-catch 區塊，而catch 要捕捉 InterruptedException 例外。

```
apple.start();
try
{
   apple.join();
   pen.start();
   pen.join();
}
catch(InterruptedException e){}
System.out.println("main() 結束");
```

## 完整程式碼(正確)

```
class multi_Test
{
   private String name;
   public multi_Test(String str)
   {
      name=str;
   }
   public void show()
   {
      for(int i=0;i<3;i++)
      {
         try
         {
            sleep((int)(1000*Math.random()));
         }
         catch(InterruptedException e){}
         System.out.println(name);
      }
   }
}

public class multi_thread_3
{
   public static void main(String args[])
   {
      multi_Test apple=new multi_Test("apple");
      multi_Test pen=new multi_Test("pen");
      apple.start();
      try
      {
         apple.join();
         pen.start();
         pen.join();
      }
      catch(InterruptedException e){}
      System.out.println("main() 結束");
   }
}
```


# 執行緒的順序

在前篇使用了 join() 來控制安排執行緒。那麼執行緒一多，我們可以安排哪些執行緒優先順序是較高的。

> Java 執行緒的優先順序是使用 1\~10來表示，數字大代表優先權較高（意指先執行）。

有兩個函數與優先權相關：

* setPriority() 用來設定優先順序
* getPriority() 用來取得順序之值

而在 setPriority() 的引數可以使用以下三個常用的代碼：

* MAX\_PRIORTY       最大優先權數值
* MIN\_PRIORTY        最小優先權數值
* NORM\_PRIORTY    系統預設

透過範例來了解一下用法吧～

修改[前一篇的範例](/java-15/java-15-join#wan-zheng-cheng-shi-ma-zheng-que)：此次建立5個執行緒，並在執行前設定優先權。

首先建立5個執行緒

```
multi_Test apple=new multi_Test("red");
multi_Test banana=new multi_Test("yellow");
multi_Test grape=new multi_Test("purple");
multi_Test cloud =new multi_Test("white");
multi_Test grass=new multi_Test("green");
```

設定優先權（使用常見的代碼）

```
apple.setPriority(Thread.MAX_PRIORITY);
banana.setPriority(Thread.MIN_PRIORITY);
```

除了使用常見代碼之外，也可以直接設定1\~10的引數

```
cloud.setPriority(3);
grass.setPriority(6);
```

## 完整程式碼

```
class multi_Test
{
   private String name;
   public multi_Test(String str)
   {
      name=str;
   }
   public void show()
   {
      for(int i=0;i<3;i++)
      {
         try
         {
            sleep((int)(1000*Math.random()));
         }
         catch(InterruptedException e){}
         System.out.println(name+" 執行中"+this.getPriority());
      }
   }
}

public class multi_thread_3
{
public static void main(String args[])
{
   multi_Test apple=new multi_Test("red");
   multi_Test banana=new multi_Test("yellow");
   multi_Test grape=new multi_Test("purple");
   multi_Test cloud =new multi_Test("white");
   multi_Test grass=new multi_Test("green");

   apple.setPriority(Thread.MAX_PRIORITY);
   banana.setPriority(Thread.MIN_PRIORITY);
   cloud.setPriority(3);
   grass.setPriority(6);

   apple.start();
   banana.start();
   grape.start();
   cloud.start();
   grass.start();
   }
}
```


# 同步處理 Synchronized

synchronized 有「同步」的意思，在 Java 中 這個關鍵字可以使各執行緒在時間上做協調，\
即 一次只允許一個執行緒進行處理，而其他的執行緒必須等待上個執行緒處理完後才可以進入處理。

當數個執行緒同時啟動，而且還共用同個變數，就會常發生無法發覺的錯誤。

## 範例題目

例如一個需要計算累加金額的變數 sum，但有兩個執行緒 p1 與 p2 同時共用。

參考以下範例：

慈善捐款接受捐款，而每次會計算出總捐款額(sum)。\
今日有兩位善心人士(p1,p2)，兩人每天(1次)都會捐100元，連續三天過後 總捐款額若無他人捐款，\
那麼總額會是600元。

因應網路塞車等因素：可以加上睡眠，當每接受一次的捐款時，小睡0\~1秒。

### 完整程式碼(未經同步處理)

讓我們先瞧瞧 沒有經過同步處理的程式：

```
class CDonate
{
   private static int sum=0;
   public static void add(int n)
   {
      int tmp=sum;
      tmp=tmp+n;
      try
      {
         Thread.sleep((int)(1000*Math.random()));
      }
      catch(InterruptedException e){}
      sum=tmp;
      System.out.println("捐款總額= "+sum);
  }
}
class CPerson extends Thread
{
   public void run()
   {
      for(int i=1;i<=3;i++)
         CDonate.add(100);
   }
}
public class synchronized_1
{
   public static void main(String args[])
   {
      CPerson p1=new CPerson();
      CPerson p2=new CPerson();
      p1.start();
      p2.start();
   }
}
```

當我們執行結果後，會發現執行結果跟預期的不同：

![](/files/-MPaP0mKtCIr2orPE-Hv)

捐款總額竟然只是300，應該要是600呀？

原來在處理p1執行緒還未結束時，p2也開始進入add()當中，因此造成計算錯誤。\
但透過加上 synchronized 關鍵字後，又會有怎樣的效果呢？

在 add() 加上synchronized 關鍵字

```
public synchronized static void add(int n){}
```

神奇的事情，發生了！！\
執行結果正確了！捐款總額等於600

![](/files/-MPaP3pibLumur7cutNL)

是我們所預期的總捐款金額。

> 那麼想想看，為什麼不用先前提過的 join() 來先限制p1執行緒結束後 再往下執行p2執行緒呢？

從本篇可以了解同步處理的重要與方便性。\
而且我們要注意若共用同個變數，必須特別去注意存取的順序。

### 完整範例程式碼(經同步處理)

```
class CDonate
{
   private static int sum=0;
   public synchronized static void add(int n)
   {
      int tmp=sum;
      tmp=tmp+n;
      try
      {
         Thread.sleep((int)(1000*Math.random()));
      }
      catch(InterruptedException e){}
      sum=tmp;
      System.out.println("捐款總額= "+sum);
  }
}
class CPerson extends Thread
{
   public void run()
   {
      for(int i=1;i<=3;i++)
         CDonate.add(100);
   }
}
public class synchronized_2
{
   public static void main(String args[])
   {
      CPerson p1=new CPerson();
      CPerson p2=new CPerson();
      p1.start();
      p2.start();
   }
}
```


# 產生不重複之亂數

本篇記錄解題時遇到的題目：如何產生不重複的1-10的數字

## 解題步驟

### 宣告陣列

* 首先宣告一個陣列空間為10的 num變數

  ```
  int[] num = new int[10];
  System.out.print(&quot;數字: &quot;);
  ```
* 透過for 迴圈製作1-10的數字

  ```
  for(int i = 0; i &lt; num.length; i++)
  {
    num[i] = i + 1;
    System.out.print(num[i]+&quot; &quot;);
  }
  ```

### **亂數函式**

* 宣告一個陣列 arr 空間為5
* arr陣列用途為放置亂數

  ```
  int[] arr = new int[5];
  ```
* 首先使用亂數製作0-9的數字n，並放入num陣列<br>

  > 假設亂數產生出 n=2 ，那麼 `num[2] = 3`\
  > 以此類推：`num[0]=1, num[1]=2, num[2]=3, …, num[9]=10`
* 再將 `num[2]` 放入 `arr[0]`

| num | \[0] | \[1] | \[2] | \[3] | \[4] | \[5] | \[6] | \[7] | \[8] | \[9] |
| --- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| 值   | 1    | 2    | 3    | 4    | 5    | 6    | 7    | 8    | 9    | 10   |

| arr | \[0] | \[1] | \[2] | \[3] | \[4] |
| --- | ---- | ---- | ---- | ---- | ---- |
| 值   | 3    | 0    | 0    | 0    | 0    |

> 陣列不會因為取代了前方的值，本值就消失\
> 所以最後方\[9]＝10

```
n = (int)(Math.random()*(10-i));
arr[i] = num[n];
```

* 剛剛是透過亂數產生0-9，現在變成產生0-8；目的是不去取用到num\[9]

  ```
  for(int j = n; j &lt; num.length - 1; j++)
  {
    num[j] = num[j+1];
  }
  ```

> 繼續剛剛的假設，如果產生出 n = 8 那麼 num\[ 8 ]＝10\
> 再把 num\[ 8 ]=10 放到 arr\[1] (此為第二個亂數)。

| num | \[0] | \[1] | \[2] | \[3] | \[4] | \[5] | \[6] | \[7] | \[8] | \[9] |
| --- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| 值   | 1    | 2    | 4    | 5    | 6    | 7    | 8    | 9    | 10   | 10   |

| arr | \[0] | \[1] | \[2] | \[3] | \[4] |
| --- | ---- | ---- | ---- | ---- | ---- |
| 值   | 3    | 10   | 0    | 0    | 0    |

* 以此類推，再產生0-7、0-6、0-5 …

  ```
  int n;
  for(int i = 0; i &lt; arr.length; i++)
  {
   n = (int)(Math.random()*(10-i));
   arr[i] = num[n];
   for(int j = n; j &lt; num.length - 1; j++)
  {
        num[j] = num[j+1];
  }
  }
  return arr;
  ```

### **完整程式碼**

```
public class Example
{
public static void main(String[] args)
{
    int[] num = new int[10];
    System.out.print(&quot;數字: &quot;);

    for(int i = 0; i &lt; num.length; i++)
    {
       num[i] = i + 1;
       System.out.print(num[i]+&quot; &quot;);
    }

    System.out.print(&quot;\n隨機不重複產生5個亂數: &quot;);

    int[] Array;

    Array = getRandom(num);

    for(int i = 0; i &lt; 5; i++)

       System.out.print(Array[i] + &quot; &quot;);
}

public static int[] getRandom(int[] num)
{
    int[] arr = new int[5];
    int n;
    for(int i = 0; i &lt; arr.length; i++)
    {
        n = (int)(Math.random()*(10-i));
        arr[i] = num[n];
        for(int j = n; j &lt; num.length - 1; j++)
        {
            num[j] = num[j+1];
        }
    }
    return arr;
}
}
```

### 後記

本文於 iT邦幫忙曾被提出[討論](https://ithelp.ithome.com.tw/questions/10187671)，在此感謝 fysh711426 替我更近一步的解說，以下為 [fysh711426](https://ithelp.ithome.com.tw/users/20106865/profile) 所示範的程式運行：

初值

```
num 1 2 3 4 5 6 7 8 9 10
arr 0 0 0 0 0
```

random n = 5

```
num 1 2 3 4 5 7 8 9 10 10
arr 6 0 0 0 0
```

random n = 5

```
num 1 2 3 4 5 8 9 10 10 10
arr 6 7 0 0 0
```

random n = 3

```
num 1 2 3 5 8 9 10 10 10 10
arr 6 7 4 0 0
```

`10-i` 是因為要讓每次取 num 陣列的範圍變小，\
以上面結果來說明，可以理解成不要取重複 10 的部分。

`num.length – 1` 是因為在把 num 往前移動時，如果不減一，j 在最尾端時 num\[j+1] 就會超出陣列範圍。


# 後記

Java 學習系列是在大學上課時所記錄的重點內容，想想現在已經很久沒有寫 Java 了。\
當初的 Java 學習系列一直都還有流量（代表依然有部分讀者在參閱我的文章 😭 謝謝支持）

> 而這個 Java 學習系列其實並不適合完全無接觸過程式經驗的讀者，當初會記錄主要是提供校內學生參考學習。
>
> 因此閱讀此系列之前，建議擁有基礎的程式學習的經驗，如C、C++、Python 等..

總之，之前的網站要看 Java 學習筆記的內容不是這麼的易讀，因此花了時間將內容整理來 GitBook。

但因為自己長年沒接觸 Java 了（現在都寫 JS 😂），如果有 Java 的問題已經無法像之前一一地為讀者解答..

不過為了讓學習系列依舊保存著，加上未來文章舊站(4xsc.com)可能會歇業一陣子，所以整理成 GitBook 或許也是最好的方式。

目前新網站為 [4-x.tw](https://4-x.tw/)：

{% embed url="<https://4-x.tw/>" %}

如果有任何網站、程式、專案的問題，都歡迎寫信與我討論。

> 作者 / [4C](https://4-x.tw/)
>
> 信箱 / <4xsc.tech@gmail.com>
>
> IG / [w.\_4x](https://www.instagram.com/w._4x/)
>
> 個人網站 / [4-x.tw](https://4-x.tw/)


# 致謝

在 2016 年 9 月大學開學前一週開始寫Java學習系列，希望藉由自學並撰寫文章來使自己對於該主題更加熟悉。另一方面也希望這系列的文章可以幫助同班同學與一同修課的朋友們，因此在撰文的過程除了讓自己看得懂之外，也不斷思考該如何解說與程式範例示範。

從架設個人部落格開始，Java 學習系列文章便是主要輸出的文章。初期常會因個人的惰性等等各種因素而沒有按時發文，導致學習系列文章進度是落後課堂考試範圍。

自從大學系主任方面得知 [iT邦幫忙](http://ithelp.ithome.com.tw/)有鐵人賽一事，二話不說地接下挑戰並且開始努力的構思文章內容。很高興可以藉著鐵人賽不斷的將 Java 學習系列補齊，也順利的完成 2017 iT邦幫忙鐵人賽。相當的感謝 iT邦幫忙提供這樣的一個挑戰，也感謝自己這 30天 順利的熬了過來。

![](/files/-MPnb1JBpl4V7dUL-FIx)

最後謝謝訂閱我的系列文章的iT朋友們，以及持續關注 Java 學習系列文章的朋友們。


