Constructors
- Initializing formal parameters
- Default constructors
- Constructors aren’t inherited
- Named constructors
- Invoking a non-default superclass constructor
- Initializer list
- Redirecting constructors
- Constant constructors
- Factory constructors
Declare a constructor by creating a function with the same name as its class (plus, optionally, an additional identifier as described in Named constructors). The most common form of constructor, the generative constructor, creates a new instance of a class:
class Point {
double x = 0;
double y = 0;
Point(double x, double y) {
// See initializing formal parameters for a better way
// to initialize instance variables.
this.x = x;
this.y = y;
}
}
The this
keyword refers to the current instance.
Initializing formal parameters
The pattern of assigning a constructor argument to an instance variable is so common, Dart has initializing formal parameters to make it easy.
Initializing parameters can also be used to initialize
non-nullable or final
instance variables,
which both must be initialized or provided a default value.
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);
}
The variables introduced by the initializing formals are implicitly final and only in scope of the initializer list.
Default constructors
If you don’t declare a constructor, a default constructor is provided for you. The default constructor has no arguments and invokes the no-argument constructor in the superclass.
Constructors aren’t inherited
Subclasses don’t inherit constructors from their superclass. A subclass that declares no constructors has only the default (no argument, no name) constructor.
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;
Point(this.x, this.y);
// Named constructor
Point.origin()
: x = xOrigin,
y = yOrigin;
}
Remember that constructors are not inherited, which means that a superclass’s named constructor is not inherited by a subclass. If you want a subclass to be created with a named constructor defined in the superclass, you must implement that constructor in the subclass.
Invoking a non-default superclass constructor
By default, a constructor in a subclass calls the superclass’s unnamed, no-argument constructor. The superclass’s constructor is called at the beginning of the constructor body. If an initializer list is also being used, it executes before the superclass is called. In summary, the order of execution is as follows:
- initializer list
- superclass’s no-arg constructor
- main class’s no-arg constructor
If the superclass doesn’t have an unnamed, no-argument constructor,
then you must manually call one of the constructors in the
superclass. Specify the superclass constructor after a colon (:
), just
before the constructor body (if any).
In the following example, the constructor for the Employee class calls the named constructor for its superclass, Person. Click Run to execute the code.
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(super.data) : super.fromJson() {
print('in Employee');
}
}
void main() {
var employee = Employee.fromJson({});
print(employee);
// Prints:
// in Person
// in Employee
// Instance of 'Employee'
}
Because the arguments to the superclass constructor are evaluated before invoking the constructor, an argument can be an expression such as a function call:
class Employee extends Person {
Employee() : super.fromJson(fetchDefaultData());
// ···
}
Super parameters
To avoid having to manually pass each parameter into the super invocation of a constructor, you can use super-initializer parameters to forward parameters to the specified or default superclass constructor. This feature can’t be used with redirecting constructors. Super-initializer parameters have similar syntax and semantics to initializing formal parameters:
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);
}
Super-initializer parameters cannot be positional if the super-constructor invocation already has positional arguments, but they can always be named:
class Vector2d {
// ...
Vector2d.named({required this.x, required this.y});
}
class Vector3d extends Vector2d {
// ...
// 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);
}
Initializer list
Besides invoking a superclass constructor, you can also initialize instance variables before the constructor body runs. 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)');
}
During development, you can validate inputs by using assert
in the
initializer list.
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
Initializer lists are handy when setting up final fields. The following example initializes three final fields in an initializer list. Click Run to execute the code.
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);
}
Redirecting constructors
Sometimes a constructor’s only purpose is to redirect to another
constructor in the same class. A redirecting constructor’s body is
empty, with the constructor call
(using this
instead of the class name)
appearing 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);
}
Constant constructors
If your class produces objects that never change, you can make these
objects compile-time constants. To do this, define a const
constructor
and make sure that all instance variables are 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. For details, see the section on using constructors.
Factory constructors
Use the factory
keyword when implementing a constructor that doesn’t
always create a new instance of its class. For example, a factory
constructor might return an instance from a cache, or it might
return an instance of a subtype.
Another use case for factory constructors is
initializing a final variable using
logic that can’t be handled in the initializer list.
In the following example,
the Logger
factory constructor returns objects from a cache,
and the 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);
}
}
Invoke a factory constructor just like you would any other constructor:
var logger = Logger('UI');
logger.log('Button clicked');
var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);