什麼是建構函式
建構函式是用來建立類別實體的特殊函式,通常用於初始化屬性。除預設建構函式外,所有建構函式名稱皆與類別名稱相同。
Dart 建構函式分類:
- 產生式建構函式(Generative)
- 預設建構函式(Default)
- 具名建構函式(Named)
- 常數建構函式(Const)
- 重新導向建構函式(Redirecting)
- 工廠建構函式(Factory)
- 重新導向工廠建構函式
- 簡潔建構語法(Dart 3.13+)
- 主建構函式(Primary Constructors,精簡欄位 + 建構寫法)
1. 產生式建構函式 Generative Constructor
最基礎的建構函式,負責建立實體、初始化成員變數,支援初始化形式參數 this.變數 簡化指派流程。
class Point {
double x;
double y;
// 產生式建構函式,this.x 直接接收參數指派給成員
Point(this.x, this.y); // 直接為 x、y 賦值
}
void main() {
final p = Point(10, 20);
print("(${p.x}, ${p.y})"); // (10, 20)
}
Code language: JavaScript (javascript)
2. 預設建構函式 Default Constructor
若類別未手動定義任何建構函式,Dart 會自動提供一個無參數、無名稱的產生式建構函式。此規則與 C++、Java、C# 等其他 C 語系語言一致。
class PointA {
double x = 0;
double y = 0;
// 隱藏式預設建構函式:PointA();
}
void main() {
final p = PointA(); // 自動呼叫預設建構函式
print("(${p.x}, ${p.y})"); // (0.0, 0.0)
}
Code language: PHP (php)
注意:只要手動撰寫任一建構函式,預設建構函式就會消失。
3. 具名建構函式 Named Constructor
用途:單一類別定義多個建構函式,語義清晰;語法格式 類別名稱.建構函式名稱()
const double xOrigin = 0;
const double yOrigin = 0;
class Point {
final double x;
final double y;
// 主要產生式建構函式
Point(this.x, this.y);
// 具名建構函式:原點
Point.origin() : x = xOrigin, y = yOrigin;
}
void main() {
final p1 = Point(5, 6);
final p2 = Point.origin(); // 呼叫具名建構函式
print(p1.x); //5
print("原點:(${p2.x}, ${p2.y})"); // (0,0)
}
Code language: PHP (php)
重要規則
- 子類別不會繼承父類別的具名建構函式;子類若需要同名具名建構函式,必須手動實作;
- 具名建構函式支援初始化串列指派 final 變數。
4. 常數建構函式 Const Constructor
用於建立編譯期常數物件,需符合以下條件:
- 所有成員變數標註
final - 建構函式加上
const;呼叫時使用const可複用常數快取,若省略 const 會建立一般新實體。
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0); // 靜態類別共用,全部類別共享同一物件
final double x, y;
// 常數建構函式可初始化 final 修飾的 x、y
const ImmutablePoint(this.x, this.y);
}
void main() {
// 編譯期常數,複用同一物件
const p1 = ImmutablePoint(1, 2);
const p2 = ImmutablePoint(1, 2); // 傳入參數完全相同才會共用實體,參數不同則為獨立物件
print(p1 == p2); // true
// 一般實體,全新獨立物件
final p3 = ImmutablePoint(1, 2);
print(p1 == p3); // false
}Code language: PHP (php)
5. 重新導向建構函式 Redirecting Constructor
在建構函式內轉呼叫同類別另一個建構函式,使用 : this(參數),函式主體必須空白。
class Point {
double x, y;
// 主要建構函式
Point(this.x, this.y);
// 重新導向建構函式:僅傳入 x,y 固定為 0,轉呼叫主建構函式
Point.alongX(double x) : this(x, 0); // 呼叫具名建構函式後,將 x 傳給主建構函式並固定 y=0
}
void main() {
final p = Point.alongX(9);
print("x=${p.x}, y=${p.y}"); // x=9, y=0
}
Code language: JavaScript (javascript)
6. 工廠建構函式 Factory Constructor
關鍵字 factory,適用場景:
- 不一定新建物件(快取複用、回傳子類別實體)
- 建構前執行複雜邏輯(JSON 解析、參數驗證);限制:無法存取
this
快取單例 Logger 範例
class Logger {
final String name;
bool mute = false;
// 私有快取,僅套件內可存取
static final Map<String, Logger> _cache = {};
// 工廠建構函式:優先從快取取出
factory Logger(String name) {
return _cache.putIfAbsent(name, () => Logger._internal(name));
}
// 工廠建構函式:從 JSON 解析建立
factory Logger.fromJson(Map<String, dynamic> json) {
return Logger(json["name"].toString());
}
// 私有內部產生式建構函式
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
void main() {
final log1 = Logger("UI");
final log2 = Logger("UI");
print(log1 == log2); // true 同一快取物件
final jsonLog = Logger.fromJson({"name": "Net"});
jsonLog.log("request success");
}
Code language: JavaScript (javascript)
7. 重新導向工廠建構函式 Redirecting Factory
語法 factory 名稱() = 其他類別.建構函式;,適用於抽象類別代理實體建立,省去重複定義參數。
abstract class Listenable {}
class _MergingListenable implements Listenable {
List<_MergingListenable> list;
_MergingListenable(this.list);
}
// 重新導向工廠,呼叫內部私有類別建構函式
factory Listenable.merge(List<Listenable> list) = _MergingListenable;
Code language: PHP (php)
8. 建構函式拆分(Constructor Tear-offs)
直接將建構函式做為函式參數傳入,省略 (),取代多餘 Lambda 匿名函式,執行效能更佳。
推薦寫法
void main() {
final charCodes = [65, 66, 67];
// 具名建構函式 String.fromCharCode
final strs = charCodes.map(String.fromCharCode);
print(strs); // (A, B, C)
// 無名建構函式拆分搭配 .new
final buffers = charCodes.map(StringBuffer.new);
}
Code language: PHP (php)
不推薦:多餘 Lambda
// 多餘包裝,不建議使用
charCodes.map((code) => String.fromCharCode(code));
Code language: JavaScript (javascript)
9. Dart 3.13+ 簡潔建構語法 Concise Constructor
類別內可省略類別名稱,直接使用 new / factory 定義建構函式,不用寫 類別名稱.xxx:
| 傳統寫法 | 簡寫 |
|---|---|
Point(this.x,this.y) | new(this.x,this.y) |
Point.origin():x=0,y=0 | new origin():x=0,y=0 |
factory Point.clone() | factory clone() |
class Point {
double x, y;
// 簡寫無參產生式建構函式
new(this.x, this.y);
// 簡寫具名產生式建構函式
new origin() : x = 0, y = 0;
// 簡寫工廠建構函式
factory clone(Point other) => new(other.x, other.y);
}
void main() {
final p = new(3, 4);
final origin = new origin();
final copy = p.clone(p);
}
Code language: PHP (php)
10. 實體變數三種初始化方式
1:宣告時直接賦值
class PointA {
double x = 1.0;
double y = 2.0;
}
Code language: JavaScript (javascript)
2:初始化形式參數
this.變數
支援選擇性參數、具名參數、可空變數
class PointB {
final double x;
final double y;
// 位置選擇參數
PointB.opt([this.x = 0, this.y = 0]);
// 具名參數附預設值
PointB.named({this.x = 5, this.y = 5});
}
Code language: JavaScript (javascript)
3:初始化串列 Initializer List
冒號 : 後進行賦值,執行順序早於建構函式主體,不可存取 this,支援 assert 參數驗證
class Point {
final double x;
final double y;
Point.fromJson(Map<String, double> json)
: x = json["x"]!,
y = json["y"]! {
print("建構主體執行,x=$x");
}
// 附斷言驗證
Point.withAssert(this.x, this.y) : assert(x >= 0);
}
Code language: JavaScript (javascript)
11. 私有欄位直接初始化參數(Dart3.12+)
底線開頭的私有變數,建構參數寫 this._欄位,外部呼叫直接使用無底線公開名稱。
class Point {
final double _x;
final double _y;
// 內部使用 _x,外部呼叫寫 x:10
Point({required this._x, this._y = 0});
}
void main() {
final p = Point(x: 10); // 不用寫 _x,直接 x
print(p._x); // 10
}
Code language: PHP (php)
初始化串列內使用私有變數原名:
class Point {
final double _x;
Point({required this._x}) : assert(_x > 0);
}
Code language: JavaScript (javascript)
12. 父類別建構函式與繼承規則
- 子類別不會繼承任何父類別建構函式;
- 建構執行順序:初始化串列 → 父類別建構函式 → 子類別建構主體;
- 若父類別無無參數預設建構函式,子類別必須手動透過
super()指定父類建構函式。
基礎 super 呼叫範例
與 Java 相同,使用 super 代表基底類別;C# 則使用 base。
class Person {
final String name;
Person(this.name);
Person.fromMap(Map map) : name = map["name"];
}
class Employee extends Person {
int id;
// 呼叫父類別具名建構函式
Employee(Map data) : super.fromMap(data), id = data["id"];
}
Code language: JavaScript (javascript)
注意:super 的參數運算式內不能使用
this。
Super 參數(Dart2.17+,簡化傳遞參數給父類)
使用 super.變數 直接轉送參數給父類建構函式,不用手動撰寫 super(x,y)
class Vector2d {
final double x, y;
Vector2d(this.x, this.y);
}
class Vector3d extends Vector2d {
final double z;
// super.x super.y 自動轉送父類建構函式
Vector3d(super.x, super.y, this.z);
}
// 父類具名建構函式 + super 具名參數
class Vector3dYZ extends Vector2d {
final double z;
Vector3dYZ({required super.y, required this.z}) : super.named(x: 0);
}
Code language: JavaScript (javascript)
Previous: 類別 class