擴充方法能夠為現有型別、第三方函式庫型別新增功能,不必修改原始類別的程式碼。IDE 的程式碼自動補齊功能,會將擴充方法與原生方法一同列出提示。
你無法修改他人提供的 API,卻想額外新增方便使用的輔助方法。
需求範例:希望用 '42'.parseInt() 取代 int.parse('42')。
語法架構
extension 擴充名稱? on 目標型別 {
// 方法、取值器、設值器、運算子、靜態成員
}Code language: JavaScript (javascript)
範例
// 全部撰寫在同一支檔案,不需要拆分,main 可直接執行
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
double parseDouble() {
return double.parse(this);
}
}
void main() {
print('42'.parseInt()); // 42
print('3.14'.parseDouble()); // 3.14
print('100'.padLeft(6)); // 和原生 String 方法對照
}Code language: Dart (dart)
執行輸出
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
42
3.14
100Code language: CSS (css)
擴充名稱為選填;this 代表呼叫此擴充功能的實體物件。
擴充功能支援的成員類型
擴充內可以定義:實體方法、取值器(getter)、設值器(setter)、運算子、靜態成員。
extension StringExt on String {
// 取值器
bool get isEmptyOrBlank => trim().isEmpty;
// 實體方法
String repeat(int count) => List.filled(count, this).join();
// 運算子多載
String operator *(int count) => repeat(count);
// 靜態成員(需透過擴充名稱存取)
static String emptyStr = '';
static void printTip() => print("字串擴充靜態方法");
}
void main() {
var str = " dart ";
print(str.isEmptyOrBlank);
print("hi" * 3);
print("hello".repeat(2));
// 存取靜態成員
print(StringExt.emptyStr);
StringExt.printTip();
}Code language: PHP (php)
執行結果
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
false
hihihi
hellohello
字串擴充靜態方法Code language: CSS (css)
未命名擴充(匿名擴充)
不填寫擴充名稱時,僅能在目前程式庫內存取,無法透過顯式呼叫解決名稱衝突。
無法在外部使用 擴充名稱(實體) 語法呼叫。
extension on String {
bool get isBlank => trim().isEmpty;
}
void main() {
print(" ".isBlank); // true
}Code language: Dart (dart)
匿名擴充裡的靜態成員,僅能在擴充內部使用。
泛型擴充(Generic extensions)
可以針對泛型型別建立擴充,例如 List<T>
extension MyFancyList<T> on List<T> {
int get doubleLength => length * 2;
List<T> operator -() => reversed.toList();
List<List<T>> split(int at) => [sublist(0, at), sublist(at)];
}
void main() {
List<int> list = [1, 2, 3, 4];
print(list.doubleLength); // 8
print(-list); // [4,3,2,1]
print(list.split(2)); // [[1,2],[3,4]]
}Code language: Dart (dart)
執行結果
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
8
[4, 3, 2, 1]
[[1, 2], [3, 4]]Code language: CSS (css)
static 靜態型別 vs dynamic
dynamic 變數無法呼叫擴充方法
擴充方法屬於靜態解析,執行階段才決定型別的 dynamic 變數找不到對應擴充:
void main() {
dynamic d = '2';
// print(d.parseInt()); // 執行階段發生 NoSuchMethodError
var v = '2';
print(v.parseInt()); // 型別推斷為 String,可以正常執行
}
extension NumberParsing on String {
int parseInt() => int.parse(this);
}Code language: JavaScript (javascript)
原因:擴充方法在編譯時依照變數的靜態型別繫結,不是執行時動態查詢。
API 名稱衝突解決方案
多個擴充存在同名成員時,有三種處理方式。
1:匯入時透過 show / hide 隱藏衝突的擴充
// string_apis.dart 內含 NumberParsing
// string_apis_2.dart 內含 NumberParsing2,同樣定義 parseInt
import 'string_apis.dart';
import 'string_apis_2.dart' hide NumberParsing2;
void main() {
print('42'.parseInt()); // 使用 string_apis.dart 的實作
}Code language: Dart (dart)
2:顯式呼叫擴充(包裝語法)
import 'string_apis.dart'; // NumberParsing
import 'string_apis_2.dart'; // NumberParsing2
void main() {
// print('42'.parseInt()); // 產生衝突,編譯錯誤
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());
}Code language: Dart (dart)
3:匯入程式庫時使用前綴
兩個擴充名稱完全相同時,利用前綴區分:
import 'string_apis.dart';
import 'string_apis_3.dart' as rad;
void main() {
print(NumberParsing('42').parseInt());
print(rad.NumberParsing('42').parseInt());
// 沒有衝突的擴充方法依舊可以隱式呼叫
print('42'.parseNum());
}Code language: Dart (dart)
擴充方法(Extension methods)
Previous: Dot shorthands(點簡寫)