[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()是正確的答案。但是考慮到這種方法有點冒險和不受歡迎——如果你不小心,它可能會很慢并且在運行時失敗。