Dart: Type System

12th December 2020 at 11:15am
Dart

Dart 是纯 OO 型语言,所有类型都是 Object 类型的子类型。

变量存储的是引用

变量都是存的引用(而不是值),即使针对数值类型也是如此。可以用 identical 来判断两个变量是否指向同个对象(对象可以看作变量在内存中的位置):

var a = "1";
var b = "1";
print(identical(a, b));    // true

即使 ab 都存的是引用,但是 Dart 做了一个常见的优化,即对于数值类型或者字符串,如果值一样,它在内存中的位置是一样的。所以上面 identical(a, b) 返回的是 true

var a = "ab";
var b = "a" + "b";
var c = "ab";

print(identical(a, b));    // false
print(identical(a, c));    // true

这是另外一个例子,可以看出 Dart 在编译时的优化还不够好,不能把 b 直接理解成 "ab"。

另外一点是,对于能在编译期就确定好值的 const 对象,编译器也会把值相同的变量指向同一个对象。

class ImmutablePoint {
  final double x, y;
  const ImmutablePoint(this.x, this.y);
}

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

print(identical(a, b));    // true

for 循环中的 index 变量作用域

对于这样一段代码:

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());

如果在 JS 中运行时,会输出 22,不合预期;在 Dart 中运行时,会输出 01,符合预期。原因是 JS 的 var 关键字使得 i 是函数作用域,所以闭包中的 i 仍然绑定到函数作用域中的同一个 i 对象上,而它的值在循环结束后是 2。Dart 则不会有这种问题,i 的作用域只在 for 循环中,在 for 循环外不可访问,而且 Dart 会 “capture the value of index”,将其传到闭包中。

对于 JS,解决方法是使用 ES6 中引入的 let 来替代 var

类型推断

Dart 虽然是个强类型语言,但非常提倡代码上能简略则简略,比如变量的类型如果是编译期能推断出来的,就不用标上类型:

var name = 'Bob';
String name = 'Bob';

上面两种写法的作用是一样的。

如果你想要一个不限定类型的变量,可以用 dynamic。这会使 Dart 认为你访问变量的任何方法或属性都是可以的,它不做编译期的检查,但运行期如果调用了不存在的方法,还是会抛 NoSuchMethodError

dynamic 对比 Object 的差别在于,使用 Object 时会有编译期检查,使用实际类型的方法或属性时需要做类型转换:

Object p1 = Point(1, 2);
print((p1 as Point).x);

dynamic p2 = Point(1, 2);
print(p2.x);

类型判定及类型推断

// throws exception if emp is not a Person or is null
(emp as Person).firstName = 'Bob';

if (emp is Person) {
  // Type check
  emp.firstName = 'Bob';
}

除了 is 关键字,还有一个 is!,跟 is 作用反过来。

默认值

对于 Dart 如果变量没有被赋值,它的值就是 null;对数值类型亦是如此:

int lineCount;
assert(lineCount == null);

null 的相关操作

为空时赋值:

// Assign value to b if b is null; otherwise, b stays the same
b ??= value;

null 条件判断,如果 expr1 不为 null,返回 expr1;否则返回 expr2

expr1 ?? expr2

比如:

String playerName(String name) => name ?? 'Guest';

// 等同于
String playerName(String name) => (name ?? 'Guest');
// 等同于
String playerName(String name) {
  if (name != null) {
    return name;
  } else {
    return 'Guest';
  }
}