Dart官网文档:https://dart.dev/language/variables

重要说明:本博客基于Dart官网文档,但并不是简单的对官网进行翻译,在覆盖核心功能情况下,我会根据个人研发经验,加入自己的一些扩展问题和场景验证。

Dart中的变量

变量是一个对象的引用,引用名就是变量的名称;就算引用是null的变量也一样。

变量有3种定义方式:var关键字,显示类型Object/dynamic类型。

var varName = 'Tom';String strName = 'Tom';Object objName = 'Tom';dynamic dynName = 'Tom';

最佳实战:对于局部变量,优先使用var关键字,其次为显示类型,再次为Object,不推荐使用dynamic(原因:容易引发运行时错误,后续的学习会讲到)。

当使用var关键字定义的变量,Dart会根据值内容,推导出变量的实际类型,如上诉代码varName变量最终为String类型。

Null safety空安全

Dart语言强制健壮空安全。空安全可以防止意外使用null而导致的错误(还记得在Java编程中,在很多对象使用的地方,我们都需要进行null判断,以防止NPE的发生)。Dart在编译期间,就会进行null潜在错误检测分析,从而防止这些错误的发生(注意:并不是所有场景都能检测分析到,后面章节会讲到)。

代码样例:如下代码,变量strName的默认值为null,在其他编程语言如Java语言中,下面的代码是合法的,但是在运行时,当执行strName.length时会引发NPE异常;但是在Dart语言中,以下代码是非法的(无法编译),因此阻止发生NPE等这些潜在的错误。

String strName;print(strName.length)

Dart为了强制执行空安全,有3个关键改变

  1. 如果明确希望某个变量、参数或者其他组件是可以为null的,那么需要在类型后面增加一个?标识:
String? name  // `name`的值可能为`null`, 或者为某个字符串String name   // `name`的值只能是某个字符串,不能为`null`
  1. Dart的变量在使用之前,必须被初始化。可空变量(含有?标识)的默认值为null,即默认初始化为null,因此无需显示的赋值初始化;而非空变量因没有默认值,因此必须显示赋值初始化。
  2. 禁止直接访问可空类型的属性或者表达式方法,包括访问null对象的hashCodetoString()方法(记住:Dart中一起皆对象,null也是),也会引发错误。

Dart空安全通过以上3个关键改变,保证null潜在错误在代码编写阶段就能被前置发现,而不是等到代码运行时。

变量默认值

第2章节中,其实已经提到一点:任何变量在使用之前,必须被初始化;可空变量因为默认值为null,因此可无需显示初始化;非空变量必须显示初始化。

特别注意:非空变量只需要确保它在被使用时已经初始化即可,而不是必须在申明的时候。如下代码,变量lineCount在申明时并未初始化,但是在print被使用时,Dart语言检测到它已经被初始化了,因此如下代码是空安全有效代码。

// 申明:未被初始化int lineCount;if (weLikeToCount) {  lineCount = countLines();} else {  lineCount = 0;}// 使用:Dart检测到已经被初始化,因此是可以使用print(lineCount);

late延迟变量

顶级变量和类变量会延迟初始化,当第一次使用到这些变量时,初始化代码的逻辑才会被执行(即:延迟初始化)。

在大多数情况下,Dart可以检测并分析一个非空变量在使用时已经初始化,但是在有些场景下,Dart无法检测分析或者检测分析会失效。最常见的2种场景:顶级变量实例变量,Dart无法确定它们在被使用时是否已经被初始化了。

如果我们明确一个非空变量在被使用之前能完成初始化(但Dart却无法检测到),可通过增加late关键字,告诉Dart该变量为延迟变量,在被使用之前确保能被初始化。当然,对于late延迟变量,在被使用时却并没有初始化,那么使用它同样会导致运行时错误

late String description;void main() {  description = 'Feijoada!';  print(description);}

late延迟变量主要处理以下2种场景:

  1. 这些变量并不是必须的,同时初始化它们非常耗时或者浪费资源(如网络交互等)。
  2. 在初始化实例变量时,需要用到实例本身。

代码样例:如下代码,当变量temperature在第1次被使用时,才会被调用readThermometer()方法,即该方法仅仅被调用1次。

late String temperature = readThermometer();

final变量和const常量

最佳实践:如果我们明确一个变量在被初始化之后,它的引用值再也不会变化,那么使用final或者const修饰,而不是使用var者显示类型

  1. const变量隐式为final变量
  2. const变量是编译期的常量变量
  3. 实例变量可以是final变量但不能是const变量(原因:实例在运行时才能确定,因此其变量无法作为编译期常量)
final name = 'Bob';final String nickname = 'Bobby';const bar = 1000000;const double atm = 1.01325 * bar;

特别注意:const不仅可以申明如上代码的常量变量,它也可以用于申明创建常量值,也可以用于申明创建常量值的构造器(还记得第2天学习内容:const构造函数?);同时,任何变量,都可以有常量值。

// 1. 常量值var foo = const [];final bar = const [];const baz = []; // 等同:`const []`// 2. 非final/const变量,它的常量值可以更新foo = const [1, 2, 3];

特别注意:常量的定义,可以包含类型检测、类型转换、集合过滤和集合展开操作符。

// `i`是一个`Object`类型常量,它的值是int数字值const Object i = 3;// 1. 类型转换const list = [i as int];// 2. 类型检测和集合过滤const map = {if (i is int) i: 'int'};// 3. 类型检测、集合过滤、集合展开const set = {if (list is List) ...list};

final变量const常量总结:

  1. final变量不可修改,但是它的值是可以更新
  2. const常量不可修改,同时它的值也不可以更新,即它的值是不可变的(immutable)。

void main() {  // 1. final变量  final finalList = [1, 2, 3];  print('1.1 final变量: $finalList');  // 输出:1.1 final变量: [1, 2, 3]  finalList    ..add(4)    ..add(5);  print('1.2 final变量: $finalList');  // 输出:1.2 final变量: [1, 2, 3, 4, 5]  // 2. const常量  const constList = ['a', 'b', 'c'];  print('2.1 const常量: $constList');  // 输出:2.1 const常量: [a, b, c]  constList    ..add('c')    ..add('d');  print('2.2 const常量: $constList');  // 错误:  // Unhandled exception:  // Unsupported operation: Cannot add to an unmodifiable list  // #0      UnmodifiableListMixin.add (dart:_internal/list.dart:114:5)}

我的本博客原地址:https://ntopic.cn/p/2023100101


本文作者:奔跑的蜗牛,转载请注明原文链接:https://ntopic.cn