finalconst 及wildcard

final

声明为final的变量,只能被赋值一次。 如果尝试修改已经赋值过的final变量,会引起编译错误。

// ch0103_1.dart
void main() {
  final myPet = 'Doggy'; // 1
  myPet = 'Kitty'; // 2
  // Error: Can't assign to the final variable 'myPet'.
  // myPet = 'Kitty'; // 2
  // ^^^^^
}
  1. 声明一个final变量myPet, 并为其赋值;
  2. 尝试修改myPet的值,引起编译错误, 告诉我们不能给final 变量 myPet 赋值。

late final

late final 这两个关键字经常一起出现,这里构建了一个简单的例子:

// ch0103_2.dart
void main() {
  final width = 3.0; // 1
  final height = 4.0; // 1a
  late final double perimeter; // 2
  // ... (Do somehting else)
  perimeter = 2 * (width + height); // 3
  print('width: $width, height: $height, perimeter: $perimeter'); // 4
  // Output: width: 3.0, height: 4.0, perimeter: 14.0
}
  1. 声明final变量 widthheight ,并分别赋值为3.0与4.0,Dart自动推断其数据类型为double (浮点数);
  2. 使用 late final 声明一个变量perimeter,在这里它表示一个矩形的周长,同时显示指定这个变量的数据类型为double,所以这里有3个关键字late final double,它们一起修饰变量perimeter;
  3. 计算变量 perimeter的值,公式为(长+宽)×2;
  4. 使用字符串插值,打印相关变量。

这个例子似乎看不出 late final 有什么特别之处,这里只是让你熟悉一下,在面向对象编程(OOP)的章节还会继续讨论它。

final 的兄弟 const

const关键字用于:

  • 声明一个常量,常量具有编译时(compile-time)不可变性,这里的编译时是与运行时(runtime)相对应的;
  • 创建常量值(constant values);
  • 定义const构造器,该构造器用于创建常量值。

后面两点与OOP有关,这里提前了解下。

声明一个常量

试图修改常量会引起编译错误,例如

// ch0103_3.dart
void main(){
  const score = 50; // 1
  score = 80; // 2
  // Error: Can't assign to the const variable 'score'.
  // score = 80; // 2
  // ^^^^^
}

创建常量值

任何变量都可以有常量值。

// ch0103_4.dart
void main() {
  final luckyNumbers = [5, 6]; // 1
  luckyNumbers.add(8); // 2
  print('luckyNumbers: $luckyNumbers');
  // Output: luckyNumbers: [5, 6, 8]

  const myFriends = ['Alice', 'Bob']; // 3
  myFriends.add('Charlie'); // 4
  // Unhandled exception:
  // Unsupported operation: Cannot add to an unmodifiable list;
}
  1. 声明一个final 列表 luckyNumbers并对其初始化,包含5、6两个数字;
  2. luckyNumbers添加数字8,然后打印出来,luckyNumbers 现在包含了5、6、8三个数字; 这说明了虽然final修饰的luckyNumbers本身(引用)不可更改,但它引用的列表是可以改变的;
  3. 声明一个const列表myFriends,初始值包含"Alice和"Bob"两个字符串;
  4. myFriends添加一个新元素"Charlie",这引起了一个运行时异常,告诉我们不能向不可变列表添加新的元素,可见constfinal具有更强的不可变性。

定义const构造器

构造器是OOP里的概念,const构造用于创建常量值。请看下面这个示例:

// ch0103_5.dart
class Pet { // 1
  final String name; // 1a
  const Pet(this.name); // 2
}

void main() {
  var myPet = Pet('Doggy'); // 3
  myPet = Pet('Little Doggy'); // 3a
  const yourPet = Pet('Kitty'); // 4

  print('myPet: ${myPet.name}, yourPet: ${yourPet.name}');
  // Output: myPet: Little Doggy, yourPet: Kitty
}
  1. class关键字用于声明一个名叫Pet的类 ,类封装了数据和行为(方法)(第4-5章专门讨论OOP); class Pet 有一个final成员变量叫做 name ;
  2. 这是Pet的const构造器;
  3. 通过调用Pet的构造器,得到一个Pet的实例(对象)myPet,随后又将myPet指向另一个Pet对象(3a);
  4. 通过调用Pet的const构造器,创建Pet的常量对象 yourPet,这行代码等于
  const yourPet = const Pet('Kitty'); // 4

在常量上下文中,Pet('Kitty')之前省略了const关键字。

常量对象

const Pet('Kitty') Pet('Kitty')是不一样的,前者创建(实例化)了一个常量对象,而后者创建的是非常量对象(除非是在常量上下文中)。

// ch0103_6.dart
void main() {
  const pet1 = Pet('Rabbit'); // 1
  var pet2 = const Pet('Rabbit'); // 2
  final pet3 = Pet('Rabbit'); // 3

  print('${pet1 == pet2}'); // Output: true
  print('${pet1 == pet3}'); // Ouptput: false
}

class Pet {
  final String name;
  const Pet(this.name); 
}

这里的 == 操作符,用于测试左右两边的操作数是否相等。

Wildcard(_

自 Dart 3.7 开始,以 _ 命名 的变量或参数,是一个通配符(wildcard ),它的值会被丢弃。同一个命名空间里多次声明 _ 不会冲突。例如

//ch0103_7.dart
void main() {
  var _ = 10; // 1
  var _ = 'Hello'; // 2
  print('Hello, Dart! $_'); // 3
  // Error: Undefined name '_'.
  // print('Hello, Dart! $_');
}

上面这段代码比较简单,不过多解释。

通配符出现的地方:

  • 变量声明, 如 var _ = 1;
  • for 循环变量 如 for (var _ in list) {}
  • catch 语句参数, 如
try {
  throw 'something bad';
} catch (_) {
  print('oops');
}
  • 函数参数,如 list.where((_) => true);
  • 范型类型及函数类型参数, 如
class T<_> {}
void genericFunction<_>() {}

for循环、函数、catch语句、范型等概念将在后续章节陆续介绍,这里关于通配符有个印象就行。

更多的示例,可查阅官方文档

参考资料