建構函式

什麼是建構函式

建構函式是用來建立類別實體的特殊函式,通常用於初始化屬性。除預設建構函式外,所有建構函式名稱皆與類別名稱相同。

Dart 建構函式分類:

  1. 產生式建構函式(Generative)
  2. 預設建構函式(Default)
  3. 具名建構函式(Named)
  4. 常數建構函式(Const)
  5. 重新導向建構函式(Redirecting)
  6. 工廠建構函式(Factory)
  7. 重新導向工廠建構函式
  8. 簡潔建構語法(Dart 3.13+)
  9. 主建構函式(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

用於建立編譯期常數物件,需符合以下條件:

  1. 所有成員變數標註 final
  2. 建構函式加上 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,適用場景:

  1. 不一定新建物件(快取複用、回傳子類別實體)
  2. 建構前執行複雜邏輯(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=0new 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. 父類別建構函式與繼承規則

  1. 子類別不會繼承任何父類別建構函式
  2. 建構執行順序:初始化串列 → 父類別建構函式 → 子類別建構主體;
  3. 若父類別無無參數預設建構函式,子類別必須手動透過 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)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *