第4章 Dart语言快速入门(下):面向对象编程、异步编程、集合操作与泛型
好,咱们接着聊。上一章我们把Dart的基础语法和函数过了一遍,这一章才是真正上强度的部分。面向对象、异步、集合、泛型——这四个东西,说白了就是你写Flutter时每天都要打交道的。我刚开始从Java转Dart时,觉得这语言怎么这么“怪”,但用顺手了才发现,真香。
4.1 面向对象编程:类、继承与混入
Dart的面向对象,和Java、C#很像,但也有自己的脾气。比如,它没有public、private这些关键字,而是用下划线_来表示私有。
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要求你在初始化列表里调用父类构造函数,而不是在方法体里。这个顺序是固定的,不能乱。
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
async和await是Dart的语法糖,让异步代码看起来像同步代码:
Future<void> loadData() async {
try {
String data = await fetchData();
print(data);
} catch (e) {
print('出错了:$e');
}
}
你看,是不是清爽多了?await会暂停当前函数,直到Future完成。但注意,await只能在async函数里使用。
async标记函数为异步函数,返回值会被自动包装成Futureawait等待一个Future完成,并返回其结果- 使用
try/catch捕获异步异常
我曾经在项目里遇到一个坑:在main()函数里直接用了await,但main()没有标记为async,结果代码直接跳过异步操作往下执行了。嗯,这个错误我犯过两次才记住。
4.3 集合操作:List、Map、Set
Dart的集合类型非常强大,尤其是配合forEach、map、where这些高阶函数,写起来很爽。
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();
我个人特别喜欢map和where,它们让数据处理变得非常声明式。你想想看,如果用传统的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:core、dart:io、dart:math - 第三方库:通过
pubspec.yaml引入,如http、path_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();
好了,这一章就到这里。下一章我们开始真正进入Flutter的世界,从第一个Flutter应用开始。到时候你会发现,今天学的这些东西,全都会用上。