Dart 函數與方法
● 應該稱為「方法」還是「函數」?
在物件導向中,會將我們傳統所學的命令是編成得函數(
function
)稱之為方法(method
)而在 Dart 中既有方法也有函數,因為它既可以作為物件導向設計,也可以作為命令式設計… Dart 的方法就是放置在類內的函數(這時會稱之為方法),而放置在最頂層的函數就稱之為函數
// 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 在方法的表達上差異在哪…
A. 方法的位置:Dart 多了「函數」
Dart 與 Java 方法較大的差異在,Java 方法的方法就會存在類中 (class),而 Dart 中的方法可以單獨存在檔案中,這種方法稱為 頂層函數(top-level function
)
Java 方法如下:
public class Hello {
void sayHello() {
System.out.println("Hello~ Java method");
}
}
Dart 頂層函數如下:
void sayHello() {
print("Hello~ Dart method");
}
B. 參數的表達:
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. 不允許有預設參數,只要有一個條件不符合就無法通過編譯
void printMsg(String msg, int count = 1) {
for (int i = 0; i < count; i++) {
print("($i) $msg");
}
}
如下圖所示,不使用位置、命名參數的話,就不允許有預設值
● 可選「位置」參數 的使用方式:
可選位置參數是 使用中括號([]
)包裹參數,並且它的重點在於「位置」,它會限制使用者一定要按照方法參數的位置做設置,不能指定參數名去賦予值;
範例如下
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");
}
可選命名參數:預設參數
● 可選「命名」參數 的使用方式:
可選位置參數是 使用花括號({}
)包裹參數,它與位置參數不同,使用可選命名參數時並 不在意參數的順序,因為使用它時 需要我們手動為方法的每個參數指定數值;
範例如下
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");
}
● 使用命名參數時不允許沒有指定參數名稱
一級方法物件:Function、typedef 使用
● Dart 的一級方法物件特點如同 Java 的 Lambda 表達式,又如 groovy 的閉包概念,把方法作為參數傳入,在方法的內部可以依據需要隨時調用這個一級方法物件
● 在 Dart 中使用一級方法物件時常會接觸的兩個關鍵字如下表
使用關鍵字 | 解釋 |
---|---|
Function | Function 對於 Dart 來說是一種「類型」,我們所以的方法其實都是 Function 類型;但是 Function 本身不具有參數檢查功能 |
typedef | 重新命名(如同 Shell alias 的功能),配合 Function 使用時,可以在編譯期間進行 參數進行檢查 |
A. 使用 Function 類型:如下範例中,我們可以看到 printItem
、showMsg
兩個方法雖然參數不同,但是都屬於 Function 類型
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);
}
B. 使用 typedef
定義 Function 類型:以下我們來改進 receiveFunction
方法,讓其接收指定類型的 Function
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
類型(還有一些子類),還可以自定義拋出任何的類型,甚至可以拋出方法、類 (基本上就是一切皆可拋出)
● Java 則是混合型異常,既有檢查異常(規定呼叫者一定要處理),也有非檢查異常(嚴重錯誤類型),想了解更多 Java 異常的細節,請點擊 深入理解 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)
錯誤棧 可以使用
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);
}
並且這兩個參數類型皆為
dynamic
(可使用 runtimeType 去查看類型)
Dart 捕捉指定類型異常:on、再次拋出:rethrow
● 上面有說到 Dart 的 catch 只提供兩個參數 (並且這兩個參數皆為 dynamic 類型),那難道就不能捕捉指定類型的異常了嗎?
不,其實是可以的,但若 Dart 要捉取指定的類型就要使用 on
關鍵字(on <類型> catch(e, s)
),範例如下:
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 將捕捉的異常再次拋出(但這並不算是例外轉譯),範例如下…
A. 定義異常函數:
void say() {
print("說不出的帥");
}
void testThrowFunc() {
// 可以拋出方法
throw say;
}
B. 使用 on
來捕捉 Function 類型的異常,並在執行過後再次透過 rethrow
把相同的異常拋
void main() {
try {
testThrowFunc();
} on Function catch(e) {
print("e: $e");
print("e(): ${e()}");
print("e.runtimeType: ${e.runtimeType}");
rethrow; // 把捕獲的異常重新拋出
} finally {
print("- v -+");
}
}
Dart Assert 斷言
● 在 Dart 中,assert 是一個常用的調試工具,用於在開發階段驗證程式的假設,它的主要目的是在開發過程中檢查程式的內部狀態是否符合預期,從而幫助開發者及早發現和修正錯誤(assert
通常用於檢查函數參數是否符合預期,這在函數的開發和調試過程中特別有用)
assert
的特性如下:
A. 僅在調試模式下啟用:
assert 語句僅在調試模式(debug mode
)下執行,在生產環境(release mode
)中,所有的 assert 語句都會被忽略,不會執行
因此,不應該依賴 assert 來進行生產環境中的程式邏輯控制
B. 提升程式的可靠性:
通過在程式的關鍵位置添加 assert,可以及早發現潛在的錯誤,從而提高程式的可靠性和穩定性
● 範例如下,當 assert 內部的判斷為 false 時發生中斷,並拋出 AssertionError
void main() {
var a = 100;
assert(a == 99);
}
Dart 引用庫
在 Dart 中,透過 import 關鍵字和指定路徑,開發者可以匯入各種類型的函式庫,包括 Dart 標準函式庫、第三方函式庫以及專案內的自訂函式庫
根據函式庫的來源和用途,Dart 使用不同的路徑前綴來明確區分這些函式庫的類型
庫分類 | 關鍵字 | 範例 |
---|---|---|
Dart 語言(標準庫) | dart: | dart:io |
第三方庫(通常可以到 pub.dev 找) | package: | package:hello/world.dart |
指定庫別名:as
● 在 Dart 中,有時候你可能會遇到不同的函式庫中存在相同名稱的類別、函數或變數
為了避免命名衝突,並提高程式碼的可讀性和可維護性,Dart 提供了 as
關鍵字,用於為匯入的程式庫指定一個別名
關鍵字 | 解釋 |
---|---|
as | 庫另外命名 |
概念範例如下
// 舉例 (並沒有這個第三方庫) 假設內部都有 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();
}
庫部分引用:show / hide
● Dart 提供了靈活的方式來引用庫中的部分內容,以便在程式碼中只引入必要的部分;這不僅可以減少不必要的依賴,還可以提高程式碼的可讀性和效能
使用關鍵字如下表
關鍵字 | 解釋 |
---|---|
show | 部分引用 |
hide | 隱藏特定部分之外都引用 |
deferred as | '庫的懶加載' 並在合適的時候使用 loadLibrary() 方法 |
概念範例如下
// 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();
}
更多的 Flutter/Dart 語言相關文章
了解 Flutter 如何在跨平台開發中佔據重要地位,掌握快速上手的技巧與項目建置流程,開啟你的跨平台開發之旅!
探索跨平台與 Flutter 技術的未來:從認識到 Flutter 專案建置 | 3 種跨平台
Dart 語言基礎
● 探討 Dart 語言:宣告、數據類型、操作符 | 從基礎到應用指南
快速掌握 Dart 語言的核心概念,包括變數宣告、數據類型及操作符,為 Flutter 開發奠定扎實基礎。
● Dart 函數與方法、異常處理、引用庫 | Java 比較
深入了解 Dart 的函數與異常處理特性,並與 Java 的處理方式進行比較,幫助你跨語言切換更加順暢。
● 深入解析 Dart 語言:命名慣例、類特性、建構函數與抽象特性
學習 Dart 類的設計邏輯及命名慣例,深入探索抽象類與 Mixin 的強大應用場景。
● 深入探索 Dart 的併發與異步處理:從 Isolate 到 Event Loop 的全面指南 | Future、Stream
徹底搞懂 Dart 的併發與異步處理,掌握 Isolate 與 Event Loop 的運行機制,助你提升應用效能!
深入 Flutter 框架
● 深入解析 Flutter Navigator:常見錯誤、解決方法與路由跳轉技巧、動畫
從常見問題到自定義解決方案,學會如何利用 Navigator 實現路由跳轉與流暢動畫效果。
● 深入理解 Flutter 中的數據共享:從普遍方案到 InheritedWidget | 3 種方案
探討數據共享的最佳實踐,了解 InheritedWidget 等三種主要方案,幫助你優化應用結構。
● 深入解析 Flutter 三顆樹:Widget、Element 與 RenderObject 完整指南
拆解 Flutter 的內部結構,全面了解 Widget、Element 和 RenderObject 之間的關係,提升你的 Flutter 開發技能!