skip to content

In Dart, why does a class use named constructors instead of overloaded ones, and what does a redirecting constructor add?

level: juniorimportance: should knowfreq 58%

answer

  1. one name per constructor
  2. no overloading in Dart
  3. ClassName.name(...)
  4. colon this(...), empty body
  5. constructors are not inherited

basics

~20 s

Dart has no overloading, so a second constructor needs its own name, such as Money.euros. A redirecting constructor forwards to another constructor of the same class with : this(...), so all initialisation lives in one place.

solid answer

~40 s

Dart does not overload functions or constructors by parameter list, so a class gets one unnamed constructor and any number of **named constructors**, written `ClassName.name(...)`, like `Money.euros(int cents)`. The name documents intent and makes call sites read well. To avoid repeating initialisation, a named constructor can **redirect** to another generative constructor of the same class with `: this(...)` or `: this.other(...)`. A redirecting constructor has no body, no field initializers, no `assert` and no `super` call — the target does all of that. Two related facts: constructors, named ones included, are **not inherited**, so a subclass declares its own; and a class that declares no constructor gets an implicit unnamed no-argument one.

code

dart · 9 lines
dart
void main() {
  const price = Money.euros(420);
  const nothing = Money.zero('USD');
  print('${price.cents} ${price.currency}'); // 420 EUR
  print('${nothing.cents} ${nothing.currency}'); // 0 USD

  final prices = [100, 250].map(Money.euros).toList();
  print(prices.length); // 2
}

go deeper

for a junior

Recall that Dart has no overloading, so extra constructors are named, and that : this(...) redirects to another one.

for a middle

Explain the redirect rules, why constructors are not inherited, and when the implicit default constructor exists.

for a senior

Keep validation in one target constructor that every named path redirects to, and use private named constructors to control creation.

for a principal

Treat the set of public constructors as API surface: each named constructor is a commitment callers will depend on.

## No overloading, so constructors get names Many languages let a class declare several constructors with the same name and different parameter lists. **Dart does not support overloading** — neither for methods nor for constructors. A class therefore has: - at most one **unnamed constructor**, `Money(...)`; - any number of **named constructors**, `Money.euros(...)`, `Money.zero(...)`, `Money._(...)`. If a class declares no constructor at all, Dart supplies a **default constructor**: unnamed, no parameters, generative. Declaring any constructor removes it. Named constructors are usually a readability win: `Money.euros(420)` says more than `Money(420, 'EUR')`, and a leading underscore (`Money._`) makes a constructor **library-private**. ## Redirecting generative constructors When several constructors share setup, a **redirecting constructor** delegates to another generative constructor of the same class: ```dart class Money { const Money(this.cents, this.currency) : assert(cents >= 0), assert(currency.length == 3); const Money.euros(int cents) : this(cents, 'EUR'); const Money.zero(String currency) : this(0, currency); final int cents; final String currency; } ``` The rules the analyzer enforces: 1. The redirect is the **only** thing after the colon: `: this(...)` or `: this.name(...)`. 2. **No body** (`redirecting_constructor_with_body`). 3. **No field initializers** (`field_initializer_redirecting_constructor`), **no `assert`** (`assert_in_redirecting_constructor`) and **no `super(...)`** (`super_in_redirecting_constructor`). 4. The target must be **generative**; redirecting a generative constructor to a factory is an error. 5. Redirect **cycles** are rejected (`recursive_constructor_redirect`). The payoff is that validation and field assignment live in exactly one constructor. In the example, the `assert`s in `Money(...)` protect every path, including `Money.euros` and `Money.zero`. A `const` redirecting constructor must target a `const` constructor. ## Redirecting factory constructors A **factory** can redirect too, with `=` instead of a colon: `factory Money.fromMinor(int cents, String currency) = Money;`. It forwards to a constructor — possibly of another class — without repeating the parameters. That is how an abstract class exposes a constructor that produces a hidden implementation. | Form | Syntax | Target | |---|---|---| | Redirecting generative | `Money.euros(int c) : this(c, 'EUR');` | generative constructor of the same class | | Redirecting factory | `factory A.make() = B;` | constructor of the class or a subtype | | Plain factory | `factory Money.parse(String s) { ... }` | any code that returns an instance | ## Constructors are not inherited A subclass inherits methods and fields, but **not constructors**. If `class Tip extends Money` declares nothing, its only constructor is its own implicit `Tip()`, which calls the superclass's unnamed no-argument generative constructor — and fails to compile if `Money` has no such constructor. To offer `Tip.euros(...)`, the subclass declares it and chains to a `Money` generative constructor with `super`. ## Tear-offs Named and unnamed constructors can be passed around as functions without calling them: `amounts.map(Money.euros)` for a named one and `Money.new` for the unnamed one. A tear-off *is* the constructor, not a lambda wrapping it. ## Choosing between the forms For a value type such as `Money`, a common shape is: - one **target constructor** that owns field assignment and every `assert` — often unnamed and `const`; - **named redirecting constructors** for convenient variants (`Money.euros`, `Money.zero`) that add no logic of their own; - **factory constructors** (`Money.parse`) for anything that needs statements, caching or a subtype; - a **private named constructor** (`Money._`) when creation must go through factories only. That split keeps each rule in one place: redirects stay one line long, validation cannot be bypassed by a forgotten path, and the class's public constructor list reads as a menu of intentions rather than a set of parameter permutations.

  • In Dart, how do you pass the unnamed constructor of Money as a function?
    Use the tear-off `Money.new`; a named constructor tears off as `Money.euros`. The result is a function with the constructor's parameters, so `amounts.map(Money.euros)` builds one instance per element. Dart style prefers the tear-off to a lambda like `(c) => Money.euros(c)`, because the tear-off is the constructor itself.
  • In Dart, why can't a redirecting constructor also validate its argument with assert?
    A redirecting generative constructor may contain only the redirect; the analyzer reports `assert_in_redirecting_constructor` otherwise. Put the `assert` in the target constructor's initializer list instead, where it guards every path that redirects to it. If a redirecting path needs its own checks, turn it into a factory that validates and then calls the target.

saying these in an interview costs you the question

  • Dart picks between two Money constructors by their parameter types
  • A subclass automatically inherits the named constructors of its superclass
  • A redirecting constructor can add its own asserts and a body
  • A class always keeps its default constructor after declaring named ones
  • A generative constructor can redirect to a factory constructor