拡張メソッドを利用すると、既存の型やサードパーティライブラリの型に対して機能を追加できます。元のクラスのソースコードを変更する必要はありません。IDEのコード補完には、ネイティブメソッドと並んで拡張メソッドが候補表示されます。
第三者が公開したAPIのソースは変更できないが、便利なメソッドを追加したい場合に活用します。
要求例:int.parse('42') の代わりに '42'.parseInt() を記述できるようにする。
構文形式
extension 拡張名? on 対象型 {
// メソッド、getter、setter、演算子、静的メンバー
}Code language: JavaScript (javascript)
サンプル
// コードを1ファイルにまとめて記述。分割不要、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) と 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名競合の解決策
複数の拡張に同名のメンバーが存在する場合、3つの対応方法があります。
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(ドット短縮記法)