扩展方法可以为已有类型、第三方库类型新增功能,无需修改原有类源码。 IDE 代码补全会把扩展方法和原生方法一同提示。
你无法修改别人提供的 API,但希望增加便捷方法。
示例需求:希望 '42'.parseInt() 代替 int.parse('42')。
语法结构
extension 扩展名称? on 目标类型 {
// 方法、getter、setter、运算符、静态成员
}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 {
// getter
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)
Previous: Dot shorthands(点简写)
Next: Extension Types