一、空安全简介
Dart从2.12版本开始强制要求空安全, Dart 空安全(Null Safety)的核心是杜绝运行时空指针异常,这意味着变量默认不能为null 编译器会强制你处理 “变量可能为 null” 的情况。
但在实际开发中,我们经常需要处理可能为null的值,Dart提供了一系列操作符来优雅地处理空值,而 ?.、??、! 等操作符,就是 Dart 提供的 “优雅处理 null” 的语法糖。。
二、核心空安全操作符
1、 ?.:空安全访问符(最常用)
- 作用:如果对象不为 null,就访问其属性 / 方法;如果为 null,直接返回 null(不会崩溃)。
- 使用场景:访问可能为 null 的对象的属性 / 方法。
对比示例:
// 定义一个可为空的对象
String? name = null;
// ❌ 直接访问:编译报错(name 可能为 null)
print(name.length);
// ✅ 使用 ?. 访问:安全,返回 null
print(name?.length); // 输出 null
// ✅ 非 null 时正常访问
name = "张三";
print(name?.length); // 输出 2
进阶场景:链式调用
class User {
String? nickname;
int? age;
}
User? user = null;
// 多层访问:只要其中一环为 null,整体返回 null
print(user?.nickname?.length); // 输出 null
user = User()..nickname = "李四";
print(user?.nickname?.length); // 输出 2
2、 ??:空合并运算符
- 作用:如果左边的值为 null,就返回右边的默认值;如果不为 null,返回左边的值。
- 使用场景:给可为空变量设置 “兜底默认值”。
基础用法:
String? username = null;
// 如果 username 为 null,用 "匿名用户" 替代
String finalName = username ?? "匿名用户";
print(finalName); // 输出 匿名用户
username = "王五";
finalName = username ?? "匿名用户";
print(finalName); // 输出 王五
结合 ?. 使用(高频组合):
User? user = null;
// 先通过 ?. 安全访问,再通过 ?? 设置默认值
String nickname = user?.nickname ?? "未设置昵称";
print(nickname); // 输出 未设置昵称
3、??=:空赋值运算符
- 作用:如果变量为 null,就给它赋值;如果不为 null,保持原值(相当于
变量 = 变量 ?? 新值) - 使用场景:给可为空变量 “懒加载” 赋值(仅当变量为 null 时赋值)。
String? title = null;
// title 为 null,赋值为 "默认标题"
title ??= "默认标题";
print(title); // 输出 默认标题
// title 不为 null,不赋值
title ??= "新标题";
print(title); // 输出 默认标题
4. !:空断言运算符(慎用)
- 作用:告诉编译器 “我确定这个变量不是 null,出问题我负责”,强制将可为空类型转为非空类型。
正确用法(先判空,再断言):
String? content = "Hello";
// 先判空,确保 content 不为 null
if (content != null) {
// 此时断言是安全的
print(content!.length); // 输出 5
}
使用场景:你100% 确定变量不为 null,但编译器无法推断的场景(比如已通过业务逻辑判空)。实例:
// 定义一个可为空的列表(接口返回,业务上确保非空)
List<String>? fruits = ["苹果", "香蕉", "橙子"];
// 业务逻辑:先判空,再取第一个元素
if (fruits != null && fruits.isNotEmpty) {
// ❌ 编译器报错:fruits 是 List<String>? 类型,即使判空,编译器仍认为可能为 null
// String firstFruit = fruits[0];
// ✅ 用 ! 告诉编译器:我确定 fruits 不是 null,你放心用
String firstFruit = fruits![0];
}
为什么报错?
- 你通过
if (fruits != null && fruits.isNotEmpty)确认了列表非空,但 Dart 编译器的静态分析能力有限,无法识别这种 “复合判空逻辑”,依然把fruits当作List<String>?对待。 - 此时
fruits[0]要求fruits是非空的List<String>,类型不匹配,编译失败。
⚠️ 避坑点:除非你能 100% 保证变量非 null,否则不要用 !,优先用 ?./??。
5、 ..:级联运算符(和空安全结合使用)
- 作用:对同一个对象连续调用属性 / 方法(无需重复写对象名),结合
?.可实现 “空安全的级联调用”。 - 使用场景:初始化对象、连续调用多个方法时。
基础级联(非空对象):
不用 ..:重复写对象名,代码冗余
void main() {
// 初始化对象
User user = User();
// 逐个赋值/调用方法(重复写 user.)
user.name = "张三";
user.age = 20;
user.address = "北京市";
user.sayHello();
}
用 ..:一次写对象名,连续操作
void main() {
User user = User()
..name = "张三" // 连续赋值属性
..age = 20
..address = "北京市"
..sayHello(); // 连续调用方法
}
安全写法(用 ?. + ..,对象为 null 不执行,会跳过级联操作)
void main() {
User? user = null; // 对象为 null
// ❌ 崩溃:null 上调用级联运算符
// user
// ..name = "张三"
// ..age = 20;
// ✅ 安全:user 为 null,级联操作不执行,无崩溃
user?. // 先判断 user 是否为 null
..name = "张三"
..age = 20;
// 验证:user 还是 null
print(user?.name); // 输出 null
}
// 非 null 时正常执行
User? user2 = User();
user2?.
..name = "李四"
..age = 25;
print(user2?.name); // 输出 李四
Dart 支持更简洁的 ?..,效果和 ?... 完全一样:
user?..name = "张三"
..age = 20;
三、避坑总结(关键)
| 操作符 | 核心用途 | 注意事项 |
|---|---|---|
?. | 安全访问属性 / 方法 | 避免直接访问可为空对象的成员 |
?? | 设置默认值 | 右边必须是非 null 的同类型值 |
! | 强制非空断言 | 仅在 100% 确定非 null 时用,否则必崩溃 |
??= | 懒加载赋值 | 仅变量为 null 时赋值,适合初始化场景 |
.. | 级联调用 | 结合 ?. 使用,避免空对象级联调用 |
总结
- 优先用
?.+??:这是处理空安全最安全、最常用的组合,能覆盖绝大多数场景 - 慎用
!:只有通过业务逻辑(如if (x != null))确认变量非 null 时,才用!断言 - 避免 “绕过” 空安全:不要为了省事就用
!强行断言,空安全的核心是 “提前处理 null”,而非 “无视 null”
1713

被折叠的 条评论
为什么被折叠?



