In Dart, which operators can a class define, and how would you declare + and [] on a coordinate value type?
answer
- operators are instance methods
- the operator keyword
- != is built in
- += comes free from +
- unary minus is operator -()
basics
~10 sArithmetic, bitwise, relational, ==, [] and []= are declared as methods with the operator keyword, such as Coordinate operator +(Coordinate other). !=, &&, ||, ?? and ! are built in, and += reuses +.
solid answer
~40 sMost Dart operators are instance methods with special names, declared with `operator`: `Coordinate operator +(Coordinate other) => Coordinate(x + other.x, y + other.y);` and `double operator [](int index) => ...`. The definable set is `<`, `>`, `<=`, `>=`, `==`, `~`, `-`, `+`, `/`, `~/`, `*`, `%`, `|`, `^`, `&`, `<<`, `>>>`, `>>`, `[]` and `[]=`. Unary minus is `operator -()` with no parameters and can coexist with a binary `operator -(Coordinate other)`. `!=` is always `!(a == b)`, the short-circuiting `&&`, `||` and `??` and prefix `!` cannot be defined, and precedence and associativity are fixed by the language. Compound assignment desugars, so `p += q` means `p = p + q`. Define `[]=` only on mutable types, and overriding `==` still obliges you to override `hashCode`.
code
dart · 25 linesclass Coordinate {
const Coordinate(this.x, this.y);
final double x;
final double y;
Coordinate operator +(Coordinate other) => Coordinate(x + other.x, y + other.y);
Coordinate operator -(Coordinate other) => Coordinate(x - other.x, y - other.y);
Coordinate operator -() => Coordinate(-x, -y); // unary minus
Coordinate operator *(double factor) => Coordinate(x * factor, y * factor);
double operator [](int index) => switch (index) {
0 => x,
1 => y,
_ => throw RangeError.range(index, 0, 1, 'index'),
};
}
void main() {
var p = const Coordinate(1, 2);
p += const Coordinate(3, 4); // p = p + Coordinate(3, 4)
assert(p[0] == 4 && p[1] == 6);
assert((-p)[1] == -6);
assert((p * 0.5)[0] == 2);
}go deeper
Recall that operators are declared with the operator keyword and that + on a value type should return a new instance.
Explain which operators are definable, why != and += are not, and how unary minus is declared next to binary minus.
Judge when operator overloading improves an API and when it hides mutation, surprising precedence or broken hash behaviour.
Set conventions for operator use across shared value types, such as only arithmetic-like operators and always immutable results.
## Operators are methods In Dart, most operators are **instance methods with special names**. `a + b` invokes the `+` method on `a` with `b` as the argument, exactly like `a.plus(b)` would, which is why `int`, `double`, `String`, `Duration` and `DateTime` can all respond to `+` differently. You declare one with the built-in identifier `operator` followed by the symbol: ```dart Coordinate operator +(Coordinate other) => Coordinate(x + other.x, y + other.y); ``` ## The definable set | Group | Operators | |---|---| | Relational and equality | `<` `>` `<=` `>=` `==` | | Arithmetic | `+` `-` `*` `/` `~/` `%` | | Bitwise and shift | `&` `\|` `^` `~` `<<` `>>` `>>>` | | Indexing | `[]` `[]=` | Everything else is built in and cannot be declared: - `!=` is always evaluated as `!(a == b)`, so defining `==` gives you both. - `&&`, `||` and `??` short-circuit, so they cannot be ordinary method calls. - Prefix `!`, the conditional `?:`, the cascade `..`, null-aware `?.` and the type operators `is` and `as` are part of the grammar. ## Arity and special forms 1. **Binary operators** take exactly one parameter: `operator *(double factor)`. 2. **Unary minus** is written `operator -()` with **no parameters**. It is a separate member from binary `-`, so a class may have both, as `int` does. Internally its name is `unary-`, exposed as `Symbol.unaryMinus`, but you never write that name in a declaration. 3. **Bitwise complement** `~` is also unary: `operator ~()`. 4. **`[]`** takes one parameter, the index or key: `double operator [](int index)`. 5. **`[]=`** takes two, the index and the value, and is conventionally declared to return `void`; the value of `a[i] = v` as an expression is `v` regardless. ## Compound assignment and increment come for free `p += q` is **not** a separate operator. It desugars to `p = p + q`, and it works for any variable whose static type defines `+`. The same holds for `-=`, `*=`, `~/=`, `<<=` and the rest, and `p[i] += 1` uses both `[]` and `[]=`. Because the result is **assigned back**, `+=` on an immutable value type rebinds the variable to a new object instead of mutating the old one, so the variable cannot be `final`. ## Fixed precedence Declaring an operator does not let you change how it parses. `a + b * c` is always `a + (b * c)`, whatever the classes do. Pick operators whose built-in precedence matches the meaning; overloading `^` to mean "power" reads naturally but binds more loosely than `*`, which surprises readers. ## Relational operators and sorting Defining `<` and `>` does not make a class sortable. `List.sort()` without a comparator expects elements that implement `Comparable` and calls `compareTo`; it never calls your `<`. If a type should both read naturally in comparisons and sort, implement `Comparable<T>` with `compareTo` and define the relational operators in terms of it, so the two can never disagree. The same caution applies to `==`: a `compareTo` that returns `0` for objects that are not `==` confuses sorted collections that treat "compares as zero" as "the same element". ## Design guidance - **Match the mathematical meaning.** `+` for vector addition, `*` by a scalar and unary `-` for negation are expected; `+` meaning "append to a log" is not. - **Keep value types immutable.** Return a new instance from `+` rather than mutating `this`, and skip `[]=` on a class used as a map key or set element; mutating it after insertion breaks hash lookups. - **Return the right type.** `operator ==` must return `bool`. Comparison operators should agree with `==` and with `compareTo` if the class implements `Comparable`. - **Parameter types follow override rules.** When you override an inherited operator, such as `==` from `Object`, the parameter may not be narrowed; declare `operator ==(Object other)` and type-test inside. - **Remember `hashCode`.** `==` is on the definable list, and overriding it without `hashCode` breaks hash-based collections.
- Can you overload != in Dart to behave differently from ==?No. `!=` is not a method; the language evaluates `a != b` as `!(a == b)`. That guarantees the two can never disagree, and it is why the definable list includes `==` but not `!=`.
- Why does `p += q` fail to compile when p is declared final, even though Coordinate's + returns a new object?Compound assignment desugars to `p = p + q`, an assignment to `p` itself. A `final` variable cannot be reassigned, so the compiler rejects it even though `+` never mutates the original object.
saying these in an interview costs you the question
- You can define != separately to optimise inequality checks.
- += must be declared as its own operator method.
- Unary minus needs a special operator name in the declaration.
- Overloading an operator lets you change its precedence.
- && and || can be overloaded like any other operator.