Constructor

What is a Constructor

A constructor is a special function used to create class instances, typically for initializing properties. Except for the default constructor, all constructors share the same name as their class.

Dart Constructor Types:

  1. Generative Constructors
  2. Default Constructors
  3. Named Constructors
  4. Const Constructors
  5. Redirecting Constructors
  6. Factory Constructors
  7. Redirecting Factory Constructors
  8. Concise Constructor Syntax (Dart 3.13+)
  9. Primary Constructors (Minimal field + constructor syntax)

1. Generative Constructor

The most basic constructor, which creates instances and initializes member variables. It supports initializer formal parameters this.variable to simplify assignment logic.

class Point {
  double x;
  double y;

  // Generative constructor: this.x directly assigns values to class members
  Point(this.x, this.y); // Directly assign values to x and y
}

void main() {
  final p = Point(10, 20);
  print("(${p.x}, ${p.y})"); // (10, 20)
}
Code language: JavaScript (javascript)

2. Default Constructor

If no custom constructor is manually defined for a class, Dart automatically provides a parameterless, unnamed generative constructor. This behavior is identical to other C-like languages including C++, Java, C#, etc.

class PointA {
  double x = 0;
  double y = 0;
  // Implicit default constructor: PointA();
}

void main() {
  final p = PointA(); // Automatically calls the default constructor
  print("(${p.x}, ${p.y})"); // (0.0, 0.0)
}
Code language: PHP (php)

Note: The default constructor disappears as soon as you write any custom constructor manually.

3. Named Constructor

Purpose: Define multiple constructors within one class with clear semantic meanings. Syntax format: ClassName.ConstructorName()

const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  final double x;
  final double y;

  // Primary generative constructor
  Point(this.x, this.y);

  // Named constructor: origin point
  Point.origin() : x = xOrigin, y = yOrigin;
}

void main() {
  final p1 = Point(5, 6);
  final p2 = Point.origin(); // Call named constructor
  print(p1.x); //5
  print("Origin: (${p2.x}, ${p2.y})"); // (0,0)
}
Code language: PHP (php)

Key Rules

  • Subclasses do not inherit parent named constructors; you must manually implement matching named constructors in subclasses if needed;
  • Named constructors support initializer lists to assign values to final fields.

4. Const Constructor

Used to create compile-time constant objects, with the following requirements:

  1. All member variables must be marked final
  2. Prefix the constructor with const. Use const at instantiation to reuse cached constant instances; omitting const creates a regular separate object.
class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0); // Static class-wide shared singleton
  final double x, y;

  // Const constructor initializes final x and y
  const ImmutablePoint(this.x, this.y);
}

void main() {
  // Compile-time constant, reuses the same object reference
  const p1 = ImmutablePoint(1, 2);
  const p2 = ImmutablePoint(1, 2); // Identical parameters share the same instance
  print(p1 == p2); // true

  // Regular instance, brand-new separate object
  final p3 = ImmutablePoint(1, 2);
  print(p1 == p3); // false
}Code language: PHP (php)

5. Redirecting Constructor

A constructor that forwards execution to another constructor of the same class using : this(arguments). The constructor body must be empty.

class Point {
  double x, y;

  // Primary constructor
  Point(this.x, this.y);

  // Redirecting constructor: only pass x, fix y to 0 and forward to main constructor
  Point.alongX(double x) : this(x, 0); // Invoke main constructor with fixed 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

Marked with the factory keyword, applicable scenarios:

  1. Not guaranteed to create new objects (cache reuse, return subclass instances)
  2. Run complex pre-initialization logic (JSON parsing, parameter validation). Restriction: cannot access this
Singleton Cache Logger Example
class Logger {
  final String name;
  bool mute = false;
  // Private cache library-scoped
  static final Map<String, Logger> _cache = {};

  // Factory constructor: retrieve from cache first
  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  // Factory constructor: build instance from JSON
  factory Logger.fromJson(Map<String, dynamic> json) {
    return Logger(json["name"].toString());
  }

  // Private internal generative constructor
  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, same cached object

