第4章 Dart语言快速入门(下):面向对象编程、异步编程、集合操作与泛型

好,咱们接着聊。上一章我们把Dart的基础语法和函数过了一遍,这一章才是真正上强度的部分。面向对象、异步、集合、泛型——这四个东西,说白了就是你写Flutter时每天都要打交道的。我刚开始从Java转Dart时,觉得这语言怎么这么“怪”,但用顺手了才发现,真香。

4.1 面向对象编程:类、继承与混入

Dart的面向对象,和Java、C#很像,但也有自己的脾气。比如,它没有publicprivate这些关键字,而是用下划线_来表示私有。

4.1.1 类的定义与构造函数

先看一个最简单的类:

class Person {
  String name;
  int age;

  // 构造函数
  Person(this.name, this.age);

  void sayHello() {
    print('你好,我是$name,今年$age岁');
  }
}

这里有个小细节:Person(this.name, this.age)是Dart的语法糖,相当于Java里写this.name = name; this.age = age;。我个人习惯用这种写法,省事。

构造函数还有命名构造函数,这个在Flutter里很常见:

class Person {
  String name;
  int age;

  Person(this.name, this.age);

  // 命名构造函数
  Person.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        age = json['age'];
}

嗯,这里要注意:命名构造函数后面的冒号是初始化列表,用来在构造函数体执行前给成员变量赋值。我在项目中遇到过,有人把初始化列表和构造函数体搞混,结果变量还没初始化就用了,直接报错。

4.1.2 继承

Dart是单继承,和Java一样。但它的继承写法更简洁:

class Student extends Person {
  String school;

  Student(String name, int age, this.school) : super(name, age);

  @override
  void sayHello() {
    print('我是学生,在$school上学');
  }
}

注意super的调用位置。Dart要求你在初始化列表里调用父类构造函数,而不是在方法体里。这个顺序是固定的,不能乱。

注意: Dart不支持多继承。如果你需要从多个类中获取功能,请使用混入(mixin)。

4.1.3 混入(Mixin)

混入是Dart的一大特色。它让你可以在不继承的情况下复用代码。说白了,就是“拿来用”。

mixin Flyable {
  void fly() {
    print('我能飞!');
  }
}

class Bird with Flyable {}

void main() {
  var bird = Bird();
  bird.fly(); // 输出:我能飞!
}

混入可以叠加使用:

mixin Swimmable {
  void swim() {
    print('我能游泳!');
  }
}

class Duck with Flyable, Swimmable {}

void main() {
  var duck = Duck();
  duck.fly();
  duck.swim();
}

我曾经在项目里用混入来抽取日志功能、权限检查等通用逻辑,比继承灵活多了。你想想看,如果每个需要日志的类都去继承一个Logger类,那继承链得多乱?

小技巧: 混入的with关键字后面可以跟多个混入类,用逗号分隔。但要注意,混入不能有构造函数。

4.2 异步编程:Future、async/await

Flutter里到处都是异步操作:网络请求、文件读写、数据库查询……如果你不会异步编程,那基本没法干活。

4.2.1 Future

Future代表一个将来会完成的任务。它有三种状态:未完成、已完成(成功)、已完成(失败)。

Future<String> fetchData() {
  return Future.delayed(Duration(seconds: 2), () {
    return '数据加载完成';
  });
}

void main() {
  fetchData().then((data) {
    print(data);
  }).catchError((error) {
    print('出错了:$error');
  });
}

这里.then()处理成功结果,.catchError()处理异常。但说实话,这种链式调用写多了,代码会变得很难看。所以我更推荐用async/await

4.2.2 async/await

asyncawait是Dart的语法糖,让异步代码看起来像同步代码:

Future<void> loadData() async {
  try {
    String data = await fetchData();
    print(data);
  } catch (e) {
    print('出错了:$e');
  }
}

你看,是不是清爽多了?await会暂停当前函数,直到Future完成。但注意,await只能在async函数里使用。

