115 司法特考三等 檢察事務官類科電子資訊組 程式語言 考古題詳解
第 一 題5 分
請根據圖 1-1 的 Java 程式碼回答問題。
interface PaymentMethod { boolean pay(int amount); }
class Card implements PaymentMethod { private int limit = 1000;
public boolean pay(int amount) {
if (amount > limit) {
return false;
}
limit -= amount;
return true;
}
}
class Cash implements PaymentMethod { public boolean pay(int amount) { return true; } }
class Order { private int total;
public Order(int total) {
this.total = total;
}
public boolean checkout(PaymentMethod method) {
return method.pay(total);
}
}
圖 1-1
(一) 在 Order 類別中,checkout() 方法的參數型別宣告為 PaymentMethod 介面。請說明為何這樣的設計能讓該方法同時接收 Card 與 Cash 的物件作為參數。(5 分)
(二) 此設計運用了物件導向程式設計的什麼特性?(5 分)
(三) 假設在某類別的 main() 方法中依序執行圖 1-2 的程式碼,請寫出其執行結果。(5 分)
PaymentMethod card = new Card(); PaymentMethod cash = new Cash(); Order order = new Order(600);
System.out.println(order.checkout(card)); System.out.println(order.checkout(cash)); System.out.println(order.checkout(card));
圖 1-2
(四) 此系統欲擴充退款功能,但並非所有付款方式都支援退款,因此新定義了一個 Refundable 介面:
interface Refundable { boolean refund(int amount); }
請在不更動既有類別架構的前提下,完成下列 tryRefund() 方法,實作邏輯如下。(10 分)
- 若傳入的
method物件,其執行時期的類別亦實作Refundable介面,則呼叫其refund(amount)方法。 - 若傳入的
method物件未實作Refundable介面,即不支援退款,則回傳false。
boolean tryRefund(PaymentMethod method, int amount) { // 請在此處填入您的實作程式碼 }
登入後即可作答並保存紀錄。
核心觀念
本題考查 Java 的:
- 介面(
interface)與實作類別 - 子型別多型(polymorphism)
- 介面型別參數
- 執行時期型別判斷(
instanceof) - 型別轉換(casting)
Card 與 Cash 都實作 PaymentMethod,因此兩者都是 PaymentMethod 的子型別。
(一) 為何可同時接收 Card 與 Cash
Card 宣告為:
class Card implements PaymentMethod
Cash 宣告為:
class Cash implements PaymentMethod
因此,Card 與 Cash 都必須提供介面所規定的:
boolean pay(int amount)
在 Java 中,實作介面的類別物件可以向上轉型為介面型別。因此下列指定皆合法:
PaymentMethod card = new Card;
PaymentMethod cash = new Cash;
checkout 的參數雖然宣告為 PaymentMethod,但實際傳入的物件可以是任何實作該介面的類別,例如:
order.checkout(new Card);
order.checkout(new Cash);
checkout 只需要依賴 PaymentMethod 所提供的 pay 方法,不必知道實際物件是 Card 還是 Cash,因此提高程式的彈性與可擴充性。
(二) 使用的物件導向特性
本設計主要運用了「多型(Polymorphism)」。
checkout 的參數型別是 PaymentMethod,但實際執行時可以傳入不同的實作物件:
- 傳入
Card時,執行Card的pay。 - 傳入
Cash時,執行Cash的pay。
相同的:
method.pay(total)
會依據執行時期物件的實際類別,呼叫不同的實作方法,這就是執行時期多型。
同時,此設計也符合「面向介面設計」的概念,使 Order 不需要依賴特定付款類別。
(三) 執行結果
初始建立:
PaymentMethod card = new Card;
Card 的初始額度為:
訂單金額為:
Order order = new Order(600);
第一次付款
order.checkout(card)
執行 Card.pay(600):
付款成功,並更新額度:
輸出:
true
第 二 題10 分
請回答以下關於 Java 程式語言例外處理(Exception Handling)的問題。
(一) 請說明 Java 語言中 checked exception 與 unchecked exception 的差異,並各舉一個 Java 內建的例外類別作為範例。(10 分)
(二) 根據圖 2-1 所示的 Java 程式碼,寫出執行 main() 方法後的完整輸出結果。(5 分)
public class Test { static int parse(String text) { try { return Integer.parseInt(text); } catch (NumberFormatException e) { return -1; } finally { System.out.print("F"); } }
public static void main(String[] args) {
System.out.println(parse("12"));
System.out.println(parse("abc"));
}
}
圖 2-1
(三) 假設有一個類別 Calculator 包含圖 2-2 所示的靜態方法,請撰寫一個 JUnit 5 測試類別,驗證下列三項行為。(10 分)
divide(10, 2)應得到 。divide(10, 0)應拋出IllegalArgumentException。- 例外訊息應為
"division by zero"。
public static int divide(int a, int b) { if (b == 0) { throw new IllegalArgumentException("division by zero"); } return a / b; }
圖 2-2
登入後即可作答並保存紀錄。
核心觀念
本題考查三個 Java 例外處理重點:
checked exception與unchecked exception的差異。try-catch-finally的執行順序,尤其是finally一定會執行。- JUnit 5 對正常回傳值、例外類型與例外訊息的測試方法。
(一)Checked Exception 與 Unchecked Exception
1. Checked exception
checked exception 是編譯器會檢查的例外,程式必須:
- 使用
try-catch捕捉;或 - 在方法宣告中使用
throws向外拋出。
否則程式無法通過編譯。
常見類別為 IOException,例如檔案讀取失敗時可能發生:
public void readFile throws IOException {
// 檔案讀取操作
}
2. Unchecked exception
unchecked exception 不會被編譯器強制要求捕捉或宣告,通常是程式執行期間因邏輯錯誤或不合法操作所產生。
其主要基底類別為 RuntimeException,例如:
NullPointerExceptionArithmeticExceptionNumberFormatExceptionIllegalArgumentException
本題程式中的 NumberFormatException 即屬於 unchecked exception。
3. 差異整理
| 比較項目 | Checked exception | Unchecked exception |
|---|---|---|
| 編譯器是否強制處理 | 是 | 否 |
是否須 catch 或 throws | 必須 | 不必 |
| 常見基底類別 | Exception 的非 RuntimeException 子類別 | RuntimeException |
| 範例 | IOException | NullPointerException |
(二)程式輸出結果
解題方法
執行:
parse("12")
時:
Integer.parseInt("12")成功,回傳12。- 離開
try前,仍會執行finally。 finally先輸出字元F。parse回傳12後,外層的System.out.println再輸出12並換行。
因此第一行為:
F12
執行:
parse("abc")
時:
Integer.parseInt("abc")產生NumberFormatException。- 由
catch捕捉,回傳-1。 - 回傳前仍會執行
finally,先輸出F。 - 外層
println再輸出-1並換行。
因此第二行為:
F-1
解題技巧
遇到下列結構:
第 三 題5 分
已知二元搜尋樹的節點定義如圖 3-1,請根據圖 3-2 的二元搜尋樹回答問題。
typedef struct TNode { int key; struct TNode *left; struct TNode *right; } TNode;
圖 3-1:節點定義
圖 3-2:二元搜尋樹(原卷包含樹狀圖)
(一) 請分別說明二元樹的遞迴走訪函式中,下列兩種情況的目的與執行行為。(5 分)
- 基底情況(Base case)。
- 遞迴情況(Recursive case)。
(二) 執行圖 3-3 的函式,root 為圖 3-2 二元搜尋樹根節點,low = 5,high = 10。
- 其最終回傳值為何?(5 分)
- 請依此函式實際遞迴走訪的先後順序,列出
current = 1節點的key值。(5 分)
int countRange(TNode *root, int low, int high) { if (root == NULL) { return 0; }
int current = (root->key >= low && root->key <= high) ? 1 : 0;
return current
+ countRange(root->left, low, high)
+ countRange(root->right, low, high);
}
圖 3-3
(三) 圖 3-4 的函式可以用來在二元搜尋樹中搜尋指定值 target。
- 請將圖 3-5 的三行指令分別填入圖 3-4 函式 A、B、C 的空格中,每個選項限填一次且不可重複填用。(5 分)
- 根據圖 3-2 的二元搜尋樹,若執行
search(root, 7),請問此函式最後回傳的指標所指向的節點,其key值為何?(5 分)
TNode *search(TNode *root, int target) { while (root != NULL) { if (A) { return root; }
if (B) {
root = root->left;
} else {
root = C;
}
}
return NULL;
}
圖 3-4
圖 3-5 選項:
root->rightroot->key == targettarget < root->key
登入後即可作答並保存紀錄。
核心觀念
本題考查:
- 二元樹遞迴走訪中的基底情況與遞迴情況。
- 二元搜尋樹節點搜尋。
- 遞迴計數與條件判斷。
- 函式呼叫順序。
圖 3-2 的二元搜尋樹結構為:
- 根節點:
- 的左子節點:
- 的右子節點:
- 的左子節點:
- 的右子節點:
(一)二元樹遞迴走訪
1. 基底情況(Base case)
基底情況是遞迴函式停止遞迴的條件,用來處理最簡單或已經沒有子樹的情形。
在二元樹中,通常判斷:
代表目前指標沒有指向任何節點,也就是已經走到空子樹。此時函式直接回傳結果,不再繼續呼叫自己,以避免無限遞迴。
例如:
if (root == NULL)
return 0;
2. 遞迴情況(Recursive case)
遞迴情況是處理目前節點,並繼續對其左子樹與右子樹呼叫相同函式。
二元樹通常分成三個部分:
- 處理目前節點。
- 遞迴處理左子樹。
- 遞迴處理右子樹。
例如本題的 countRange:
return current
+ countRange(root->left, low, high)
+ countRange(root->right, low, high);
這會將目前節點、左子樹及右子樹的結果加總。
(二)countRange 函式
1. 最終回傳值
條件為:
各節點判斷如下:
| 節點 key | 是否落在 | current |
|---|---|---|
| 是 | ||
| 是 | ||
| 否 | ||
| 否 | ||
| 是 |
因此:
2. current = 1 的節點走訪順序
函式先判斷目前節點的 current,再遞迴走訪左子樹,最後走訪右子樹,因此節點判斷順序為:
其中 current = 1 的節點依序為:
(三)二元搜尋樹搜尋函式
1. A、B、C 填空
正確填法為:
第 四 題5 分
某網路書店之網站提供下列 API,網站系統可依客戶端需求回傳 JSON 或 XML 資料。
GET /api/books:查詢所有書籍。GET /api/books/{id}:查詢指定書籍。POST /api/books:新增書籍。DELETE /api/books/{id}:刪除指定書籍。
(一) 請說明 HTTP 的無狀態性(Stateless)是什麼意思。(5 分)
(二) 若此網路書店需要辨識已登入的使用者,伺服器端可以採用哪一種機制來保存或傳遞登入狀態?請舉出一種說明即可。(5 分)
(三) 假設伺服器收到下列請求:
GET /api/books?discount=20
伺服器執行圖 4 的 Python 程式碼,且所有數值,包含計算後的結果,在轉為 JSON 輸出時,皆會強制保留一位小數,即浮點數格式。請寫出最後一行指令在標準 JSON 格式下所輸出的單行完整 JSON 字串結果。(5 分)
books = [ {"id": 1, "price": 500}, {"id": 2, "price": 800} ]
discount = float(request.query["discount"])
for each book in books: book["salePrice"] = book["price"] * (100 - discount) / 100
print(output_as_json(books))
圖 4
(四) 系統後台使用下列 Python 程式碼來實作 GET /api/books/{id},即查詢指定書籍的功能:
book_id = request.query["id"] sql = "SELECT title, price FROM Book WHERE id = " + book_id result = database.execute(sql)
- 請說明此程式碼在網頁安全上可能遭受的惡意攻擊類型與原因。(5 分)
- 請使用參數化查詢(Parameterized Query)修正上述程式碼,寫出修正後的完整程式片段,以有效防範該攻擊。(5 分)
登入後即可作答並保存紀錄。
核心觀念
本題考查:
- HTTP 的無狀態性。
- 登入狀態的保存機制。
- JSON 數值格式與折扣計算。
- SQL Injection 與參數化查詢。
解題方法
(一)HTTP 的無狀態性
HTTP 的無狀態性是指:伺服器處理每一個 HTTP 請求時,彼此獨立,不會自動記住前一次請求的使用者狀態。
因此,每次請求都必須自行攜帶必要資訊,例如驗證用的 Cookie、Session ID 或 Token。伺服器不會因為先前已處理過登入請求,就自動知道後續請求是哪一位使用者。
無狀態性不代表伺服器不能保存資料,而是指 HTTP 通訊協定本身不會自動維持請求之間的連續狀態。
(二)保存或傳遞登入狀態的機制
可採用 Cookie 搭配 Session 機制:
- 使用者登入成功後,伺服器建立 Session,並產生唯一的 Session ID。
- 伺服器將 Session ID 放入
Set-Cookie回應標頭,傳送給瀏覽器。 - 瀏覽器之後每次請求,自動將 Cookie 送回伺服器。
- 伺服器依 Session ID 找到對應的登入使用者資料,完成身分辨識。
此方式是在無狀態的 HTTP 上,額外建立登入狀態管理機制。
(三)JSON 輸出結果
請求為:
GET /api/books?discount=20
因此:
售價計算公式為:
第一本書:
第二本書:
題目指定所有數值均以一位小數輸出,因此 id 與 price 也須表示成浮點數格式。
(四)1. 網頁安全問題
此程式碼可能遭受 SQL Injection(SQL 注入攻擊)。
原因是程式直接將使用者輸入的 book_id 串接到 SQL 指令中:
sql = "SELECT title, price FROM Book WHERE id = " + book_id