Starting today, we'll dive into Object-Oriented Programming (OOP). OOP is a core concept in modern programming languages, and Dart, being a purely object-oriented language, fully supports this paradigm. In this lesson, we'll start with the fundamental concepts of classes and objects, with special attention to programming conventions brought by Dart's null safety mechanism.
I. Classes and Objects: The Core of OOP
In the object-oriented world, a Class is an abstract description of things, while an Object is a concrete instance of a class.
For example:
- "Car" is a class (it describes common features of cars like having wheels and being able to drive)
- Your red sedan is an object of the "Car" class (a specific, existing instance)
In Dart, everything is an object, including basic types like numbers and strings, all of which inherit from the Object class.
II. Defining Classes and Creating Objects
1. Defining a Class
Use the class keyword to define a class. Class names typically follow PascalCase (each word starts with a capital letter).
// Define a "Person" class
class Person {
// Class members (variables and methods) will be defined here
}
2. Creating Objects
Use the new keyword (which can be omitted) to create an instance (object) of a class:
class Person {
// Empty class (no members yet)
}
void main() {
// Create objects of the Person class
Person p1 = new Person(); // Using the new keyword
Person p2 = Person(); // Omitting new (Dart recommended style)
print(
p1,
); // Output: Instance of 'Person' (indicates this is an instance of the Person class)
print(p2); // Output: Instance of 'Person'
}
III. Member Variables and Methods (Null Safety)
A class consists of member variables (also called properties, describing characteristics of things) and methods (describing behaviors of things). With Dart's null safety mechanism, non-nullable member variables must be initialized, otherwise an error will occur.
1. Defining and Initializing Member Variables
class Person {
// Approach 1: Directly initialize non-nullable variables (recommended for default values)
String name = ""; // Non-nullable string, initialized to empty string
int age = 0; // Non-nullable integer, initialized to 0
// Approach 2: Declare as nullable type (add ?, default value is null)
double? height; // Nullable type, allowed to be null
String? address; // Nullable string
}
void main() {
Person person = Person();
// Access and modify member variables (using . syntax)
person.name = "Zhang San";
person.age = 20;
person.height = 1.75;
person.address = "Beijing City";
print(
"Name: ${person.name}, Age: ${person.age}, Height: ${person.height}m, Address: ${person.address}",
);
// Output: Name: Zhang San, Age: 20, Height: 1.75m, Address: Beijing City
}
2. Member Methods
Methods are functions defined inside a class, used to implement object behaviors.
class Person {
// Member variables (initialized)
String name = "";
int age = 0;
// Member method (greeting)
void sayHello() {
print("Hello everyone, my name is $name, I'm $age years old");
}
// Method with parameters (calculate age in future years)
int getAgeAfterYears(int years) {
return age + years;
}
}
void main() {
Person person = Person();
person.name = "Li Si";
person.age = 22;
// Call methods
person
.sayHello(); // Output: Hello everyone, my name is Li Si, I'm 22 years old
int futureAge = person.getAgeAfterYears(5);
print("Age in 5 years: $futureAge"); // Output: Age in 5 years: 27
}
3. The this Keyword
this represents a reference to the current object, used to distinguish between member variables and local variables (especially when they have the same name):
class Person {
String name = "";
int age = 0;
// Using this to distinguish member variables and parameters (when names are the same)
void setInfo(String name, int age) {
this.name =
name; // this.name refers to member variable, name refers to parameter
this.age =
age; // this.age refers to member variable, age refers to parameter
}
void sayHello() {
// this can be omitted when names are different
print("Hello everyone, my name is $name, I'm $age years old");
}
}
void main() {
Person person = Person();
person.setInfo("Wang Wu", 19); // Pass parameters
person
.sayHello(); // Output: Hello everyone, my name is Wang Wu, I'm 19 years old
}
4. Core Principles of Null Safety
- Non-nullable types (int, String, etc.): Must be initialized (cannot be null), and subsequent assignments cannot be null.
- Nullable types (int?, String?, etc.): Can be null, default value is null, need to check for null when using (can use ?? operator).
class Person {
String? name; // Nullable type
int age = 0; // Non-nullable type
}
void main() {
Person p = Person();
print(p.name ?? "Unknown name"); // Output: Unknown name (since name is null)
print(p.age); // Output: 0 (non-nullable type has initial value)
}
IV. Constructors: Special Methods for Initializing Objects
A constructor is a special method used to initialize member variables (especially non-nullable types) when creating an object. It has the same name as the class and no return value.
1. Default Constructor
If no constructor is explicitly defined, Dart automatically generates a parameterless default constructor, but non-nullable member variables must be initialized in advance:
class Person {
String name = ""; // Non-nullable variable must be initialized
int age = 0; // Non-nullable variable must be initialized
}
void main() {
Person p = Person(); // Call default constructor
print("${p.name}, ${p.age}"); // Output: , 0 (using initial values)
}
2. Custom Constructors
We can define custom constructors to directly initialize member variables when creating objects (recommended for initializing non-nullable variables):
class Person {
String name; // Non-nullable type (initialized in constructor)
int age; // Non-nullable type (initialized in constructor)
double? height; // Nullable type (no forced initialization required)
// Custom constructor (with parameters)
Person(
this.name,
this.age,
); // Shorthand: directly assign parameters to member variables
// Equivalent to full version:
// Person(String name, int age) {
// this.name = name;
// this.age = age;
// }
void sayHello() {
print("Hello everyone, my name is $name, I'm $age years old");
}
}
void main() {
// Must pass parameters when creating object (to initialize non-nullable variables)
Person person = Person("Zhao Liu", 25);
person
.sayHello(); // Output: Hello everyone, my name is Zhao Liu, I'm 25 years old
}
3. Named Constructors
A class can have only one default constructor, but multiple named constructors to provide different initialization methods. Named constructors follow the format ClassName.methodName.
class Person {
String name;
int age;
String? address; // Nullable type
// Default constructor
Person(this.name, this.age);
// Named constructor 1: initialization with address
Person.withAddress(this.name, this.age, this.address);
// Named constructor 2: initialization from JSON data (initializer list syntax)
Person.fromJson(Map<String, dynamic> json)
: name =
json['name'] ??
"", // Initializer list: ensure non-null (?? is null-coalescing operator)
age = json['age'] ?? 0 {
// Constructor body: handle other logic
address = json['address'];
}
void printInfo() {
print("Name: $name, Age: $age, Address: ${address ?? 'Unknown'}");
}
}
void main() {
// Using default constructor
Person p1 = Person("Sun Qi", 30);
p1.printInfo(); // Output: Name: Sun Qi, Age: 30, Address: Unknown
// Using named constructor withAddress
Person p2 = Person.withAddress("Zhou Ba", 28, "Beijing City");
p2.printInfo(); // Output: Name: Zhou Ba, Age: 28, Address: Beijing City
// Using named constructor fromJson
Map<String, dynamic> json = {
'name': "Wu Jiu",
'age': 35,
'address': "Shanghai City",
};
Person p3 = Person.fromJson(json);
p3.printInfo(); // Output: Name: Wu Jiu, Age: 35, Address: Shanghai City
}
Note: In named constructors, the initializer list (the part after :) is used to initialize member variables and must come before the constructor body ({}), primarily for handling initialization of non-nullable variables.
4. Optional Parameters in Constructors
Constructors can also use optional parameters (positional or named) to make initialization more flexible:
class Person {
String name;
int age;
String? gender; // Gender (optional)
// Named constructor: using named optional parameters
Person(this.name, this.age, {this.gender});
void printInfo() {
print("Name: $name, Age: $age, Gender: ${gender ?? 'Not specified'}");
}
}
void main() {
Person p1 = Person("Zheng Shi", 22); // Without specifying gender
p1.printInfo(); // Output: Name: Zheng Shi, Age: 22, Gender: Not specified
Person p2 = Person("Wang Shiyi", 26, gender: "Male"); // With gender specified
p2.printInfo(); // Output: Name: Wang Shiyi, Age: 26, Gender: Male
}
V. Object Memory Model (Simplified Understanding)
To better understand classes and objects, let's briefly examine how they're stored in memory:
- A class is a "template" that exists only once in memory
- Each object is an instance created from the template, each occupying independent memory space
- Objects store values of member variables, while member methods share the class definition
class Person {
String name;
Person(this.name);
void sayHello() => print("Hello, $name");
}
void main() {
Person p1 = Person("A");
Person p2 = Person("B");
// p1 and p2 are two independent objects with names "A" and "B" respectively
// but they share the same definition of the sayHello method
}
Top comments (0)