  final jsonLog = Logger.fromJson({"name": "Net"});
  jsonLog.log("request success");
}
Code language: PHP (php)

7. Redirecting Factory Constructor

Syntax: factory Name() = OtherClass.Constructor;. Ideal for abstract classes delegating instance creation to avoid repetitive parameter definitions.

abstract class Listenable {}

class _MergingListenable implements Listenable {
  List<_MergingListenable> list;
  _MergingListenable(this.list);
}

// Redirect factory to internal private class constructor
factory Listenable.merge(List<Listenable> list) = _MergingListenable;
Code language: PHP (php)

8. Constructor Tear-offs

Pass constructors directly as function arguments without parentheses, replacing redundant lambda anonymous functions for better performance.

Recommended Style
void main() {
  final charCodes = [65, 66, 67];
  // Named constructor String.fromCharCode
  final strs = charCodes.map(String.fromCharCode);
  print(strs); // (A, B, C)

  // Unnamed constructor tear-off via .new
  final buffers = charCodes.map(StringBuffer.new);
}
Code language: PHP (php)
Discouraged Redundant Lambda
// Unnecessary wrapping, avoid this pattern
charCodes.map((code) => String.fromCharCode(code));
Code language: JavaScript (javascript)

9. Concise Constructor Syntax (Dart 3.13+)

Omit the class name inside the class body; use new / factory directly to define constructors without ClassName.xxx:

Traditional SyntaxShortened Syntax
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;

  // Shortened unnamed generative constructor
  new(this.x, this.y);

  // Shortened named generative constructor
  new origin() : x = 0, y = 0;

  // Shortened factory constructor
  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. Three Ways to Initialize Instance Variables

1: Direct assignment at declaration
class PointA {
  double x = 1.0;
  double y = 2.0;
}
Code language: JavaScript (javascript)

2: Initializer Formal Parameters

this.variable

Supports optional positional parameters, named parameters, and nullable fields

class PointB {
  final double x;
  final double y;

  // Optional positional parameters
  PointB.opt([this.x = 0, this.y = 0]);

  // Named parameters with default values
  PointB.named({this.x = 5, this.y = 5});
}
Code language: PHP (php)

3: Initializer List

Assign values after colon :. Executes before the constructor body. Cannot reference this. Supports assert for parameter validation.

class Point {
  final double x;
  final double y;

  Point.fromJson(Map<String, double> json)
      : x = json["x"]!,
        y = json["y"]! {
    print("Constructor body executed, x=$x");
  }

  // With assertion validation
  Point.withAssert(this.x, this.y) : assert(x >= 0);
}
Code language: PHP (php)

11. Direct Private Field Initializer Parameters (Dart 3.12+)

For underscore-prefixed private fields, write constructor parameters as this._field. External callers use the public name without underscore.

class Point {
  final double _x;
  final double _y;

  // Internal storage _x, external invocation uses x:10
  Point({required this._x, this._y = 0});
}

void main() {
  final p = Point(x: 10); // No underscore required for external calls
  print(p._x); // 10
}
Code language: PHP (php)

Use original private variable names inside initializer lists:

class Point {
  final double _x;
  Point({required this._x}) : assert(_x > 0);
}
Code language: JavaScript (javascript)

12. Parent Class Constructors & Inheritance Rules

  1. Subclasses do not inherit any parent constructors;
  2. Constructor execution order: Initializer list → Superclass constructor → Subclass constructor body;
  3. If the superclass lacks a parameterless default constructor, subclasses must manually call super() to specify the parent constructor.
Basic super Call Example

Similar to Java’s super keyword; C# uses base for base class references.

class Person {
  final String name;
  Person(this.name);
  Person.fromMap(Map map) : name = map["name"];
}

class Employee extends Person {
  int id;
  // Call parent named constructor
  Employee(Map data) : super.fromMap(data), id = data["id"];
}
Code language: PHP (php)

Note: this cannot be used inside super argument expressions.

Super Parameters (Dart 2.17+, simplify parent argument forwarding)

Use super.variable to forward arguments directly to the superclass constructor without manual 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 auto-forward to parent constructor
  Vector3d(super.x, super.y, this.z);
}

// Parent named constructor + super named parameters
class Vector3dYZ extends Vector2d {
  final double z;
  Vector3dYZ({required super.y, required this.z}) : super.named(x: 0);
}
Code language: JavaScript (javascript)

Leave a Reply

Your email address will not be published. Required fields are marked *