[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()是正确的答案。但是考虑到这种方法有点冒险和不受欢迎——如果你不小心,它可能会很慢并且在运行时失败。