核心要点:
  • async标记函数为异步函数,返回值会被自动包装成Future
  • await等待一个Future完成,并返回其结果
  • 使用try/catch捕获异步异常

我曾经在项目里遇到一个坑:在main()函数里直接用了await,但main()没有标记为async,结果代码直接跳过异步操作往下执行了。嗯,这个错误我犯过两次才记住。

4.3 集合操作:List、Map、Set

Dart的集合类型非常强大,尤其是配合forEachmapwhere这些高阶函数,写起来很爽。

4.3.1 List(列表)

List<String> fruits = ['苹果', '香蕉', '橘子'];

// 添加元素
fruits.add('葡萄');

// 遍历
fruits.forEach((fruit) {
  print(fruit);
});

// 映射(转换)
List<String> upperFruits = fruits.map((f) => f.toUpperCase()).toList();

// 过滤
List<String> longFruits = fruits.where((f) => f.length > 1).toList();

我个人特别喜欢mapwhere,它们让数据处理变得非常声明式。你想想看,如果用传统的for循环,得写多少行?

4.3.2 Map(映射)

Map<String, int> scores = {
  '张三': 90,
  '李四': 85,
  '王五': 95,
};

// 遍历
scores.forEach((key, value) {
  print('$key: $value');
});

// 获取所有键
List<String> names = scores.keys.toList();

// 获取所有值
List<int> values = scores.values.toList();

4.3.3 Set(集合)

Set的特点是元素唯一,没有重复:

Set<int> numbers = {1, 2, 3, 3, 4}; // 实际只有 {1, 2, 3, 4}

// 添加元素
numbers.add(5);

// 检查是否存在
bool hasThree = numbers.contains(3); // true

我在项目中经常用Set来做去重操作。比如从API获取的用户ID列表,可能有重复,转成Set就搞定了。

小技巧: 集合操作可以链式调用。比如:list.map(...).where(...).toList(),非常优雅。

4.4 泛型与库的使用

4.4.1 泛型

泛型让你可以编写类型安全的代码,同时保持灵活性。说白了,就是“写一次,多种类型都能用”。

class Box<T> {
  T value;

  Box(this.value);

  T getValue() {
    return value;
  }
}

void main() {
  var intBox = Box<int>(42);
  var stringBox = Box<String>('Hello');

  print(intBox.getValue()); // 42
  print(stringBox.getValue()); // Hello
}

泛型还可以限制类型范围:

class NumberBox<T extends num> {
  T value;

  NumberBox(this.value);
}

// 只能传入 int 或 double
var box = NumberBox<int>(10);

我曾经在项目里写了一个通用的缓存类,用泛型支持缓存任意类型的数据。如果没有泛型,我得为每种类型写一个缓存类,那代码量……想想都可怕。

4.4.2 库的使用

Dart通过import来引入库。有三种类型:

  • 内置库:如dart:coredart:iodart:math
  • 第三方库:通过pubspec.yaml引入,如httppath_provider
  • 自定义库:自己写的文件
// 引入内置库
import 'dart:math';

// 引入第三方库
import 'package:http/http.dart' as http;

// 引入自定义库
import 'utils/helper.dart';

// 只引入部分内容
import 'utils/helper.dart' show calculateSum;

// 隐藏某些内容
import 'utils/helper.dart' hide calculateSum;

这里有个小细节:as关键字可以给库起别名,避免命名冲突。比如两个库都有Logger类,你可以用as区分:

import 'package:logger/logger.dart' as SimpleLogger;
import 'package:complex_logger/logger.dart' as ComplexLogger;

var simple = SimpleLogger.Logger();
var complex = ComplexLogger.Logger();
总结一下: 这一章的内容,是Dart语言的核心中的核心。面向对象让你组织代码,异步编程让你处理耗时操作,集合操作让你高效处理数据,泛型让你写出更通用的代码。这四个东西,在Flutter开发中几乎无处不在。

好了,这一章就到这里。下一章我们开始真正进入Flutter的世界,从第一个Flutter应用开始。到时候你会发现,今天学的这些东西,全都会用上。