---
title: 'Dart 異常處理、函數方法、引用庫'
disqus: kyleAlien
---
Dart 異常處理、函數方法、引用庫
===
## Overview of Content
如有引用參考請詳註出處,感謝 :cat:
:::success
* 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
本篇文章對應的是 [**Dart 函數與方法、異常處理、引用庫 | Java 比較**](https://devtechascendancy.com/dart-java-compare_exception_function-methods/)
:::
:::info
以下使用 Dart SDK `3.4.3` 版本
:::
[TOC]
## Dart 函數與方法
:::info
* **應該稱為「方法」還是「函數」**?
在物件導向中,會將我們傳統所學的命令是編成得函數(`function`)稱之為方法(`method`)
而在 Dart 中既有方法也有函數,因為它既可以作為物件導向設計,也可以作為命令式設計… Dart 的方法就是放置在類內的函數(這時會稱之為方法),而放置在最頂層的函數就稱之為函數
```java=
// Dart 類
public class Hello {
// 宣告在 Dart 類內,稱之為「方法」
void sayHello() {
System.out.println("Hello~ Java method");
}
}
// 宣告在 Dart 頂層,稱之為「函數」
void sayHello() {
print("Hello~ Dart method")
}
```
:::
### Dart、Java 方法的差異
* 以下我們以 Java 這門語言的方法(`Method`)來與 Dart 這門語言的方法相比,看看 Dart 跟 Java 在方法的表達上差異在哪…
1. **方法的位置**:Dart 多了「**函數**」
Dart 與 Java 方法較大的差異在,**Java 方法的方法就會存在類中 (class),而 Dart 中的方法可以單獨存在檔案中**,這種方法稱為 **頂層函數**(`top-level function`)
Java 方法如下:
```java=
// Java
public class Hello {
void sayHello() {
System.out.println("Hello~ Java method");
}
}
```
Dart 頂層函數如下:
```dart=
void sayHello() {
print("Hello~ Dart method");
}
```
2. **參數的表達**:
Java 方法重載機制,大多數時候是因為有多種不同的參數所需,所以需要多個相同名稱的方法(但參數需不同),範例如下… 我們可以看到,這這對於生產程式的效率上並不佳
```java=
class Hello {
void sayHello() {
System.out.println("Hello~ Java method");
}
// 為了參數方法重載
void sayHello(String personName) {
System.out.println("Hello~ Java method, " + personName);
}
// 為了參數方法重載
void sayHello(String personName, int count) {
for (int i = 0; i < count; i++) {
System.out.println("Hello~ Java method, " + personName);
}
}
}
```
而在 Dart 中則是以 **==可選位置參數==、==命名參數==來處理**,並且 Dart 的方法 **允許參數有默認值**
> Dart 的可選位置參數、命名參數會在接下來小節會舉例說明
另外在 **Dart 中並沒有方法重載機制**,下圖中證明 Dart 不支援方法重載
> 
### 可選位置參數:預設參數
* 在一般的方法參數設置下,呼叫方法時,方法的 ^1.^ 參數都是必須設置,並且 ^2.^ 不允許有預設參數,只要有一個條件不符合就無法通過編譯
```dart=
void printMsg(String msg, int count = 1) {
for (int i = 0; i < count; i++) {
print("($i) $msg");
}
}
```
如下圖所示,不使用位置、命名參數的話,就不允許有預設值
> 
* **可選「位置」參數** 的使用方式:
可選位置參數是 **使用中括號(`[]`)包裹參數**,並且它的重點在於「==**位置**==」,它會限制使用者一定要按照方法參數的位置做設置,不能指定參數名去賦予值;
範例如下
```java=
void testPosition() {
// 全部參數使用預設值
printInfo();
printInfo("Alien", 998877);
printInfo("Alien", 1111, 998877);
// printInfo(123); // 順序不對,前面必須要指定 name
// printInfo(id : 123); // 無法指定
}
void printInfo([String name = "Hello", // 有預設參數
int id = 9527, // 有預設參數
int? phone]) {
print("name: $name, id: $id, phone: $phone");
}
```
> 
### 可選命名參數 - 預設參數
* **可選「命名」參數** 的使用方式:
可選位置參數是 **使用花括號(`{}`)包裹參數**,它與位置參數不同,使用可選命名參數時並 **不在意參數的順序**,因為使用它時 **需要我們手動為方法的每個參數指定數值**;
範例如下
```dart=
void testSetName() {
// 全部參數使用預設值
printInfo_2();
// 按照順序命名(okay)
printInfo_2(name: "Alien", id: 123, phone: 666666);
// 不按照順序命名(okay)
printInfo_2(phone: 9999, id: 777, name: "Kyle");
}
void printInfo_2({String name = "World", int id = 9527, int? phone}) {
print("name: $name, id: $id, phone: $phone");
}
```
> 
:::warning
* 使用命名參數時不允許沒有指定參數名稱
> 
:::
### 一級方法物件:Function、typedef 使用
* **Dart 的一級方法物件特點如同 [Java 的 Lambda 表達式](https://devtechascendancy.com/java-functional-lambda-methods/),又如 groovy 的閉包概念,==把方法作為參數傳入==,在方法的內部可以依據需要隨時調用這個一級方法物件**
* 在 Dart 中使用一級方法物件時常會接觸的兩個關鍵字如下表
| 使用關鍵字 | 解釋 |
| - | - |
| Function | **`Function` 對於 Dart 來說是一種「類型」**,我們所以的方法其實都是 `Function` 類型;但是 `Function` 本身不具有參數檢查功能 |
| typedef | 重新命名(如同 Shell `alias` 的功能),**配合 `Function` 使用時,可以在編譯期間進行 ==參數進行檢查==** |
1. 使用 Function 類型:如下範例中,我們可以看到 `printItem`、`showMsg` 兩個方法雖然參數不同,但是都屬於 Function 類型
```dart=
void printItem(String item) {
print("myFunction be call, element $item");
}
void showMsg(String msg, int count) {
for (int i = 0; i < count; i++) {
print("($i), msg: $msg");
}
}
// 接收 Function 類型
void receiveFunction(Function function) {
print("Function runtimeType: ${function.runtimeType}");
}
void main() {
receiveFunction(printItem);
receiveFunction(showMsg);
}
```
> 
2. 使用 `typedef` 定義 Function 類型:以下我們來改進 `receiveFunction` 方法,讓其接收指定類型的 Function
```dart=
void printItem(String item) {
print("myFunction be call, element $item");
}
void showMsg(String msg, int count) {
for (int i = 0; i < count; i++) {
print("($i), msg: $msg");
}
}
// 定義 Function 類型的細節
typedef ShowItemFunc = void Function(String);
// 接收指定 Function 類型
void receiveFunction(ShowItemFunc function) {
print("Function runtimeType: ${function.runtimeType}");
}
void main() {
receiveFunction(printItem);
// Error! 因為並不是所需類型的 Function
receiveFunction(showMsg);
}
```
> 
## Dart 異常
Dart 異常與 Java 不同,Dart 屬於 **非檢查異常**:^1^ 方法不必聲明他們所拋出的異常,也就是呼叫方法時 ^2^ 不必捕捉異常
另外,Dart 提供了 `Exception`、`Error` 類型(還有一些子類),還可以自定義拋出任何的類型,**甚至可以拋出方法、類 (基本上就是一切皆可拋出)**
:::info
* Java 則是混合型異常,既有檢查異常(規定呼叫者一定要處理),也有非檢查異常(嚴重錯誤類型),想了解更多 Java 異常的細節,請點擊 [**深入理解 Java 異常處理:從基礎概念到最佳實踐指南**](https://devtechascendancy.com/java-jvm-exception-handling-guide/)
```java=
class JavaException {
void nonCheckThrow() {
throw new RuntimeException();
}
void checkThrow() throws IOException {
throw new IOException();
}
}
```
:::
### Dart 異常處理:try-catch-finally、不同的 catch
* 雖然 Dart 可以不捕捉異常,但是它也同樣支持 `try / catch / finally` 操作
**Dart 與 Java 捕捉異常的不同之處在於 ==`catch`==,Dart 的 `catch` 只有兩個預設參數 ^1.^`e(error)` 拋出的異常物件、^2.^`s(stack)` 錯誤棧 可以使用**
> ==並且這兩個參數類型皆為 `dynamic`== (可使用 runtimeType 去查看類型)
```java=
void main() {
try {
testException("帥到被捕捉");
} catch(e, s) { // 無法定義類型的參數
print(e);
print("runtimeType: ${e.runtimeType}\n");
print(s);
print("runtimeType: ${s.runtimeType}");
} finally {
print("被捉住哭哭");
}
}
void testException(String str) {
throw new Exception(str);
}
```
> 
### 捕捉指定類型異常:on、再次拋出:rethrow
* 上面有說到 Dart 的 catch 只提供兩個參數 (並且這兩個參數皆為 dynamic 類型),那難道就不能捕捉指定類型的異常了嗎?
不,其實是可以的,但若 Dart 要捉取指定的類型就要使用 `on` 關鍵字(`on <類型> catch(e, s)`),範例如下:
```dart=
void tryThrow() {
throw UnsupportedError('This method not implement');
}
void main() {
try {
tryThrow();
} on UnsupportedError catch (e) {
print("Use `on` to catch unsupported error: ${e.message}");
}
}
```
> 
* 另外 Dart 也可以使用 **rethrow 將捕捉的異常再次拋出**(但這並不算是例外轉譯),範例如下…
1. 定義異常函數:
```dart=
void say() {
print("說不出的帥");
}
void testThrowFunc() {
// 可以拋出方法
throw say;
}
```
2. 使用 `on` 來捕捉 Function 類型的異常,並在執行過後再次透過 `rethrow` 把相同的異常拋
```dart=
void main() {
try {
testThrowFunc();
} on Function catch(e) {
print("e: $e");
print("e(): ${e()}");
print("e.runtimeType: ${e.runtimeType}");
rethrow; // 把捕獲的異常重新拋出
} finally {
print("- v -+");
}
}
```
> 
### Assert 斷言
* 在 Dart 中,assert 是一個常用的調試工具,用於在開發階段驗證程式的假設,它的主要目的是在開發過程中檢查程式的內部狀態是否符合預期,從而幫助開發者及早發現和修正錯誤(`assert` 通常用於檢查函數參數是否符合預期,這在函數的開發和調試過程中特別有用)
`assert` 的特性如下:
1. **僅在調試模式下啟用**:
assert 語句僅在調試模式(`debug mode`)下執行,在生產環境(`release mode`)中,所有的 assert 語句都會被忽略,不會執行
> 因此,不應該依賴 assert 來進行生產環境中的程式邏輯控制
2. **提升程式的可靠性**:
通過在程式的關鍵位置添加 assert,可以及早發現潛在的錯誤,從而提高程式的可靠性和穩定性
* 範例如下,**當 assert 內部的判斷為 false 時發生中斷**,並拋出 AssertionError
```java=
void main() {
var a = 100;
assert(a == 99);
}
```
> 
## Dart 引用庫
在 Dart 中,透過 import 關鍵字和指定路徑,開發者可以匯入各種類型的函式庫,包括 Dart 標準函式庫、第三方函式庫以及專案內的自訂函式庫
根據函式庫的來源和用途,Dart 使用不同的路徑前綴來明確區分這些函式庫的類型
| 庫分類 | 關鍵字 | 範例 |
| - | - | - |
| Dart 語言(標準庫) | dart: | dart:io |
| 第三方庫(通常可以到 [pub.dev](https://pub.dev/) 找) | package: | package:hello/world.dart |
### 指定庫別名:as
* 在 Dart 中,有時候你可能會遇到不同的函式庫中存在相同名稱的類別、函數或變數
為了避免命名衝突,並提高程式碼的可讀性和可維護性,Dart 提供了 `as` 關鍵字,用於為匯入的程式庫指定一個別名
| 關鍵字 | 解釋 |
| - | - |
| as | 庫另外命名 |
概念範例如下
```dart=
// 舉例 (並沒有這個第三方庫) 假設內部都有 Json class
import 'package:lib1/json1.dart';
import 'package:lib2/json2.dart' as Json2;
void main() {
// lib 1 中的 Json
Json j = new Json();
// lib 2 中的 Json
Json2.Json j2 = new Json2.Json();
}
```
### 庫部分引用
* Dart 提供了靈活的方式來引用庫中的部分內容,以便在程式碼中只引入必要的部分;這不僅可以減少不必要的依賴,還可以提高程式碼的可讀性和效能
使用關鍵字如下表
| 關鍵字 | 解釋 |
| - | - |
| show | 部分引用 |
| hide | 隱藏特定部分之外都引用|
| deferred as | '**庫的懶加載**' 並在合適的時候使用 `loadLibrary()` 方法 |
概念範例如下
```dart=
// lib1 指使用 Json class
import 'package:lib1/json1.dart' show Json;
// lib2 除了 Json class 之後都使用
import 'package:lib2/json2.dart' hide Json;
// 改名為 T
import 'package:xxx/ui/fragment/transfer/Transfer.dart' as T;'
void main() {
T.Transfer(); // 建構函數上就必須加上 T
popCalendarDialog();
}
import 'package:xxx/utils/Calendar.dart' deferred as Calendar;'
void popCalendarDialog() async {
await Calendar.loadLibrary(); // 開始加載庫
Calendar.open();
}
```
## Appendix & FAQ
:::info
:::
###### tags: `Flutter`、`Dart`