11.Dart基础-面向对象(一)
1. 声明类
在 Dart 中使用 class
关键字声明类,基本的语法格式为:
class 类名 {
// 类内部的成员
}
例如,声明一个 Person
类如下:
class Person {
}
2. 实例化类
在 Dart 中,使用 new
关键字创建类的实例,基本的语法格式为:
var 变量名 = new 类名();
例如,创建一个 Person
类的实例的代码如下:
var p = new Person();
在 Dart 中创建类实例的时候,new
关键字可以被省略。因此最佳实践是省略 new
关键字来创建类的实例,能让代码更简洁。示例代码如下:
var p = Person();
3. 成员变量
在声明类的时候,可以在 {}
内部声明当前类的成员变量和方法。其中,成员变量用来封装当前类的属性。例如:Person
类中可以封装姓名(name)、年龄(age)、身份证号(idCard)等这些成员变量,用来表示一个人固有的属性。
注意:成员变量又叫做“实例变量”,只能通过类的实例对象进行访问
3.1 普通成员变量
在 class 类中,普通成员变量是可读可写的,但是必须在定义的时候赋初始值,或者把成员变量的类型声明成可为 null 的类型。语法格式为:
class 类名 {
数据类型 成员变量名 = 初始值;
// Or
数据类型? 成员变量名; // 注意:默认值为 null
}
示例代码如下:
class Person {
// 1. 定义成员变量 username,并赋初值为字符串 admin
String username = 'admin';
// 2. 定义可为 null 的成员变量 nickname,由于没有使用 = 赋初值,因此默认值为 null
String? nickname;
}
// ----
void main(List<String> args) {
// a. 创建 Person 类的实例
var p = Person();
// b. 输出 admin
print(p.username);
// c. 输出 null
print(p.nickname);
}
普通成员变量是可读可写的,即:可以读取它的值,也能为其赋新值。示例代码如下:
class Person {
// 定义成员变量 username,并赋初值为字符串 admin
String username = 'admin';
}
// ----
void main(List<String> args) {
// 创建 Person 类的实例
var p = Person();
// 1. 读取 Person 对象 p 的 username 属性值,输出 admin
print(p.username);
// 2. 修改 Person 对象 p 的 username 属性值,赋值为字符串 liulongbin
p.username = 'liulongbin';
// 3. 再次读取属性值,输出字符串 liulongbin
print(p.username);
}
3.2 late 修饰的成员变量
上一小节中,在定义普通成员变量时如果不想赋初始值,则必须把成员变量的数据类型声明成可为 null 的类型。例如:
class Person {
String? nickname;
}
如果既不想为成员变量赋初始值,又不想让它的值可为 null,此时就可以使用 late
来修饰成员变量。例如:
class Person {
late String nickname;
}
注意:被 late
修饰的成员变量表示惰性赋值,必须在访问成员变量之前为其赋值,否则会报错 LateInitializationError: Field 'late修饰的成员变量' has not been initialized
。示例代码如下:
class Person {
// 1. 定义成员变量 username,并赋初值为字符串 admin
String username = 'admin';
// 2. 定义惰性赋值的成员变量 nickname,访问属性值之前必须先赋值
late String nickname;
}
// ----
void main(List<String> args) {
// a. 创建 Person 类的实例
var p = Person();
// b. 由于没有为 p 的 nickname 赋值,所以这里在访问 p.nickname 时会报错:
// Unhandled exception:
// LateInitializationError: Field 'nickname' has not been initialized.
print(p.nickname);
}
正确的用法如下所示:
class Person {
// 1. 定义成员变量 username,并赋初值为字符串 admin
String username = 'admin';
// 2. 定义惰性赋值的成员变量 nickname,访问属性值之前必须先赋值
late String nickname;
}
// ----
void main(List<String> args) {
// a. 创建 Person 类的实例
var p = Person();
// b. 为对象 p 的 nickname 属性赋初始值
p.nickname = 'escook';
// c. 访问 p.nickname 输出字符串 escook
print(p.nickname);
}
注意:使用 late 修饰的成员变量也是可读可写的。如果想声明只读的成员变量,则不能使用 late 关键字修饰。
3.3 final 修饰的成员变量
普通成员变量和使用 late
修饰的成员变量都是可读可写的。如果想声明只读的成员变量,需要使用 final
关键字。例如:
class Person {
// 1. 定义成员变量 username,并赋初值为字符串 admin
String username = 'admin';
// 2. 定义可为 null 的成员变量 nickname,由于没有使用 = 赋初值,因此默认值为 null
late String nickname;
// 3. 定义只读的成员变量 createTime
final DateTime createTime = DateTime.now();
}
根据 02.Dart基础-使用 final 和 const 定义常量 章节的知识点,我们知道 final
表示运行时可以动态生成一个最终的值。所以使用 final
修饰的成员变量,必须在创建实例期间动态提供一个初始值。为 final
成员变量提供初始值有两种方式:
-
定义
final
成员变量时使用=
动态赋初始值class Person { // 创建 Person 实例的 createTime 属性时,使用 = 为其赋初始值 final DateTime createTime = DateTime.now(); }
-
基于构造函数的终值初始化为
final
修饰的成员变量赋初始值class Person { final DateTime createTime; // 基于构造函数的终值初始化,为 final 修饰的成员变量动态赋初始值 // 在后面的“构造函数”章节,会具体讲解“构造函数的终值初始化”的用法 Person(this.createTime); }
注意:使用 final
修饰的成员变量是只读的,不允许重新赋值,例如:
class Person {
// 1. 定义成员变量 username,并赋初值为字符串 admin
String username = 'admin';
// 2. 定义可为 null 的成员变量 nickname,由于没有使用 = 赋初值,因此默认值为 null
late String nickname;
// 3. 定义只读的成员变量 createTime
final DateTime createTime = DateTime.now();
}
// ----
void main(List<String> args) {
// a. 创建 Person 类的实例
var p = Person();
// b. 读取对象 p 的 createTime 属性值,输出当前时间 2022-12-21 14:48:53.596582
print(p.createTime);
// c. 为对象 p 的只读属性 createTime 重新赋值,编译会报错:
// 'createTime' can't be used as a setter because it's final.
p.createTime = DateTime.now();
}
4. Getter 与 Setter
Dart 类中声明的普通成员变量,既能够读取他们的值,又能够为其重新赋值。这是因为 Dart 在内部为普通成员变量生成了隐式的 Getter 和 Setter 访问器。其中:
- 拥有 Getter 访问器的成员变量允许外界读取其值
- 拥有 Setter 访问器的成员变量允许外界为其重新赋值
所以为什么 final
修饰的成员变量无法被重新赋值呢,因为 Dart 在内部只为 final
修饰的成员变量生成了隐式的 Getter 访问器,没有为其生成 Setter 访问器。
注意:普通成员变量和使用
late
修饰的成员变量,都会隐式的生成 Getter 和 Setter 访问器。而final
修饰的成员变量只有 Getter 访问器。
4.1 Getter
Dart 除了会为成员变量生成隐式的 Getter 访问器之外,还允许程序员自定义 Getter 访问器,自定义 Getter 访问器的特点是:
- 访问器的名称不能和已有的成员变量重名
- 访问器必须有返回值类型
- 访问器必须 return 具体的返回值
4.1.1 getter 的完整写法
在 class 类中,声明 getter 访问器的完整语法格式为:
class 类名{
返回值类型 get Getter的名字 {
// 业务逻辑;
return 返回值;
}
}
例如,下面的示例代码中定义了名为 fullName
的 getter 访问器,用来把成员变量 firstName
和 lastName
组装起来,返回完整的姓名:
class Person {
// 名字
String firstName = 'longbin';
// 姓氏
String lastName = 'liu';
// 定义名为 fullName 的 Getter 访问器
// 返回值的类型是 String
String get fullName {
// 必须 return 一个 String 类型的返回值
return '$firstName $lastName';
}
}
// ----
void main(List<String> args) {
var p = Person();
// 输出字符串 'longbin liu'
print(p.fullName);
}
4.1.2 getter 的简化写法
如果 getter 的 {}
中只有一个 return 语句,则可以使用胖箭头的形式对其进行简写,语法格式为:
class 类名 {
返回值类型 get Getter的名字 => 返回值;
}
例如,4.1.1 中的 getter 可以使用胖箭头简写为:
class Person {
// 名字
String firstName = 'longbin';
// 姓氏
String lastName = 'liu';
// 简化后的写法更方便:
String get fullName => '$firstName $lastName';
}
// ----
void main(List<String> args) {
var p = Person();
// 输出字符串 'longbin liu'
print(p.fullName);
}
4.2 Setter
Dart 除了会为非 final
的成员变量隐式的生成 Setter 访问器之外,还允许程序员自定义 Setter 访问器。自定义 Setter 访问器的特点是:
- 访问器的名称不能和已有的成员变量重名
- 不需要定义返回值类型,也不需要 return 任何返回值
- 在形参列表中,要包含一个位置参数,用来表示为当前 setter 赋的新值
4.2.1 setter 的完整写法
在 class 类中,声明 setter 访问器的完整语法格式为:
class 类名 {
set Setter的名字(位置参数) {
// 业务逻辑;
}
}
例如,下面的示例代码中定义了名为 fullName
的 getter 和 setter 访问器。当用户调用 setter 访问器并提供了字符串类型的 full
参数后,在 setter 访问器内部把 full
先进行字符串的分割,再分别为成员变量 firstName
和 lastName
赋值:
class Person {
// 名字
String firstName = 'longbin';
// 姓氏
String lastName = 'liu';
// 定义名为 fullName 的 Getter 访问器
String get fullName => '$firstName $lastName';
// 1. 定义名为 fullName 的 Setter 访问器,形参 full 就是用户通过 = 为当前 Setter 赋的新值
set fullName(String full) {
// 2. 把完整的姓名按照空格进行 split 分割
var parts = full.split(' ');
// 3. 分别给 firstName 和 lastName 赋新值
firstName = parts[0];
lastName = parts[1];
}
}
// ----
void main(List<String> args) {
var p = Person();
// a1. 调用 Getter 访问器,输出 fullName 的值为字符串 'longbin liu'
print(p.fullName);
// a2. firstName 的值默认是 longbin
print(p.firstName);
// a3. lastName 的值默认是 liu
print(p.lastName);
// b. 调用 Setter 访问器为 fullName 赋新值
p.fullName = 'daodao zhai';
// c1. 再次调用 Getter 访问器,输出 fullName 的值为字符串 'daodao zhai'
print(p.fullName);
// c2. firstName 的值变成了 daodao
print(p.firstName);
// c2. lastName 的值变成了 zhai
print(p.lastName);
}
4.2.2 setter 的简化写法
如果 setter 的 {}
中的业务逻辑可以简化为1行代码,则可以使用胖箭头对 setter 访问器进行简写,语法格式为:
class 类名 {
set Setter的名字(位置参数) => 业务代码;
}
例如,下面的代码的第8行,使用胖箭头声明了名为 greeting
的 Setter 访问器:
class Person {
String username = 'liulongbin';
// 1. 定义名为 greeting 的 Getter 访问器
String get greeting => '大家好,我是$username';
// 2. 定义名为 greeting 的 Setter 访问器,把接收到的新值赋值给成员变量 username
set greeting(String name) => username = name;
}
// ----
void main(List<String> args) {
var p = Person();
// a1. 访问成员变量 username 的隐式 Getter 访问器,输出:liulongbin
print(p.username);
// a2. 调用名为 greeting 的 Getter 访问器,输出:大家好,我是liulongbin
print(p.greeting);
// b. 调用名为 greeting 的 Setter 访问器
p.greeting = 'escook';
// c1. 访问成员变量 username 的隐式 Getter 访问器,输出:escook
print(p.username);
// c2. 再次调用名为 greeting 的 Getter 访问器,输出:大家好,我是escook
print(p.greeting);
}
注意:在使用胖箭头对 Getter 和 Setter 进行简写时,一定要注意最后的
;
不能省略,而完整写法的后面是不需要加;
的
5. 实例方法
实例方法又叫做“成员方法”,必须通过类的实例对象才能调用的方法叫做实例方法。在类中声明实例方法的基本语法格式为:
class 类名 {
返回值类型 方法名(形参列表) {
函数体;
}
}
例如,下面的代码为 Person
类声明了名为 show
的实例方法:
class Person {
// 1. 成员变量
String name = 'liulongbin';
int age = 20;
String location = '北京';
// 2. 实例方法,返回值的类型是 String
String getShowMessage() {
// 3. 所以方法体的最后必须 return 一个 String 字符串
return '大家好,我是$name,今年$age岁了,坐标$location';
}
}
// ----
void main(List<String> args) {
// a. 创建 Person 实例
var p = Person();
// b. 基于实例 p 调用实例方法 getShowMessage,返回值是 String,用变量 msg 接收
var msg = p.getShowMessage();
// c. 打印变量的值,输出字符串:大家好,我是liulongbin,今年20岁了,坐标北京
print(msg);
}
5.1 实例方法中的 this
实例方法中的 this
表示当前类的实例对象,用来访问实例上的成员,默认可以省略,示例代码如下:
class Person {
String name = 'liulongbin';
show() {
// 1. this.name 表示“明确访问”当前类实例上的成员变量 name 的值
// 输出:liulongbin
print(this.name);
// 2. name 表示“隐式访问”当前类实例上的成员变量 name 的值
// 输出:liulongbin
print(name);
}
}
// ----
void main(List<String> args) {
var p = Person();
p.show();
}
注意:在上面的代码中,
this.name
和name
最终访问的都是成员变量name
的值,因此它们的作用是等价的
在实例方法中,如果局部变量的名字和成员变量的名字相同,此时必须使用 this
来表示“明确访问”成员变量,否则访问到的是就近作用域中的局部变量。示例代码如下:
class Person {
String name = 'liulongbin';
// 在 show 方法的作用域中,包含局部变量 name
void show(String name) {
// 1. this.name 表示“明确访问”实例上的成员变量 name 的值,因此输出:liulongbin
print(this.name);
// 2. name 表示访问“函数作用域内”形参列表中 name 的参数值,因此输出:escook
print(name);
}
}
// ----
void main(List<String> args) {
var p = Person();
p.show('escook');
}