戳这里了解《Flutter入门与实战》专栏,持续更新、系统学习!
集合是应用程序中最为厂家的数据结构,Dart 一共支持如下四种集合,其中核心的 List
, Map
和 Set
在基础框架中,而 Queue
在 dart:collection
库定义。
本篇介绍集合的最佳实践。
对于核心的集合类List
, Map
和 Set
,由于经常使用,Dart 为这些类提供的内置的语法来快速构建这些集合对象。
// 推荐用法
var points = [];
var addresses = {};
var counts = {};
// 不推荐
var addresses = Map();
var counts = Set();
集合还有一些特殊的用法,比如使用展开操作符(而且同时支持 ? 操作符判断是否为空)将一个集合加入到另一个集合。同时还支持结合 if 和 for 来在控制元素的加入。
// 推荐用法
var arguments = [
...options,
command,
...?modeFlags,
for (var path in filePaths)
if (path.endsWith('.dart'))
path.replaceAll('.dart', '.js')
];
// 不推荐
var arguments = [];
arguments.addAll(options);
arguments.add(command);
if (modeFlags != null) arguments.addAll(modeFlags);
arguments.addAll(filePaths
.where((path) => path.endsWith('.dart'))
.map((path) => path.replaceAll('.dart', '.js')));
上面的推荐用法其实除了展开操作符以外,使用 if 和 for 的并不常见。说实话,个人挺不习惯这种写法的,感觉可读性并不高。
由于集合遵循的是 Iterable
协议,这个协议并不需要集合随时知道它的长度。因此调用.length
的时候,其实相当于是遍历了一遍,执行速度是很低的。这是获取 length
的实现方法:
int get length {
assert(this is! EfficientLengthIterable);
int count = 0;
Iterator it = iterator;
while (it.moveNext()) {
count++;
}
return count;
}
因此,更高效的判断集合是否为空的做法是使用.isEmpty
或 .isNotEmpty
。
bool get isEmpty => !iterator.moveNext();
因此,不要用.length == 0来判断集合是否为空。
// 正确示例
if (lunchBox.isEmpty) return 'so hungry...';
if (words.isNotEmpty) return words.join(' ');
// 错误示例
if (lunchBox.length == 0) return 'so hungry...';
if (!words.isEmpty) return words.join(' ');
在 JS 中,会使用 forEacth 方法来迭代元素,这是因为内置的 for-in 循环和我们想要的不一样。但是在 Dart 中的 for-in 循环是正常的迭代,这样会简化我们的代码。
// 正确示例
for (final person in people) {
...
}
// 错误示例
people.forEach((person) {
...
});
但是如果我们是要对每个元素进行操作的话,那么可以直接将这个操作作为方法传递到 forEacth
中,这样的代码更简洁。
people.forEach(print);
注意 Map
是不可迭代的,因此使用 forEach
是没问题的。
下面是两行对比的代码:
var list = ['a', 'b'];
var copy1 = list.toList();
var copy2 = List.from(list);
print(copy1.runtimeType);
print(copy2.runtimeType);
猜猜打印出来的结果会是什么?
List<String>
List<dynamic>
如果使用 List.from
方法的话,如果不指定泛型类型,会抹除集合的类型,变成 dynamic
!!!因此,除非某些对象需要做这样的类型转换,否则不应该使用 List.from
方法。当然,List.from
也不是没有用,比如数值类型支持强制转换,可以指定类型做强制转换,例如下面剩下的因为都是整数了,因此可以转为 List类型``。
var numbers = [1, 2.3, 4]; // List<num>.
numbers.removeAt(1); // Now it only contains integers.
var ints = List<int>.from(numbers);
如果要从动态集合筛选某个类型的子集,那么应该使用 whereType
方法,而不是使用 where
来过滤。
var list = ['1', '2', 1, 2];
// 正确示例
var intList = list.whereType();
// 错误示例
var intList = list.where((e) => e is int);
这是因为,where
方法返回的仍然是一个 WhereIterable
对象,而不是我们想要的WhereIterable
对象,这意味如果使用 where
还需要做一次强制转换,这并不推荐。
// 错误示例
var list = ['1', '2', 1, 2];
var intList = list.where((e) => e is int).cast();
通常,当在处理迭代对象或 stream
的时候,我们会对其做一系列的操作。之后,我们会指定一个类型的对象。相对于使用 cast()
方法,我们应该使用其他可能存在的转换方式。例如,当我们使用 toList 的时候,可以使用 List
来进行类型转换。
// 正确示例
var stuff = [1, 2];
var ints = List.from(stuff);
// 错误示例
var stuff = [1, 2];
var ints = stuff.toList().cast();
我们也可以使用 map
来将集合转为另一个类型的集合。
// 正确示例
var stuff = [1, 2];
var reciprocals = stuff.map((n) => 1 / n);
// 错误示例
var stuff = [1, 2];
var reciprocals = stuff.map((n) => 1 / n).cast();
当我们没有其他办法进行类型转换时,那么也需要尽可能地避免使用 cast()
做类型转换。这里有几条建议能够避免使用强制转换:
// 正确示例
List singletonList(int value) {
var list = [];
list.add(value);
return list;
}
// 错误示例
List singletonList(int value) {
var list = []; // List.
list.add(value);
return list.cast();
}
// 正确示例
void printEvens(List
List.from()
做转换。如果集合的大部分元素都会被访问到,而且不再需要对转换前的做处理,那么就使用 List.from
来做转换。cast()方法返回的是一个延迟处理的集合,当需要使用元素时才会执行转换。对于转换少量元素而言,这样效率会高。但是,大部分情况下,将对象包装为延迟对象的缺陷更明显。// 正确示例
int median(List objects) {
// 假设我们知道集合只有整数
var ints = List.from(objects);
ints.sort();
return ints[ints.length ~/ 2];
}
// 错误示例
int median(List objects) {
// 假设我们知道集合只有整数
var ints = objects.cast();
ints.sort();
return ints[ints.length ~/ 2];
}
本篇总结了 Dart 语言中使用集合的一些场景的最佳实践,实际上很多要点我们在平时并不会注意 —— 抱着能用就行了的态度。但是,这些内容官方早就有了指引,知道何为正确会有助于我们编写质量更高的代码!