[TOC]
Dart开箱即用地支持四种集合类型:列表、映射、队列和集合。以下最佳实践适用于集合。
## 尽可能使用集合字面量。
有两种方法可以创建一个空的可增长列表:\[\]和list()。同样,有三种方法可以创建空的链接散列映射:{}、map()和LinkedHashMap()。
如果您想要创建一个不可增长的列表,或者其他一些自定义集合类型,那么无论如何,都要使用构造函数。否则,使用漂亮的字面量语法。核心库公开了这些构造函数以方便采用,但是惯用Dart代码没有使用它们。
~~~
var points = [];
var addresses = {};
~~~
不要出现以下写法:
~~~
var points = List();
var addresses = Map();
~~~
如果重要的话,你甚至可以为它们提供一个类型参数。
~~~
var points = <Point>[];
var addresses = <String, Address>{};
~~~
不要出现以下写法:
~~~
var points = List<Point>();
var addresses = Map<String, Address>();
~~~
注意,这并不适用于这些类的命名构造函数。List.from()、Map.fromIterable()和friends都有它们的用途。同样,如果您正在传递一个size to List()来创建一个不可增长的size,那么使用它是有意义的。
## 不要使用.length查看集合是否为空。
迭代器不要求集合知道它的长度或能提供它。调用.length来查看集合中是否包含任何内容会非常缓慢。
相反,还有一些更快、更易读的getter:. isempty和. isnotempty。使用不需要你否定结果的方法。
~~~
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(' ');
~~~
## 考虑使用高阶方法转换序列。
如果您有一个集合,并且希望从中生成一个新的修改后的集合,那么使用.map()、.where()和Iterable上的其他方便的方法通常更短,也更具有声明性。
使用这些而不是for循环,可以清楚地表明您的意图是生成一个新的序列,而不是产生副作用。
~~~
var aquaticNames = animals
.where((animal) => animal.isAquatic)
.map((animal) => animal.name);
~~~
与此同时,这可能会更有效。如果您正在链接或嵌套许多高阶方法,那么编写一段命令式代码可能会更清楚。
## 避免使用带有函数字面量的Iterable.forEach()。
forEach()函数在JavaScript中广泛使用,因为内建的for-in循环没有完成您通常想要的工作。在Dart中,如果你想遍历一个序列,惯用的方法是使用循环。
~~~
for (var person in people) {
...
}
~~~
不要出现以下写法:
~~~
people.forEach((person) {
...
});
~~~
例外情况是,如果您想要做的只是调用某个已经存在的函数,并将每个元素作为参数。在这种情况下,forEach()非常方便。
~~~
people.forEach(print);
~~~
## 不要使用List.from(),除非您打算更改结果的类型。
给定一个迭代,有两种明显的方法可以生成包含相同元素的新列表:
~~~
var copy1 = iterable.toList();
var copy2 = List.from(iterable);
~~~
明显的区别是第一个比较短。重要的区别是第一个保留了原始对象的类型参数:
~~~
// Creates a List<int>:
var iterable = [1, 2, 3];
// Prints "List<int>":
print(iterable.toList().runtimeType);
~~~
不要出现以下写法:
~~~
// Creates a List<int>:
var iterable = [1, 2, 3];
// Prints "List<dynamic>":
print(List.from(iterable).runtimeType);
~~~
如果您想改变类型,那么调用List.from()是很有用的:
~~~
var numbers = [1, 2.3, 4]; // List<num>.
numbers.removeAt(1); // Now it only contains integers.
var ints = List<int>.from(numbers);
~~~
但是,如果您的目标只是复制迭代并保留其原始类型,或者您不关心类型,那么使用toList()。
## 使用whereType()按类型筛选集合。
假设你有一个包含多个对象的列表,你想从列表中得到整数。您可以这样使用where():
不推荐以下写法:
~~~
var objects = [1, "a", 2, "b", 3];
var ints = objects.where((e) => e is int);
~~~
这是冗长的,但更糟糕的是,它返回一个iterable,其类型可能不是您想要的。在这里的示例中,它返回一个Iterable,尽管您可能想要一个Iterable,因为您要过滤它的类型是它。
有时您会看到通过添加cast()来“纠正(corrects)”上述错误的代码:
不推荐以下写法:
~~~
var objects = [1, "a", 2, "b", 3];
var ints = objects.where((e) => e is int).cast<int>();
~~~
这是冗长的,会创建两个包装器,带有两个间接层和冗余运行时检查。幸运的是,核心库有这个用例的whereType()方法:
~~~
var objects = [1, "a", 2, "b", 3];
var ints = objects.whereType<int>();
~~~
使用whereType()非常简洁,生成所需类型的迭代,并且没有不必要的包装级别。
## 当相似的操作可以完成时,不要使用cast()。
通常,当您处理一个可迭代或流时,您会对它执行几个转换。最后,您希望生成具有特定类型参数的对象。与其修改对cast()的调用,不如看看现有的转换是否可以改变类型。
如果您已经调用toList(),那么可以用调用List.from()替换它,其中T是您想要的结果列表的类型。
~~~
var stuff = <dynamic>[1, 2];
var ints = List<int>.from(stuff);
~~~
不推荐以下写法:
~~~
var stuff = <dynamic>[1, 2];
var ints = stuff.toList().cast<int>();
~~~
如果您正在调用map(),请给它一个显式类型参数,以便它生成所需类型的迭代。类型推断通常根据传递给map()的函数为您选择正确的类型,但有时需要显式。
~~~
var stuff = <dynamic>[1, 2];
var reciprocals = stuff.map<double>((n) => 1 / n);
~~~
不推荐以下写法:
~~~
var stuff = <dynamic>[1, 2];
var reciprocals = stuff.map((n) => 1 / n).cast<double>();
~~~
## 避免使用cast()。
这是对前一个规则的更温和的概括。有时,您无法使用附近的操作来修复某些对象的类型。即使这样,尽可能避免使用cast()来“更改(change)”集合的类型。
选择以下任何一种:
* 用正确的类型创建它。更改第一次创建集合的地方的代码,使其具有正确的类型。
* 在访问时强制转换元素。如果您立即遍历集合,则在迭代中强制转换每个元素。
* 积极的使用List.from()代替cast()。如果您最终将访问集合中的大多数元素,并且不需要由原始活动对象支持对象,那么可以使用List.from()对其进行转换。
cast()方法返回一个惰性集合,该集合检查每个操作的元素类型。如果您只对几个元素执行一些操作,那么惰性可能是好的。但是在许多情况下,延迟验证和包装的开销超过了好处。
下面是一个使用正确类型创建它的示例:
~~~
List<int> singletonList(int value) {
var list = <int>[];
list.add(value);
return list;
}
~~~
不推荐以下写法:
~~~
List<int> singletonList(int value) {
var list = []; // List<dynamic>.
list.add(value);
return list.cast<int>();
}
~~~
下面是对每个元素的访问:
~~~
void printEvens(List<Object> objects) {
// We happen to know the list only contains ints.
for (var n in objects) {
if ((n as int).isEven) print(n);
}
}
~~~
不推荐以下写法:
~~~
void printEvens(List<Object> objects) {
// We happen to know the list only contains ints.
for (var n in objects.cast<int>()) {
if (n.isEven) print(n);
}
}
~~~
下面是使用List.from()进行强制转换:
~~~
int median(List<Object> objects) {
// We happen to know the list only contains ints.
var ints = List<int>.from(objects);
ints.sort();
return ints[ints.length ~/ 2];
}
~~~
不推荐以下写法:
~~~
int median(List<Object> objects) {
// We happen to know the list only contains ints.
var ints = objects.cast<int>();
ints.sort();
return ints[ints.length ~/ 2];
}
~~~
当然,这些替代方法并不总是有效,有时cast()是正确的答案。但是考虑到这种方法有点冒险和不受欢迎——如果你不小心,它可能会很慢并且在运行时失败。