一、空安全迁移的常见误区

迁移到Dart空安全时,最常见的错误就是没有正确理解?!late的用法。很多开发者习惯性地认为变量默认是可空的,但实际上在空安全模式下,变量默认是非空的。

// 错误示例:未声明可空性
String name; // 在空安全模式下会报错,因为默认是非空类型

// 正确示例:明确声明可空性
String? name; // 使用`?`表示该变量可以为null

另一个常见误区是滥用!操作符(非空断言)。虽然它能绕过编译检查,但如果运行时变量确实是null,就会抛出异常。

void printLength(String? text) {
  print(text!.length); // 如果text是null,运行时崩溃!
}

// 更安全的做法:
void safePrintLength(String? text) {
  if (text != null) {
    print(text.length); // 安全访问
  }
}

二、如何处理迁移中的类型推断问题

Dart的类型推断在空安全下变得更严格,尤其是泛型场景。比如List<int?>List<int>是不同的类型,混用会导致错误。

// 错误示例:类型不匹配
List<int> numbers = [1, 2, null]; // 编译错误,因为null不能赋值给int

// 正确示例:明确定义可空列表
List<int?> nullableNumbers = [1, 2, null]; // 合法

如果使用dynamicObject,要注意它们与可空类型的区别:

dynamic obj = null;
print(obj?.toString()); // 使用安全调用
Object? obj2 = null;
print(obj2?.toString()); // 同样需要安全调用

三、late关键字的正确使用姿势

late适合那些你确定在使用前一定会初始化的变量,但滥用会导致运行时错误。

class Profile {
  late String username; // 声明为late,延迟初始化

  void initialize(String name) {
    username = name; // 必须在使用前初始化
  }

  void printName() {
    print(username); // 如果未初始化,抛出LateInitializationError
  }
}

late在懒加载场景很有用:

class HeavyService {
  late final ExpensiveObject _cache = ExpensiveObject(); // 首次访问时初始化

  void useCache() {
    print(_cache); // 在这里才初始化
  }
}

四、集合与可空性的交互

集合操作(如mapwhere)需要特别注意可空性。例如:

List<int?> nums = [1, 2, null, 4];
var filtered = nums.where((n) => n != null).toList(); // 类型仍是List<int?>
var casted = filtered.cast<int>(); // 显式转换

如果使用collection包的nonNulls方法会更简洁:

import 'package:collection/collection.dart';

void main() {
  var nums = [1, null, 3];
  var nonNullList = nums.nonNulls.toList(); // 自动过滤null并推断为List<int>
}

五、异步代码中的空安全陷阱

Future和Stream也可能涉及可空类型。例如:

Future<String?> fetchName() async {
  return null; // 允许返回null
}

void main() async {
  String? name = await fetchName();
  print(name?.length ?? 'Unknown'); // 安全处理
}

Stream的错误处理示例:

Stream<int?> generateNumbers() async* {
  yield 1;
  yield null;
}

void main() {
  generateNumbers().listen((n) {
    print(n?.toString() ?? 'Null value'); // 必须处理null
  });
}

六、总结与最佳实践

  1. 优先使用?:除非确定变量非空,否则声明为可空类型。
  2. 慎用!:仅在确保非空时使用,否则用条件判断。
  3. 合理使用late:适合延迟初始化或依赖注入的场景。
  4. 测试覆盖:迁移后务必增加null相关测试用例。

通过逐步调整和严格测试,可以平稳过渡到空安全,享受编译时检查带来的可靠性提升。