Dart

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 成员变量提供初始值有两种方式:

  1. 定义 final 成员变量时使用 = 动态赋初始值

    class Person {
     // 创建 Person 实例的 createTime 属性时,使用 = 为其赋初始值
     final DateTime createTime = DateTime.now();
    }
  2. 基于构造函数的终值初始化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 在内部为普通成员变量生成了隐式的 GetterSetter 访问器。其中:

  1. 拥有 Getter 访问器的成员变量允许外界读取其值
  2. 拥有 Setter 访问器的成员变量允许外界为其重新赋值

所以为什么 final 修饰的成员变量无法被重新赋值呢,因为 Dart 在内部只为 final 修饰的成员变量生成了隐式的 Getter 访问器,没有为其生成 Setter 访问器。

注意:普通成员变量和使用 late 修饰的成员变量,都会隐式的生成 Getter 和 Setter 访问器。而 final 修饰的成员变量只有 Getter 访问器。

4.1 Getter

Dart 除了会为成员变量生成隐式的 Getter 访问器之外,还允许程序员自定义 Getter 访问器,自定义 Getter 访问器的特点是:

  1. 访问器的名称不能和已有的成员变量重名
  2. 访问器必须有返回值类型
  3. 访问器必须 return 具体的返回值

4.1.1 getter 的完整写法

在 class 类中,声明 getter 访问器的完整语法格式为:

class 类名{
  返回值类型 get Getter的名字 {
    // 业务逻辑;
    return 返回值;
  }
}

例如,下面的示例代码中定义了名为 fullName 的 getter 访问器,用来把成员变量 firstNamelastName 组装起来,返回完整的姓名:

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 访问器的特点是:

  1. 访问器的名称不能和已有的成员变量重名
  2. 不需要定义返回值类型,也不需要 return 任何返回值
  3. 在形参列表中,要包含一个位置参数,用来表示为当前 setter 赋的新值

4.2.1 setter 的完整写法

在 class 类中,声明 setter 访问器的完整语法格式为:

class 类名 {
  set Setter的名字(位置参数) {
    // 业务逻辑;
  }
}

例如,下面的示例代码中定义了名为 fullName 的 getter 和 setter 访问器。当用户调用 setter 访问器并提供了字符串类型的 full 参数后,在 setter 访问器内部把 full 先进行字符串的分割,再分别为成员变量 firstNamelastName 赋值:

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.namename 最终访问的都是成员变量 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');
}

一个不再讲课の前端程序员。

留言

您的电子邮箱地址不会被公开。 必填项已用*标注