Dart Classes and Objects
A class is a core concept of object-oriented programming; it is an abstract description of things in the real world.
Objects are concrete instances of classes. This chapter introduces class definitions, constructors, member variables and methods, getters/setters, and static members in Dart.
Class definition and instantiation
A class is a template for creating objects, defining what properties (data) and behaviors (methods) the objects have.
Example
class User {
// Member variables (properties)
String name = '';
int age = 0;
// Member methods (behaviors)
void introduce() {
print('Hello, I am $name, I am $age years old.');
}
bool isAdult() {
return age >= 18;
}
}
void main() {
// Instantiate: with the new keyword (new can be omitted in Dart 2.0+)
var user1 = User();
user1.name = 'example';
user1.age = 10;
// Access member variables and methods
print('User name: ${user1.name}');
user1.introduce();
print('Is adult: ${user1.isAdult()}');
// Create another object, independent of each other
var user2 = User();
user2.name = 'Xiao Ming';
user2.age = 20;
user2.introduce();
}
用户名: example 你好,我是 example,今年 10 岁。 是否成年: false 你好,我是 小明,今年 20 岁。
Since Dart 2.0, the `new` keyword is optional when instantiating. The community convention is to omit `new` to keep code concise. Subsequent examples in this tutorial will omit `new`.
Constructor
A constructor is a special method automatically called when an object is created, used to initialize the object's state.
Dart provides multiple forms of constructors, making them very flexible.
Default constructor
If you do not define a constructor, Dart automatically provides a no-argument default constructor.
Regular constructor
Example
String name;
int age;
// Normal constructor: same name as the class
Student(String name, int age) {
this.name = name;
this.age = age;
}
void printInfo() {
print('Student: $name, Age: $age');
}
}
void main() {
var s1 = Student('example', 10);
var s2 = Student('Xiao Ming', 15);
s1.printInfo();
s2.printInfo();
}
学生: example, 年龄: 10 学生: 小明, 年龄: 15
Syntactic sugar constructor
When constructor parameters are directly assigned to member variables, Dart provides shorthand syntax.
Example
String name;
int age;
// Syntactic sugar: this.parameterName automatically assigns the parameter to a member variable with the same name
// This way is more concise than manually writing this.name = name
Student(this.name, this.age);
void printInfo() {
print('EXAMPLE Student: $name, Age: $age');
}
}
void main() {
var s = Student('example', 10);
s.printInfo();
}
EXAMPLE 学生: example, 年龄: 10
Named constructor
A class can have multiple named constructors for different initialization logic.
Example
double x;
double y;
// Default constructor
Point(this.x, this.y);
// Named constructor: creates the origin
Point.origin()
: x = 0,
y = 0;
// Named constructor: creates from a single value (x and y are the same)
Point.diagonal(double value)
: x = value,
y = value;
@override
String toString() => 'Point($x, $y)';
}
void main() {
var p1 = Point(3, 4);
var p2 = Point.origin();
var p3 = Point.diagonal(5);
print('EXAMPLE Coordinates: $p1, $p2, $p3');
}
EXAMPLE 坐标: Point(3.0, 4.0), Point(0.0, 0.0), Point(5.0, 5.0)
initializer list
The initializer list runs before the constructor body executes and is used to initialize final fields or perform parameter validation.
Example
final String name; // final fields must be initialized in the constructor
final int birthYear;
final int age;
// Initializer list: after the colon, before the function body
// The initializer list runs before the constructor body executes
Person(this.name, this.birthYear)
: age = 2026 - birthYear, // Computed property
assert(birthYear > 1900, 'Invalid birth year'); // Assertion check
void printInfo() {
print('EXAMPLE User: $name, Birth: $birthYear, Age: $age');
}
}
void main() {
var p = Person('example', 2016);
p.printInfo();
// The following line will trigger an assert failure (in development mode)
// var p2 = Person('error', 1800);
}
EXAMPLE 用户: example, 出生: 2016, 年龄: 10
`assert` only works in debug mode and is ignored in release mode. It is used to detect logic errors early during development.
Member variables and methods
Member variables store the state of an object, and member methods define the behavior of an object.
Example
// Private member: starts with an underscore, accessible only in this file
String _accountNumber;
double _balance = 0;
// Public member
String ownerName;
BankAccount(this.ownerName, this._accountNumber);
// Public method
void deposit(double amount) {
if (amount > 0) {
_balance += amount;
print('Deposited ¥$amount, current balance: ¥$_balance');
}
}
bool withdraw(double amount) {
if (amount > 0 && amount <= _balance) {
_balance -= amount;
print('Withdrawn ¥$amount, current balance: ¥$_balance');
return true;
}
print('Insufficient balance, withdrawal failed');
return false;
}
// Private method: for internal use within the class only
void _logTransaction(String type, double amount) {
print('[Internal log] $type: ¥$amount');
}
}
void main() {
var account = BankAccount('EXAMPLE', '6222-0000-1234');
account.deposit(1000);
account.withdraw(300);
account.withdraw(800); // Insufficient balance
// External code cannot access private members
// print(account._balance); // Error: _balance is private
}
存入 ¥1000,当前余额: ¥1000 取出 ¥300,当前余额: ¥700 余额不足,取款失败
Getter and Setter
Getters and setters are special methods for reading and writing member variables.
They allow you to execute extra logic when accessing properties while keeping the concise syntax of property access.
Example
double _radius; // Private variable storing the radius
Circle(this._radius);
// Getter: returns diameter (computed property)
double get diameter => _radius * 2;
// Getter: returns area
double get area => 3.14159 * _radius * _radius;
// Getter and Setter work together: expose the radius property externally
double get radius => _radius;
set radius(double value) {
// Validation logic can be added in the Setter
if (value <= 0) {
throw ArgumentError('Radius must be greater than 0');
}
_radius = value;
}
// Read-only Getter: no corresponding Setter
String get info => 'Circle(radius: $_radius)';
}
void main() {
var circle = Circle(5);
// Using Getter (like accessing a normal property)
print('EXAMPLE Circle: ${circle.info}');
print('Diameter: ${circle.diameter}');
print('Area: ${circle.area.toStringAsFixed(2)}');
// Using Setter
circle.radius = 10;
print('Modified diameter: ${circle.diameter}');
// Setting an invalid value will throw an exception
// circle.radius = -1; // throws ArgumentError
}
EXAMPLE 圆形: 圆(半径: 5.0) 直径: 10.0 面积: 78.54 修改后直径: 20.0
The advantage of getters and setters is that the caller does not need to know whether `radius` is a simple field or a computed property. If validation logic needs to be added to `radius` in the future, you can simply change the field to a getter/setter, and the calling code does not need to be modified. This is the benefit of encapsulation.
Static members
Static members belong to the class itself, not to any instance of the class.
All instances share the same static variable, and static methods can be called without creating an object.
Example
// Static variable: shared by all instances
static int totalCount = 0;
// Instance variable: independent for each instance
String label;
Counter(this.label) {
// Each time an instance is created, the static counter increments by 1
totalCount++;
}
// Static method: called via the class name
static void printTotal() {
print('EXAMPLE total instance count: $totalCount');
}
// Static methods are often used for utility functions
static bool isValidLabel(String label) {
return label.isNotEmpty && label.length <= 20;
}
}
void main() {
// Static method: called directly via the class name, no instance creation required
print('Is label valid: ${Counter.isValidLabel('test')}');
var c1 = Counter('Counter A');
Counter.printTotal(); // 1
var c2 = Counter('Counter B');
var c3 = Counter('Counter C');
Counter.printTotal(); // 3
// Static variable: accessed via the class name
print('Final count: ${Counter.totalCount}');
}
标签是否有效: true EXAMPLE 总实例数: 1 EXAMPLE 总实例数: 3 最终计数: 3
other extensionsStatic methods cannot access instance members (because there is no `this`). Static methods are suitable for utility functions and factory logic, but do not overuse them—static methods cannot be overridden by subclasses, and overuse will reduce the testability and flexibility of the code.