Constructors
Constructors are special functions that create instances of classes.
Dart implements many types of constructors. Except for default constructors, these functions use the same name as their class.
- Generative constructors: Creates new instances and initializes instance variables.
- Default constructors: Used to create a new instance when a constructor hasn't been specified. It doesn't take arguments and isn't named.
- Named constructors: Clarifies the purpose of a constructor or allows the creation of multiple constructors for the same class.
- Constant constructors: Creates instances as compile-type constants.
- Factory constructors: Either creates a new instance of a subtype or returns an existing instance from cache.
- Redirecting constructor: Forwards calls to another constructor in the same class.
Types of constructors
#Generative constructors
#To instantiate a class, use a generative constructor.
class Point {
// Initializer list of variables and values
double x = 2.0;
double y = 2.0;
// Generative constructor with initializing formal parameters:
Point(this.x, this.y);
}
Default constructors
#If you don't declare a constructor, Dart uses the default constructor. The default constructor is a generative constructor without arguments or name.
Named constructors
#Use a named constructor to implement multiple constructors for a class or to provide extra clarity:
const double xOrigin = 0;
const double yOrigin = 0;
class Point {
final double x;
final double y;
// Sets the x and y instance variables
// before the constructor body runs.
Point(this.x, this.y);
// Named constructor
Point.origin()
: x = xOrigin,
y = yOrigin;
}
A subclass doesn't inherit a superclass's named constructor. To create a subclass with a named constructor defined in the superclass, implement that constructor in the subclass.
Constant constructors
#If your class produces unchanging objects, make these objects compile-time constants. To make objects compile-time constants, define a const
constructor with all instance variables set as final
.
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
Constant constructors don't always create constants. They might be invoked in a non-const
context. To learn more, consult the section on using constructors.
Redirecting constructors
#A constructor might redirect to another constructor in the same class. A redirecting constructor has an empty body. The constructor uses this
instead of the class name after a colon (:).
class Point {
double x, y;
// The main constructor for this class.
Point(this.x, this.y);
// Delegates to the main constructor.
Point.alongXAxis(double x) : this(x, 0);
}
Factory constructors
#When encountering one of following two cases of implementing a constructor, use the factory
keyword:
The constructor doesn't always create a new instance of its class. Although a factory constructor cannot return
null
, it might return:- an existing instance from a cache instead of creating a new one
- a new instance of a subtype
You need to perform non-trivial work prior to constructing an instance. This could include checking arguments or doing any other processing that cannot be handled in the initializer list.
The following example includes two factory constructors.
Logger
factory constructor returns objects from a cache.Logger.fromJson
factory constructor initializes a final variable from a JSON object.
class Logger {
final String name;
bool mute = false;
// _cache is library-private, thanks to
// the _ in front of its name.
static final Map<String, Logger> _cache = <String, Logger>{};
factory Logger(String name) {
return _cache.putIfAbsent(name, () => Logger._internal(name));
}
factory Logger.fromJson(Map<String, Object> json) {
return Logger(json['name'].toString());
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
Use a factory constructor as any other constructor:
var logger = Logger('UI');
logger.log('Button clicked');
var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);
Redirecting factory constructors
#A redirecting factory constructor specifies a call to a constructor of another class to use whenever someone makes a call to the redirecting constructor.
factory Listenable.merge(List<Listenable> listenables) = _MergingListenable
It might appear that ordinary factory constructors could create and return instances of other classes. This would make redirecting factories unnecessary. Redirecting factories have several advantages:
- An abstract class might provide a constant constructor that uses the constant constructor of another class.
- A redirecting factory constructor avoids the need for forwarders to repeat the formal parameters and their default values.
Constructor tear-offs
#Dart allows you to supply a constructor as a parameter without calling it. Called a tear-off (as you tear off the parentheses) serves as a closure that invokes the constructor with the same parameters.
If the tear-off is a constructor with the same signature and return type as the method accepts, you can use the tear-off as a parameter or variable.
Tear-offs differ from lambdas or anonymous functions. Lambdas serve as a wrapper for the constructor whereas a tear-off is the constructor.
Use Tear-Offs
// Use a tear-off for a named constructor:
var strings = charCodes.map(String.fromCharCode);
// Use a tear-off for an unnamed constructor:
var buffers = charCodes.map(StringBuffer.new);
Not Lambdas
// Instead of a lambda for a named constructor:
var strings = charCodes.map((code) => String.fromCharCode(code));
// Instead of a lambda for an unnamed constructor:
var buffers = charCodes.map((code) => StringBuffer(code));
For more discussion, watch this Decoding Flutter video on tear-offs.
Dart Tear-offs | Decoding Flutter
Instance Variable Initialization
#Dart can initialize variables in three ways.
Initialize instance variables in the declaration
#Initialize the instance variables when you declare the variables.
class PointA {
double x = 1.0;
double y = 2.0;
// The implicit default constructor sets these variables to (1.0,2.0)
// PointA();
@override
String toString() {
return 'PointA($x,$y)';
}
}
Use initializing formal parameters
#To simplify the common pattern of assigning a constructor argument to an instance variable, Dart has initializing formal parameters.
In the constructor declaration, include this.<propertyName>
and omit the body. The this
keyword refers to the current instance.
When the name conflict exists, use this
. Otherwise, Dart style omits the this
. An exception exists for the generative constructor where you must prefix the initializing formal parameter name with this
.
As noted earlier in this guide, certain constructors and certain parts of constructors can't access this
. These include:
- Factory constructors
- The right-hand side of an initializer list
- Arguments to a superclass constructor
Initializing formal parameters also allow you to initialize non-nullable or final
instance variables. Both of these types of variables require initialization or a default value.
class PointB {
final double x;
final double y;
// Sets the x and y instance variables
// before the constructor body runs.
PointB(this.x, this.y);
// Initializing formal parameters can also be optional.
PointB.optional([this.x = 0.0, this.y = 0.0]);
}
Private fields can't be used as named initializing formals.
class PointB {
// ...
PointB.namedPrivate({required double x, required double y})
: _x = x,
_y = y;
// ...
}
This also works with named variables.
class PointC {
double x; // must be set in constructor
double y; // must be set in constructor
// Generative constructor with initializing formal parameters
// with default values
PointC.named({this.x = 1.0, this.y = 1.0});
@override
String toString() {
return 'PointC.named($x,$y)';
}
}
// Constructor using named variables.
final pointC = PointC.named(x: 2.0, y: 2.0);
All variables introduced from initializing formal parameters are both final and only in scope of the initialized variables.
To perform logic that you can't express in the initializer list, create a factory constructor or static method with that logic. You can then pass the computed values to a normal constructor.
The constructor parameters could be set as nullable and not be initialized.
class PointD {
double? x; // null if not set in constructor
double? y; // null if not set in constructor
// Generative constructor with initializing formal parameters
PointD(this.x, this.y);
@override
String toString() {
return 'PointD($x,$y)';
}
}
Use an initializer list
#Before the constructor body runs, you can initialize instance variables. Separate initializers with commas.
// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, double> json)
: x = json['x']!,
y = json['y']! {
print('In Point.fromJson(): ($x, $y)');
}
To validate inputs during development, use assert
in the initializer list.
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
Initializer lists help set up final
fields.
The following example initializes three final
fields in an initializer list. To execute the code, click Run.
import 'dart:math';
class Point {
final double x;
final double y;
final double distanceFromOrigin;
Point(double x, double y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
void main() {
var p = Point(2, 3);
print(p.distanceFromOrigin);
}
Constructor inheritance
#Subclasses, or child classes, don't inherit constructors from their superclass, or immediate parent class. If a class doesn't declare a constructor, it can only use the default constructor.
A class can inherit the parameters of a superclass. These are called super parameters
Constructors work in a somewhat similar way to how you call a chain of static methods. Each subclass can call its superclass's constructor to initialize an instance, like a subclass can call a superclass's static method. This process doesn't "inherit" constructor bodies or signatures.
Non-default superclass constructors
#Dart executes constructors in the following order:
- initializer list
- superclass's unnamed, no-arg constructor
- main class's no-arg constructor
If the superclass lacks an unnamed, no-argument constructor, call one of the constructors in the superclass. Before the constructor body (if any), specify the superclass constructor after a colon (:
).
In the following example, the Employee
class constructor calls the named constructor for its superclass, Person
. To execute the following code, click Run.
class Person {
String? firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
// Person does not have a default constructor;
// you must call super.fromJson().
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
void main() {
var employee = Employee.fromJson({});
print(employee);
// Prints:
// in Person
// in Employee
// Instance of 'Employee'
}
As Dart evaluates the arguments to the superclass constructor before invoking the constructor, an argument can be an expression like a function call.
class Employee extends Person {
Employee() : super.fromJson(fetchDefaultData());
// ยทยทยท
}
Super parameters
#To avoid passing each parameter into the super invocation of a constructor, use super-initializer parameters to forward parameters to the specified or default superclass constructor. You can't use this feature with redirecting constructors. Super-initializer parameters have syntax and semantics like initializing formal parameters.
If the super-constructor invocation includes positional arguments, super-initializer parameters can't be positional.
class Vector2d {
final double x;
final double y;
Vector2d(this.x, this.y);
}
class Vector3d extends Vector2d {
final double z;
// Forward the x and y parameters to the default super constructor like:
// Vector3d(final double x, final double y, this.z) : super(x, y);
Vector3d(super.x, super.y, this.z);
}
To further illustrate, consider the following example.
// If you invoke the super constructor (`super(0)`) with any
// positional arguments, using a super parameter (`super.x`)
// results in an error.
Vector3d.xAxisError(super.x): z = 0, super(0); // BAD
This named constructor tries to set the x
value twice: once in the super constructor and once as a positional super parameter. As both address the x
positional parameter, this results in an error.
When the super constructor has named arguments, you can split them between named super parameters (super.y
in the next example) and named arguments to the super constructor invocation (super.named(x: 0)
).
class Vector2d {
// ...
Vector2d.named({required this.x, required this.y});
}
class Vector3d extends Vector2d {
final double z;
// Forward the y parameter to the named super constructor like:
// Vector3d.yzPlane({required double y, required this.z})
// : super.named(x: 0, y: y);
Vector3d.yzPlane({required super.y, required this.z}) : super.named(x: 0);
}
Unless stated otherwise, the documentation on this site reflects Dart 3.5.3. Page last updated on 2024-08-04. View source or report an issue.