On this page22 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. Only One Unnamed Constructor
- 2. Named Constructors
- 3. Converting Values in a Named Constructor
- 4. Redirecting Constructors
- 5. const Constructors
- 6. Canonical Instances and identical()
- 7. When const Is Impossible
- 8. Factory Constructors
- 9. A Factory That Reuses Objects
- 10. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 55 — Named & Constant Constructors
| Category | 9 — Object-Oriented Programming |
| Lecture | 055 of 140 |
| Estimated video length | 10 min |
| Target | Dart 3.12.2 |
| Level | Beginner |
A class with one constructor has only one way to be built. Named constructors give it several clearly labelled ways, and
constconstructors let equal objects be shared.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2, so you will recognize it when you meet it yourself.
Prerequisites
Students should already understand, from Lectures 001–054:
- classes, objects, fields and methods (Lecture 053)
- constructors with
this.field,requirednamed parameters and initializer lists (Lecture 054) finalandconst(Lecture 006) andconstlists (Lecture 046)identical()(Lecture 018) and maps with??(Lectures 014 and 049)
Connection With Previous Lectures
In Lecture 054, every class had one constructor:
class Temperature {
final double celsius;
Temperature(this.celsius);
}Now Sara reads a thermometer that shows Fahrenheit. To build a Temperature, she must
convert the number herself before every call — and so must everyone else who uses the class.
The class knows how to store Celsius, but it cannot be built from anything else. We need a
second, clearly named way in.
Learning Objectives
By the end of this lecture you will be able to:
- Explain why a class cannot have two unnamed constructors
- Create named constructors such as
Point.origin() - Use initializer lists inside named constructors
- Write redirecting constructors with
: this(...) - Create
constconstructors and constant objects - Predict when two objects are
identical() - Recognize when a
constconstructor is impossible - Write simple
factoryconstructors, including one that reuses objects
1. Only One Unnamed Constructor
The constructor named after the class is the unnamed constructor. A class can have only one. Trying to add a second one for Fahrenheit fails:
class Temperature {
final double celsius;
Temperature(this.celsius);
// Temperature(double fahrenheit) : celsius = (fahrenheit - 32) * 5 / 9; // ✗
}Error: 'Temperature' is already declared in this scope.Two constructors with the same name clash, even with different parameters.
2. Named Constructors
A named constructor adds a name after a dot: ClassName.name(...).
Analogy — a shop has a front door, a side door and a delivery door. Every door leads into the same shop; the name tells you which way you are coming in.
class Point {
final int x;
final int y;
Point(this.x, this.y);
Point.origin() : x = 0, y = 0;
}
void main() {
var a = Point(3, 4);
var b = Point.origin();
print('${a.x}, ${a.y}');
print('${b.x}, ${b.y}');
}3, 4
0, 0Point.origin() needs no arguments: its initializer list sets both fields. The call reads
like a sentence — "a point at the origin".
3. Converting Values in a Named Constructor
Named constructors solve Sara's problem. The conversion moves into the class, once:
class Temperature {
final double celsius;
Temperature(this.celsius);
Temperature.fromFahrenheit(double fahrenheit)
: celsius = (fahrenheit - 32) * 5 / 9;
}
void main() {
var today = Temperature(25);
var reading = Temperature.fromFahrenheit(98.6);
print(today.celsius);
print(reading.celsius.toStringAsFixed(1));
}25.0
37.0A class may have as many named constructors as it needs, each with its own parameters —
positional, named or required, exactly as in Lecture 054.
4. Redirecting Constructors
Writing x = 0, y = 0 repeats work the main constructor already does. A redirecting
constructor hands the job to another constructor with : this(...):
class Point {
final int x;
final int y;
Point(this.x, this.y);
Point.origin() : this(0, 0);
Point.onXAxis(int x) : this(x, 0);
}
void main() {
var p = Point.onXAxis(7);
print('${p.x}, ${p.y}');
print(Point.origin().y);
}7, 0
0A redirecting constructor does nothing else: no body, no other initializers. If the main constructor later gains a check, every redirect gets it for free.
5. const Constructors
In Lecture 006 we said const would come back for objects. Put const before a constructor,
make every field final, and the class can create constant objects — fixed before the
program runs:
class Point {
final int x;
final int y;
const Point(this.x, this.y);
}
void main() {
const corner = Point(10, 20);
print('${corner.x}, ${corner.y}');
}10, 20Like a const list, a const object can never change — its fields are final, and the
whole object is decided while you write the code.
6. Canonical Instances and identical()
Dart creates one object for each distinct constant. Writing the same constant twice gives you the same object back — the canonical instance:
class Point {
final int x;
final int y;
const Point(this.x, this.y);
}
void main() {
const a = Point(1, 2);
const b = Point(1, 2);
var c = Point(1, 2);
var d = Point(1, 2);
print(identical(a, b));
print(identical(c, d));
print(identical(a, c));
}true
false
falsea and b are one shared object. c and d were called without const, so each call
built a new object, even though the constructor is const. A const constructor allows
constant objects; you still ask for one with const.
== on your own objects compares identity for now; teaching a class what "equal" means
comes in Lecture 060 — Method Overriding.
7. When const Is Impossible
A const constructor has three rules, and Dart enforces each one.
Every field must be final:
class Point {
int x;
int y;
// const Point(this.x, this.y); // ✗
}Error: Constructor is marked 'const' so all fields must be final.The constructor cannot have a body:
class Point {
final int x;
final int y;
// const Point(this.x, this.y) { print('made'); } // ✗
}Error: A const constructor can't have a body.
Try removing either the 'const' keyword or the body.A const object needs constant arguments. A value worked out while the program runs
cannot go in:
void main() {
var x = 3;
// const p = Point(x, 0); // ✗ x is not a constant
final p = Point(x, 0); // ✓ a normal object
print(p.x);
}Error: Not a constant expression.That last case is not a problem with the class — Point(x, 0) without const works fine.
8. Factory Constructors
Every constructor so far builds a new object. A factory constructor is a normal
function body that must return an object of the class — however it likes. Reading an
object out of a map is a typical job:
class Student {
final String name;
final int marks;
Student(this.name, this.marks);
factory Student.fromMap(Map<String, Object> map) {
var name = map['name'] as String? ?? 'Unknown';
var marks = map['marks'] as int? ?? 0;
return Student(name, marks);
}
}
void main() {
var ali = Student.fromMap({'name': 'Ali', 'marks': 85});
var blank = Student.fromMap({});
print('${ali.name}: ${ali.marks}');
print('${blank.name}: ${blank.marks}');
}Ali: 85
Unknown: 0A map value is an Object?, so as String? treats it as a nullable String (Lecture 011)
and ?? supplies a default. The factory can run any such code before it calls a real
constructor. It has no this, because no object exists yet when it starts. Turning text
into such maps is the job of Lecture 108 — JSON and Lecture 109 — JSON & Dart Objects.
9. A Factory That Reuses Objects
A factory may also return an object that already exists. Here a map remembers every badge made so far:
final badgeCache = <String, Badge>{};
class Badge {
final String name;
Badge.create(this.name) {
print('Creating $name badge');
}
factory Badge(String name) {
return badgeCache.putIfAbsent(name, () => Badge.create(name));
}
}
void main() {
var first = Badge('Gold');
var second = Badge('Gold');
print(identical(first, second));
}Creating Gold badge
trueThe unnamed constructor is the factory, so callers write a plain Badge('Gold'); the
named Badge.create does the real building. A cache that belongs to the class itself uses
static, coming in Lecture 058 — Static Members.
10. A Real-World Example
class Account {
final String owner;
final double balance;
const Account(this.owner, this.balance);
const Account.empty(String owner) : this(owner, 0);
factory Account.fromMap(Map<String, Object> map) {
var owner = map['owner'] as String? ?? 'Guest';
var balance = map['balance'] as double? ?? 0;
return Account(owner, balance);
}
}
void main() {
const guest = Account.empty('Guest');
var zayan = Account('Zayan', 150);
var sara = Account.fromMap({'owner': 'Sara', 'balance': 2000.0});
for (var account in [guest, zayan, sara]) {
print('${account.owner}: ${account.balance}');
}
print(identical(guest, const Account('Guest', 0)));
}Guest: 0.0
Zayan: 150.0
Sara: 2000.0
trueThree doors: the main constructor, a redirecting const named one (a redirect may be const
when its target is), and a factory reading a map. Account.empty('Guest') and
Account('Guest', 0) describe the same constant, so they are the same object.
Code Examples
Example 1 — A Named Constructor
class Point {
final int x;
final int y;
Point(this.x, this.y);
Point.origin() : x = 0, y = 0;
}
void main() {
var p = Point.origin();
print('(${p.x}, ${p.y})');
}(0, 0)Example 2 — Two Named Constructors
class Student {
final String name;
final int grade;
Student(this.name, this.grade);
Student.newcomer(this.name) : grade = 1;
Student.guest() : name = 'Guest', grade = 0;
}
void main() {
var zayan = Student.newcomer('Zayan');
var visitor = Student.guest();
print('${zayan.name}, grade ${zayan.grade}');
print('${visitor.name}, grade ${visitor.grade}');
}Zayan, grade 1
Guest, grade 0Example 3 — Redirecting
class Rectangle {
final int width;
final int height;
Rectangle(this.width, this.height);
Rectangle.square(int side) : this(side, side);
}
void main() {
var tile = Rectangle.square(5);
print(tile.width * tile.height);
}25Example 4 — A const Object
class Color {
final int red;
final int green;
final int blue;
const Color(this.red, this.green, this.blue);
}
void main() {
const white = Color(255, 255, 255);
print('${white.red}, ${white.green}, ${white.blue}');
}255, 255, 255Example 5 — Same Constant, Same Object
class Color {
final int red;
final int green;
final int blue;
const Color(this.red, this.green, this.blue);
}
void main() {
const a = Color(0, 0, 0);
const b = Color(0, 0, 0);
print(identical(a, b));
print(identical(Color(0, 0, 0), Color(0, 0, 0)));
}true
falseThe last line calls the constructor without const, so it builds two separate objects.
Example 6 — A const List of Objects
class Point {
final int x;
final int y;
const Point(this.x, this.y);
}
void main() {
const corners = [Point(0, 0), Point(0, 5), Point(5, 5)];
print(corners.length);
print(identical(corners[0], const Point(0, 0)));
}3
trueInside a const list, every element is constant automatically — no extra const needed.
Example 7 — Student Marks
class Report {
final String student;
final int total;
Report(this.student, this.total);
factory Report.fromMarks(String student, Map<String, int> marks) {
var total = 0;
for (var mark in marks.values) {
total += mark;
}
return Report(student, total);
}
}
void main() {
var marks = {'Math': 80, 'Science': 75, 'English': 85};
var report = Report.fromMarks('Ali', marks);
print('${report.student}: ${report.total}');
}Ali: 240How it works
- The factory adds up the map's values before any
Reportexists. - It then calls the main constructor with the finished total.
Example 8 — Shopping Item with a Discount
class Item {
final String name;
final double price;
Item(this.name, this.price);
Item.discounted(String name, double price, double rate)
: this(name, price - price * rate);
}
void main() {
var bag = Item.discounted('Bag', 1500, 0.2);
print('${bag.name}: ${bag.price}');
}Bag: 1200.0Example 9 — Bank Account
class Account {
final String owner;
final double balance;
const Account(this.owner, this.balance);
const Account.empty(String owner) : this(owner, 0);
}
void main() {
var sara = Account('Sara', 2000);
const zayan = Account.empty('Zayan');
print('${sara.owner}: ${sara.balance}');
print('${zayan.owner}: ${zayan.balance}');
}Sara: 2000.0
Zayan: 0.0Example 10 — Temperature Both Ways
class Temperature {
final double celsius;
Temperature(this.celsius);
Temperature.fromFahrenheit(double f) : celsius = (f - 32) * 5 / 9;
}
void main() {
var readings = [Temperature(30), Temperature.fromFahrenheit(212)];
for (var t in readings) {
print('${t.celsius} °C');
}
}30.0 °C
100.0 °CHow it works
- Both constructors produce the same kind of object, so they fit in one list.
(212 - 32) * 5 / 9is100.0.
Common Beginner Mistakes
1. Two unnamed constructors
class Temperature {
final double celsius;
Temperature(this.celsius);
// Temperature(double f) : celsius = (f - 32) * 5 / 9; // ✗
}Error: 'Temperature' is already declared in this scope.Give the second one a name: Temperature.fromFahrenheit(...).
2. Calling a named constructor that does not exist
class Point {
final int x;
final int y;
Point(this.x, this.y);
}
void main() {
// var p = Point.origin(); // ✗
}Error: Member not found: 'Point.origin'.3. Giving a redirecting constructor a body
class Point {
final int x;
final int y;
Point(this.x, this.y);
// Point.origin() : this(0, 0) { print('origin'); } // ✗
}Error: Redirecting constructors can't have a body.
Try removing the body, or not making this a redirecting constructor.4. Asking for const from a normal constructor
class Point {
final int x;
final int y;
Point(this.x, this.y);
}
void main() {
// const p = Point(1, 2); // ✗
}Error: Cannot invoke a non-'const' constructor where a const expression is expected.
Try using a constructor or factory that is 'const'.Add const to the constructor — if all fields are final.
5. Using this inside a factory
class Student {
final String name;
Student(this.name);
factory Student.fromMap(Map<String, String> map) {
// print(this.name); // ✗ no object exists yet
return Student(map['name'] ?? '');
}
}Error: Expected identifier, but got 'this'.Best Practices
- Name constructors after what they take or mean:
fromFahrenheit,fromMap,origin. - Redirect to the main constructor instead of repeating field assignments.
- Make a class's constructor
constwhen all its fields arefinaland its values can be known in advance. - Create objects with
constwherever the arguments are constant. - Use a
factoryonly when you need code before building, or want to return an existing object — otherwise a normal constructor is simpler.
Mini Challenge
Write a Box class with final fields width and height (both int), a const
constructor, and a const Box.square(int side) that redirects. Create
const Box.square(4) and const Box(4, 4), print the area of the first, and whether the two
are identical.
Your program should print exactly:
Area: 16
trueTry it yourself before opening the solution.
Show solution
class Box {
final int width;
final int height;
const Box(this.width, this.height);
const Box.square(int side) : this(side, side);
}
void main() {
const a = Box.square(4);
const b = Box(4, 4);
print('Area: ${a.width * a.height}');
print(identical(a, b));
}Area: 16
trueHow it works
Box.square(4)redirects toBox(4, 4), so both describe the same constant.- Dart keeps one object per constant, so
identicalistrue.
Practice Questions
Work through these in order — they run from beginner to application to challenge.
Beginner
- Give a
Pointclass (final int x, y) a named constructorPoint.origin()using an initializer list. Print0, 0. - Add
Student.guest()to aStudentclass with afinal String name, setting it to'Guest'. Print the name. - Make
Point's constructorconst, createconst p = Point(2, 3)and print2, 3.
Application
-
Give
Rectangle(final int width, height) a redirectingRectangle.square(int side). Print the area of a square with side6. -
Write
Temperature.fromFahrenheitand print the Celsius value of50Fahrenheit. -
Predict, then run: two
const Point(1, 1)objects and twoPoint(1, 1)objects — what doesidenticalprint for each pair? -
This class does not compile. Fix it with one keyword so that
const Score(90)works:class Score { int value; const Score(this.value); } -
Write
factory Item.fromMap(Map<String, Object> map)for anItemwithnameandprice(double). Use'Unknown'and0when a key is missing. Print an item built from{'name': 'Pen', 'price': 50.0}and one from{}.
Challenge
- Write an unnamed
factory Grade(String letter)that returns the same object for the same letter, using a top-level map and a namedGrade.create. Printtruefor two'A'grades. - Write an
Accountwith aconstmain constructor, aconst Account.empty(String owner)that redirects, and a factoryAccount.fromMap. Print Ali's account from{'owner': 'Ali', 'balance': 500.0}, then Zayan's empty account.
Practice Answers
Question 1 — Solution
class Point {
final int x;
final int y;
Point(this.x, this.y);
Point.origin() : x = 0, y = 0;
}
void main() {
var p = Point.origin();
print('${p.x}, ${p.y}');
}0, 0Question 2 — Solution
class Student {
final String name;
Student(this.name);
Student.guest() : name = 'Guest';
}
void main() {
print(Student.guest().name);
}GuestQuestion 3 — Solution
class Point {
final int x;
final int y;
const Point(this.x, this.y);
}
void main() {
const p = Point(2, 3);
print('${p.x}, ${p.y}');
}2, 3Question 4 — Solution
class Rectangle {
final int width;
final int height;
Rectangle(this.width, this.height);
Rectangle.square(int side) : this(side, side);
}
void main() {
var square = Rectangle.square(6);
print(square.width * square.height);
}36Question 5 — Solution
class Temperature {
final double celsius;
Temperature(this.celsius);
Temperature.fromFahrenheit(double f) : celsius = (f - 32) * 5 / 9;
}
void main() {
print(Temperature.fromFahrenheit(50).celsius);
}10.0Question 6 — Solution
class Point {
final int x;
final int y;
const Point(this.x, this.y);
}
void main() {
print(identical(const Point(1, 1), const Point(1, 1)));
print(identical(Point(1, 1), Point(1, 1)));
}true
falseOnly the const pair shares one canonical object.
Question 7 — Solution
Make the field final:
class Score {
final int value;
const Score(this.value);
}
void main() {
const s = Score(90);
print(s.value);
}90Question 8 — Solution
class Item {
final String name;
final double price;
Item(this.name, this.price);
factory Item.fromMap(Map<String, Object> map) {
var name = map['name'] as String? ?? 'Unknown';
var price = map['price'] as double? ?? 0;
return Item(name, price);
}
}
void main() {
var pen = Item.fromMap({'name': 'Pen', 'price': 50.0});
var blank = Item.fromMap({});
print('${pen.name}: ${pen.price}');
print('${blank.name}: ${blank.price}');
}Pen: 50.0
Unknown: 0.0Question 9 — Solution
final gradeCache = <String, Grade>{};
class Grade {
final String letter;
Grade.create(this.letter);
factory Grade(String letter) {
return gradeCache.putIfAbsent(letter, () => Grade.create(letter));
}
}
void main() {
print(identical(Grade('A'), Grade('A')));
}trueQuestion 10 — Solution
class Account {
final String owner;
final double balance;
const Account(this.owner, this.balance);
const Account.empty(String owner) : this(owner, 0);
factory Account.fromMap(Map<String, Object> map) {
var owner = map['owner'] as String? ?? 'Guest';
var balance = map['balance'] as double? ?? 0;
return Account(owner, balance);
}
}
void main() {
var ali = Account.fromMap({'owner': 'Ali', 'balance': 500.0});
const zayan = Account.empty('Zayan');
print('${ali.owner}: ${ali.balance}');
print('${zayan.owner}: ${zayan.balance}');
}Ali: 500.0
Zayan: 0.0Quick Revision
- Why can a class not have two unnamed constructors?
- How do you call a named constructor?
- What does
: this(0, 0)do in a constructor? - What two things must be true of a class for its constructor to be
const? - Why is
identical(const Point(1, 2), const Point(1, 2))true? - Is
Point(1, 2)constant if the constructor isconst? - How is a
factoryconstructor different from a normal one?
Key Takeaways
- A class has at most one unnamed constructor; named constructors (
Point.origin()) add more ways in. - Named constructors can use initializer lists to convert values, like
Temperature.fromFahrenheit. - A redirecting constructor (
: this(...)) passes the work to another constructor and has no body. - A
constconstructor needs all fieldsfinaland no body;constobjects need constant arguments. - Each distinct constant exists once: two equal
constobjects areidentical(). - Without the
constkeyword at the call, aconstconstructor builds a new object. - A
factoryruns code and returns an object — a new one or one it saved earlier.
What's Next?
Lecture 56 — this, Getters & Setters. Our objects can now be built in many ways, but
their fields are still read and written directly. Next we will look closely at this, and
learn how getters and setters let a class compute values and control every change to its
data.