Dart 速查表 codelab
字串插值
可空的變數
避空運算子
條件屬性存取
集合字面量 (Collection literals)
箭頭語法
級聯
- Getters and setters
可選位置引數
命名引數
例外
在構造方法中使用 this
- Initializer lists
命名構造方法
工廠構造方法
重新導向構造方法
Const 構造方法
下一步是什麼?
Dart 語言旨在讓從其他程式語言轉來的開發者們能夠輕鬆學習,但也有它的獨特之處。本篇將基於谷歌工程師編寫的 Dart 語言速查表 為你介紹一些最重要的語言特性。
在這篇 codelab 中的嵌入式編輯器已經完成了部分程式碼片段。你可以在這些編輯器上將程式碼補充完整,然後點選 Run (執行) 按鈕進行測試。如果你需要幫助,請點選 Hint (提示) 按鈕。要執行程式碼格式化 (dart format),點選 Format (格式化) 按鈕,Reset (重置) 按鈕將會清除你的操作,並把編輯器恢復到初始狀態。
字串插值
為了將表示式的值放在字串中,請使用 ${expression}
。若表示式為單個識別符號,則可以省略 {}
。
下面是一些使用字串插值的例子:
'${3 + 2}' |
'5' |
|
'${"word".toUpperCase()}' |
'WORD' |
|
'$myObject' |
myObject.toString() 的值 |
Code example
程式碼範例
下面的方法接收兩個整型變數作為引數,然後讓它返回一個包含以空格分隔的整數的字串。例如,stringify(2, 3)
應該返回 '2 3'
。
{$ begin main.dart $}
String stringify(int x, int y) {
TODO('Return a formatted string here');
}
{$ end main.dart $}
{$ begin solution.dart $}
String stringify(int x, int y) {
return '$x $y';
}
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
try {
final str = stringify(2, 3);
if (str == '2 3') {
_result(true);
} else if (str == '23') {
_result(false, ['Test failed. It looks like you forgot the space!']);
} else {
_result(false, ['That\'s not quite right. Keep trying!']);
}
} on UnimplementedError {
_result(false, ['Test failed. Did you implement the method?']);
} catch (e) {
_result(false, ['Tried calling stringify(2, 3), but received an exception: ${e.runtimeType}']);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
Both x and y are simple values,
and Dart's string interpolation will handle
converting them to string representations.
All you need to do is use the $ operator to
reference them inside single quotes, with a space in between.
{$ end hint.txt $}
可空的變數
Dart 要求使用健全的空安全,這意味著除非變數顯式宣告為可空型別,否則它們將不能為空。換句話說,型別預設是不可為空的。
舉個例子,下面的程式碼在空安全下是有錯誤的,因為 int
型別的變數不能為 null
:
int a = null; // INVALID.
你可以透過在型別後新增 ?
來表示該型別可空:
int? a = null; // Valid.
在所有 Dart 版本中,null
在未初始化的變數裡都是預設值,所以你可以這樣簡化你的程式碼:
int? a; // The initial value of a is null.
想了解更多有關 Dart 的空安全的內容,請閱讀 健全的空安全。
Code example
程式碼範例
試著定義以下兩種變數:
-
一個可空的
String
,名為name
,值為'Jane'
。 -
一個可空的
String
,名為address
,值為null
。
可以忽略以下程式碼一開始在 DartPad 中的錯誤。
{$ begin main.dart $}
// Declare the two variables here
{$ end main.dart $}
{$ begin solution.dart $}
String? name = 'Jane';
String? address;
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
try {
if (name == 'Jane' && address == null) {
// verify that "name" is nullable
name = null;
_result(true);
} else {
_result(false, ['That\'s not quite right. Keep trying!']);
}
} catch (e) {
_result(false, ['Tried calling stringify(2, 3), but received an exception: ${e.runtimeType}']);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
Declare the two variables as "String" followed by "?".
Then, assign "Jane" to "name"
and leave "address" uninitialized.
{$ end hint.txt $}
避空運算子
Dart 提供了一系列方便的運算子用於處理可能會為空值的變數。其中一個是 ??=
賦值運算子,僅當該變數為空值時才為其賦值:
int? a; // = null
a ??= 3;
print(a); // <-- Prints 3.
a ??= 5;
print(a); // <-- Still prints 3.
另外一個避空運算子是 ??
,如果該運算子左邊的表示式返回的是空值,則會計算並返回右邊的表示式。
print(1 ?? 3); // <-- Prints 1.
print(null ?? 12); // <-- Prints 12.
Code example
程式碼範例
嘗試在下面的程式碼片段中交替使用 ??=
和 ??
運運算元,實現期望的需求。
可以忽略以下程式碼一開始在 DartPad 中的錯誤。
{$ begin main.dart $}
String? foo = 'a string';
String? bar; // = null
// Substitute an operator that makes 'a string' be assigned to baz.
String? baz = foo /* TODO */ bar;
void updateSomeVars() {
// Substitute an operator that makes 'a string' be assigned to bar.
bar /* TODO */ 'a string';
}
{$ end main.dart $}
{$ begin solution.dart $}
String? foo = 'a string';
String? bar; // = null
// Substitute an operator that makes 'a string' be assigned to baz.
String? baz = foo ?? bar;
void updateSomeVars() {
// Substitute an operator that makes 'a string' be assigned to bar.
bar ??= 'a string';
}
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
final errs = <String>[];
try {
updateSomeVars();
if (foo != 'a string') {
errs.add('Looks like foo somehow ended up with the wrong value.');
} else if (bar != 'a string') {
errs.add('Looks like bar ended up with the wrong value.');
} else if (baz != 'a string') {
errs.add('Looks like baz ended up with the wrong value.');
}
} catch (e) {
errs.add('Tried calling updateSomeVars and received an exception: ${e.runtimeType}.');
}
if (errs.isEmpty) {
_result(true);
} else {
_result(false, errs);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
All you need to do in this exercise is
replace the TODO comments with either ?? or ??=.
Read the codelab text to make sure you understand both,
and then give it a try.
{$ end hint.txt $}
條件屬性存取
要保護可能會為空的屬性的正常存取,請在點(.
)之前加一個問號(?
)。
myObject?.someProperty
上述程式碼等效於以下內容:
(myObject != null) ? myObject.someProperty : null
你可以在一個表示式中連續使用多個 ?.
:
myObject?.someProperty?.someMethod()
如果 myObject
或 myObject.someProperty
為空,則前面的程式碼返回 null(並不再呼叫 someMethod
)。
Code example
程式碼範例
嘗試使用條件屬性存取來完成下面的程式碼片段。
{$ begin main.dart $}
// This method should return the uppercase version of `str`
// or null if `str` is null.
String? upperCaseIt(String? str) {
// Try conditionally accessing the `toUpperCase` method here.
}
{$ end main.dart $}
{$ begin solution.dart $}
// This method should return the uppercase version of `str`
// or null if `str` is null.
String? upperCaseIt(String? str) {
return str?.toUpperCase();
}
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
final errs = <String>[];
try {
String? one = upperCaseIt(null);
if (one != null) {
errs.add('Looks like you\'re not returning null for null inputs.');
}
} catch (e) {
errs.add('Tried calling upperCaseIt(null) and got an exception: ${e.runtimeType}.');
}
try {
String? two = upperCaseIt('asdf');
if (two == null) {
errs.add('Looks like you\'re returning null even when str has a value.');
} else if (two != 'ASDF') {
errs.add('Tried upperCaseIt(\'asdf\'), but didn\'t get \'ASDF\' in response.');
}
} catch (e) {
errs.add('Tried calling upperCaseIt(\'asdf\') and got an exception: ${e.runtimeType}.');
}
if (errs.isEmpty) {
_result(true);
} else {
_result(false, errs);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
If this exercise wanted you to conditionally lowercase a string,
you could do it like this: str?.toLowerCase()
{$ end hint.txt $}
集合字面量 (Collection literals)
Dart 內建了對 list、map 以及 set 的支援。你可以透過字面量直接建立它們:
final aListOfStrings = ['one', 'two', 'three'];
final aSetOfStrings = {'one', 'two', 'three'};
final aMapOfStringsToInts = {
'one': 1,
'two': 2,
'three': 3,
};
Dart 的型別推斷可以自動幫你分配這些變數的型別。在這個例子中,推斷型別是 List<String>
、Set<String>
和 Map<String, int>
。
你也可以手動指定型別:
final aListOfInts = <int>[];
final aSetOfInts = <int>{};
final aMapOfIntToDouble = <int, double>{};
在使用子類別型的內容初始化列表,但仍希望列表為 List <BaseType>
時,指定其型別很方便:
final aListOfBaseType = <BaseType>[SubType(), SubType()];
Code example
程式碼範例
嘗試將以下變數設定為指定的值。替換當前的 null 值。
{$ begin main.dart $}
// Assign this a list containing 'a', 'b', and 'c' in that order:
final aListOfStrings = null;
// Assign this a set containing 3, 4, and 5:
final aSetOfInts = null;
// Assign this a map of String to int so that aMapOfStringsToInts['myKey'] returns 12:
final aMapOfStringsToInts = null;
// Assign this an empty List<double>:
final anEmptyListOfDouble = null;
// Assign this an empty Set<String>:
final anEmptySetOfString = null;
// Assign this an empty Map of double to int:
final anEmptyMapOfDoublesToInts = null;
{$ end main.dart $}
{$ begin solution.dart $}
// Assign this a list containing 'a', 'b', and 'c' in that order:
final aListOfStrings = ['a', 'b', 'c'];
// Assign this a set containing 3, 4, and 5:
final aSetOfInts = {3, 4, 5};
// Assign this a map of String to int so that aMapOfStringsToInts['myKey'] returns 12:
final aMapOfStringsToInts = {'myKey': 12};
// Assign this an empty List<double>:
final anEmptyListOfDouble = <double>[];
// Assign this an empty Set<String>:
final anEmptySetOfString = <String>{};
// Assign this an empty Map of double to int:
final anEmptyMapOfDoublesToInts = <double, int>{};
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
final errs = <String>[];
if (aListOfStrings is! List<String>) {
errs.add('aListOfStrings should have the type List<String>.');
} else if (aListOfStrings.length != 3) {
errs.add('aListOfStrings has ${aListOfStrings.length} items in it, rather than the expected 3.');
} else if (aListOfStrings[0] != 'a' || aListOfStrings[1] != 'b' || aListOfStrings[2] != 'c') {
errs.add('aListOfStrings doesn\'t contain the correct values (\'a\', \'b\', \'c\').');
}
if (aSetOfInts is! Set<int>) {
errs.add('aSetOfInts should have the type Set<int>.');
} else if (aSetOfInts.length != 3) {
errs.add('aSetOfInts has ${aSetOfInts.length} items in it, rather than the expected 3.');
} else if (!aSetOfInts.contains(3) || !aSetOfInts.contains(4) || !aSetOfInts.contains(5)) {
errs.add('aSetOfInts doesn\'t contain the correct values (3, 4, 5).');
}
if (aMapOfStringsToInts is! Map<String, int>) {
errs.add('aMapOfStringsToInts should have the type Map<String, int>.');
} else if (aMapOfStringsToInts['myKey'] != 12) {
errs.add('aMapOfStringsToInts doesn\'t contain the correct values (\'myKey\': 12).');
}
if (anEmptyListOfDouble is! List<double>) {
errs.add('anEmptyListOfDouble should have the type List<double>.');
} else if (anEmptyListOfDouble.isNotEmpty) {
errs.add('anEmptyListOfDouble should be empty.');
}
if (anEmptySetOfString is! Set<String>) {
errs.add('anEmptySetOfString should have the type Set<String>.');
} else if (anEmptySetOfString.isNotEmpty) {
errs.add('anEmptySetOfString should be empty.');
}
if (anEmptyMapOfDoublesToInts is! Map<double, int>) {
errs.add('anEmptyMapOfDoublesToInts should have the type Map<double, int>.');
} else if (anEmptyMapOfDoublesToInts.isNotEmpty) {
errs.add('anEmptyMapOfDoublesToInts should be empty.');
}
if (errs.isEmpty) {
_result(true);
} else {
_result(false, errs);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
This exercise is fairly straightforward.
Just add a list, set, or map literal after each equals sign.
See the codelab text for the correct syntax to use.
{$ end hint.txt $}
箭頭語法
你也許已經在 Dart 程式碼中見到過 =>
符號。這種箭頭語法是一種定義函式的方法,該函式將在其右側執行表示式並返回其值。
例如,考慮呼叫這個 List
類中的 any
方法:
bool hasEmpty = aListOfStrings.any((s) {
return s.isEmpty;
});
這裡是一個更簡單的程式碼實現:
bool hasEmpty = aListOfStrings.any((s) => s.isEmpty);
Code example
程式碼範例
嘗試使用箭頭語法完成下面陳述式:
{$ begin main.dart $}
class MyClass {
int value1 = 2;
int value2 = 3;
int value3 = 5;
// Returns the product of the above values:
int get product => TODO();
// Adds 1 to value1:
void incrementValue1() => TODO();
// Returns a string containing each item in the
// list, separated by commas (e.g. 'a,b,c'):
String joinWithCommas(List<String> strings) => TODO();
}
{$ end main.dart $}
{$ begin solution.dart $}
class MyClass {
int value1 = 2;
int value2 = 3;
int value3 = 5;
// Returns the product of the above values:
int get product => value1 * value2 * value3;
// Adds 1 to value1:
void incrementValue1() => value1++;
// Returns a string containing each item in the
// list, separated by commas (e.g. 'a,b,c'):
String joinWithCommas(List<String> strings) => strings.join(',');
}
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
final obj = MyClass();
final errs = <String>[];
try {
final product = obj.product;
if (product != 30) {
errs.add('The product property returned $product instead of the expected value (30).');
}
} on UnimplementedError {
_result(false, ['Tried to use MyClass.product but failed. Did you implement the method?']);
return;
} catch (e) {
_result(false, ['Tried to use MyClass.product, but encountered an exception: ${e.runtimeType}.']);
return;
}
try {
obj.incrementValue1();
if (obj.value1 != 3) {
errs.add('After calling incrementValue, value1 was ${obj.value1} instead of the expected value (3).');
}
} on UnimplementedError {
_result(false, ['Tried to use MyClass.incrementValue1 but failed. Did you implement the method?']);
return;
} catch (e) {
_result(false, ['Tried to use MyClass.incrementValue1, but encountered an exception: ${e.runtimeType}.']);
return;
}
try {
final joined = obj.joinWithCommas(['one', 'two', 'three']);
if (joined != 'one,two,three') {
errs.add('Tried calling joinWithCommas([\'one\', \'two\', \'three\']) and received $joined instead of the expected value (\'one,two,three\').');
}
} on UnimplementedError {
_result(false, ['Tried to use MyClass.joinWithCommas but failed. Did you implement the method?']);
return;
} catch (e) {
_result(false, ['Tried to use MyClass.joinWithCommas, but encountered an exception: ${e.runtimeType}.']);
return;
}
if (errs.isEmpty) {
_result(true);
} else {
_result(false, errs);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
For the product, you can just multiply the three values together.
For incrementValue1, you can use the increment operator (++).
For joinWithCommas, try using the join method found in the List class.
{$ end hint.txt $}
級聯
要對同一物件執行一系列操作,請使用級聯(..
)。我們都看到過這樣的表示式:
myObject.someMethod()
它在 myObject
上呼叫 someMethod
方法,而表示式的結果是 someMethod
的返回值。
下面是一個使用級連語法的相同表示式:
myObject..someMethod()
Although it still invokes someMethod()
on myObject
, the result
of the expression isn’t the return value—it’s a reference to myObject
!
雖然它仍然在 myObject
上呼叫了 someMethod
,但表示式的結果卻不是該方法返回值,而是是 myObject
物件的參考!使用級聯,你可以將需要單獨操作的陳述式連結在一起。例如,下方的程式碼使用了空判斷呼叫符 (?.
) 在 button
不為 null
時獲取屬性:
var button = querySelector('#confirm');
button?.text = 'Confirm';
button?.classes.add('important');
button?.onClick.listen((e) => window.alert('Confirmed!'));
button?.scrollIntoView();
現在你可以在第一個級聯位置,使用 空判斷 級聯運運算元 (?..
),它可以確保級聯操作均在例項不為 null
時執行。使用空判斷級聯後,你也不再需要 button
變量了:
querySelector('#confirm')
?..text = 'Confirm'
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'))
..scrollIntoView();
Code example
程式碼範例
使用級聯建立一個陳述式,分別將 BigObject
的 anInt
屬性設為 1
、aString
屬性設為 String!
、aList
屬性設定為
[3.0]
然後呼叫 allDone()
。
{$ begin main.dart $}
class BigObject {
int anInt = 0;
String aString = '';
List<double> aList = [];
bool _done = false;
void allDone() {
_done = true;
}
}
BigObject fillBigObject(BigObject obj) {
// Create a single statement that will update and return obj:
return TODO('obj..');
}
{$ end main.dart $}
{$ begin solution.dart $}
class BigObject {
int anInt = 0;
String aString = '';
List<double> aList = [];
bool _done = false;
void allDone() {
_done = true;
}
}
BigObject fillBigObject(BigObject obj) {
return obj
..anInt = 1
..aString = 'String!'
..aList.add(3)
..allDone();
}
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
BigObject obj;
try {
obj = fillBigObject(BigObject());
} on UnimplementedError {
_result(false, ['Tried to call fillBigObject but failed. Did you implement the method?']);
return;
} catch (e) {
_result(false, [
'Caught an exception of type ${e.runtimeType} while running fillBigObject'
]);
return;
}
final errs = <String>[];
if (obj.anInt != 1) {
errs.add(
'The value of anInt was ${obj.anInt} rather than the expected (1).');
}
if (obj.aString != 'String!') {
errs.add(
'The value of aString was \'${obj.aString}\' rather than the expected (\'String!\').');
}
if (obj.aList.length != 1) {
errs.add(
'The length of aList was ${obj.aList.length} rather than the expected value (1).');
} else {
if (obj.aList[0] != 3.0) {
errs.add(
'The value found in aList was ${obj.aList[0]} rather than the expected (3.0).');
}
}
if (!obj._done) {
errs.add('It looks like allDone() wasn\'t called.');
}
if (errs.isEmpty) {
_result(true);
} else {
_result(false, errs);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
The best solution for this exercise starts with obj.. and
has four assignment operations chained together.
Try starting with `return obj..anInt = 1`,
then add another cascade (..) and start the next assignment.
{$ end hint.txt $}
Getters and setters
任何需要對屬性進行更多控制而不是允許簡單欄位存取的時候,你都可以自訂 getter 和 setter。
例如,你可以用來確保屬性值合法:
class MyClass {
int _aProperty = 0;
int get aProperty => _aProperty;
set aProperty(int value) {
if (value >= 0) {
_aProperty = value;
}
}
}
你還可以使用 getter 來定義計算屬性:
class MyClass {
final List<int> _values = [];
void addValue(int value) {
_values.add(value);
}
// A computed property.
int get count {
return _values.length;
}
}
Code example
程式碼範例
想象你有一個購物車類,其中有一個私有的 List<double>
型別的 prices 屬性。新增以下內容:
-
一個名為
total
的 getter,用於返回總價格。 -
只要新列表不包含任何負價格, setter 就會用新的列表替換列表(在這種情況下,setter 應該丟擲
InvalidPriceException
)。
可以忽略以下程式碼一開始在 DartPad 中的錯誤。
{$ begin main.dart $}
class InvalidPriceException {}
class ShoppingCart {
List<double> _prices = [];
// Add a "total" getter here:
// Add a "prices" setter here:
}
{$ end main.dart $}
{$ begin solution.dart $}
class InvalidPriceException {}
class ShoppingCart {
List<double> _prices = [];
double get total => _prices.fold(0, (e, t) => e + t);
set prices(List<double> value) {
if (value.any((p) => p < 0)) {
throw InvalidPriceException();
}
_prices = value;
}
}
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
var foundException = false;
try {
final cart = ShoppingCart();
cart.prices = [12.0, 12.0, -23.0];
} on InvalidPriceException {
foundException = true;
} catch (e) {
_result(false, ['Tried setting a negative price and received a ${e.runtimeType} instead of an InvalidPriceException.']);
return;
}
if (!foundException) {
_result(false, ['Tried setting a negative price and didn\'t get an InvalidPriceException.']);
return;
}
final secondCart = ShoppingCart();
try {
secondCart.prices = [1.0, 2.0, 3.0];
} catch(e) {
_result(false, ['Tried setting prices with a valid list, but received an exception: ${e.runtimeType}.']);
return;
}
if (secondCart._prices.length != 3) {
_result(false, ['Tried setting prices with a list of three values, but _prices ended up having length ${secondCart._prices.length}.']);
return;
}
if (secondCart._prices[0] != 1.0 || secondCart._prices[1] != 2.0 || secondCart._prices[2] != 3.0) {
final vals = secondCart._prices.map((p) => p.toString()).join(', ');
_result(false, ['Tried setting prices with a list of three values (1, 2, 3), but incorrect ones ended up in the price list ($vals) .']);
return;
}
var sum = 0.0;
try {
sum = secondCart.total;
} catch (e) {
_result(false, ['Tried to get total, but received an exception: ${e.runtimeType}.']);
return;
}
if (sum != 6.0) {
_result(false, ['After setting prices to (1, 2, 3), total returned $sum instead of 6.']);
return;
}
_result(true);
}
{$ end test.dart $}
{$ begin hint.txt $}
Two functions are handy for this exercise.
One is `fold`, which can reduce a list to a single value
(try it to calculate the total).
The other is `any`, which can check each item in a list
with a function you give it
(try using it to check if there are any negative prices in the prices setter).
{$ end hint.txt $}
可選位置引數
Dart 有兩種傳參方法:位置引數和命名引數。位置引數你可能會比較熟悉:
int sumUp(int a, int b, int c) {
return a + b + c;
}
// ···
int total = sumUp(1, 2, 3);
在 Dart 裡,你可以將這些引數包裹在方括號中,使其變成可選位置引數:
int sumUpToFive(int a, [int? b, int? c, int? d, int? e]) {
int sum = a;
if (b != null) sum += b;
if (c != null) sum += c;
if (d != null) sum += d;
if (e != null) sum += e;
return sum;
}
// ···
int total = sumUpToFive(1, 2);
int otherTotal = sumUpToFive(1, 2, 3, 4, 5);
可選位置引數永遠放在方法引數列表的最後。除非你給它們提供一個預設值,否則預設為 null:
int sumUpToFive(int a, [int b = 2, int c = 3, int d = 4, int e = 5]) {
// ···
}
// ···
int newTotal = sumUpToFive(1);
print(newTotal); // <-- prints 15
Code example
程式碼範例
實現一個名為 joinWithCommas
的方法,它接收一至五個整數,然後返回由逗號分隔的包含這些數字的字串。以下是方法呼叫和返回值的一些範例:
|
|
|
---|---|---|
joinWithCommas(1) |
'1' |
|
joinWithCommas(1, 2, 3) |
'1,2,3' |
|
joinWithCommas(1, 1, 1, 1, 1) |
'1,1,1,1,1' |
{$ begin main.dart $}
String joinWithCommas(int a, [int? b, int? c, int? d, int? e]) {
return TODO();
}
{$ end main.dart $}
{$ begin solution.dart $}
String joinWithCommas(int a, [int? b, int? c, int? d, int? e]) {
var total = '$a';
if (b != null) total = '$total,$b';
if (c != null) total = '$total,$c';
if (d != null) total = '$total,$d';
if (e != null) total = '$total,$e';
return total;
}
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
final errs = <String>[];
try {
final value = joinWithCommas(1);
if (value != '1') {
errs.add('Tried calling joinWithCommas(1) and got $value instead of the expected (\'1\').');
}
} on UnimplementedError {
_result(false, ['Tried to call joinWithCommas but failed. Did you implement the method?']);
return;
} catch (e) {
_result(false, ['Tried calling joinWithCommas(1), but encountered an exception: ${e.runtimeType}.']);
return;
}
try {
final value = joinWithCommas(1, 2, 3);
if (value != '1,2,3') {
errs.add('Tried calling joinWithCommas(1, 2, 3) and got $value instead of the expected (\'1,2,3\').');
}
} on UnimplementedError {
_result(false, ['Tried to call joinWithCommas but failed. Did you implement the method?']);
return;
} catch (e) {
_result(false, ['Tried calling joinWithCommas(1, 2 ,3), but encountered an exception: ${e.runtimeType}.']);
return;
}
try {
final value = joinWithCommas(1, 2, 3, 4, 5);
if (value != '1,2,3,4,5') {
errs.add('Tried calling joinWithCommas(1, 2, 3, 4, 5) and got $value instead of the expected (\'1,2,3,4,5\').');
}
} on UnimplementedError {
_result(false, ['Tried to call joinWithCommas but failed. Did you implement the method?']);
return;
} catch (e) {
_result(false, ['Tried calling stringify(1, 2, 3, 4 ,5), but encountered an exception: ${e.runtimeType}.']);
return;
}
if (errs.isEmpty) {
_result(true);
} else {
_result(false, errs);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
The b, c, d, and e parameters are null if they aren't provided by caller.
The important thing, then, is to check whether those arguments are null
before you add them to the final string.
{$ end hint.txt $}
命名引數
你可以在引數列表的靠後位置使用花括號 ({}
) 來定義命名引數。
除非顯式使用 required
進行標記,否則命名引數預設是可選的。
void printName(String firstName, String lastName, {String? middleName}) {
print('$firstName ${middleName ?? ''} $lastName');
}
// ···
printName('Dash', 'Dartisan');
printName('John', 'Smith', middleName: 'Who');
// Named arguments can be placed anywhere in the argument list
printName('John', middleName: 'Who', 'Smith');
正如你所料,這些引數預設為 null,但你也可以為其提供預設值。
如果一個引數的型別是非空的,那麼你必須要提供一個預設值(如下方程式碼所示),或者將其標記為 required
(如 構造部分所示)。
void printName(String firstName, String lastName, {String middleName = ''}) {
print('$firstName $middleName $lastName');
}
一個方法不能同時使用可選位置引數和可選命名引數。
Code example
程式碼範例
向 MyDataObject
類新增一個 copyWith()
例項方法,它應該包含三個可空的命名引數。
int? newInt
String? newString
double? newDouble
copyWith
方法應該根據當前例項返回一個新的
MyDataObject
並將前面引數(如果有的話)的資料複製到物件的屬性中。例如,如果 newInt
不為空,則將其值複製到 anInt
中。
可以忽略以下程式碼一開始在 DartPad 中的錯誤。
{$ begin main.dart $}
class MyDataObject {
final int anInt;
final String aString;
final double aDouble;
MyDataObject({
this.anInt = 1,
this.aString = 'Old!',
this.aDouble = 2.0,
});
// Add your copyWith method here:
}
{$ end main.dart $}
{$ begin solution.dart $}
class MyDataObject {
final int anInt;
final String aString;
final double aDouble;
MyDataObject({
this.anInt = 1,
this.aString = 'Old!',
this.aDouble = 2.0,
});
MyDataObject copyWith({int? newInt, String? newString, double? newDouble}) {
return MyDataObject(
anInt: newInt ?? this.anInt,
aString: newString ?? this.aString,
aDouble: newDouble ?? this.aDouble,
);
}
}
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
final source = MyDataObject();
final errs = <String>[];
try {
final copy = source.copyWith(newInt: 12, newString: 'New!', newDouble: 3.0);
if (copy.anInt != 12) {
errs.add('Called copyWith(newInt: 12, newString: \'New!\', newDouble: 3.0), and the new object\'s anInt was ${copy.anInt} rather than the expected value (12).');
}
if (copy.aString != 'New!') {
errs.add('Called copyWith(newInt: 12, newString: \'New!\', newDouble: 3.0), and the new object\'s aString was ${copy.aString} rather than the expected value (\'New!\').');
}
if (copy.aDouble != 3) {
errs.add('Called copyWith(newInt: 12, newString: \'New!\', newDouble: 3.0), and the new object\'s aDouble was ${copy.aDouble} rather than the expected value (3).');
}
} catch (e) {
_result(false, ['Called copyWith(newInt: 12, newString: \'New!\', newDouble: 3.0) and got an exception: ${e.runtimeType}']);
}
try {
final copy = source.copyWith();
if (copy.anInt != 1) {
errs.add('Called copyWith(), and the new object\'s anInt was ${copy.anInt} rather than the expected value (1).');
}
if (copy.aString != 'Old!') {
errs.add('Called copyWith(), and the new object\'s aString was ${copy.aString} rather than the expected value (\'Old!\').');
}
if (copy.aDouble != 2) {
errs.add('Called copyWith(), and the new object\'s aDouble was ${copy.aDouble} rather than the expected value (2).');
}
} catch (e) {
_result(false, ['Called copyWith() and got an exception: ${e.runtimeType}']);
}
if (errs.isEmpty) {
_result(true);
} else {
_result(false, errs);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
The copyWith method shows up in a lot of classes and libraries.
Yours should do a few things:
use optional named parameters,
create a new instance of MyDataObject,
and use the data from the parameters to fill it
(or the data from the current instance if the parameters are null).
This is a chance to get more practice with the ?? operator!
{$ end hint.txt $}
例外
Dart 程式碼可以丟擲和捕獲例外。與 Java 相比,Dart 的所有例外都是 unchecked exception。方法不會宣告它們可能丟擲的例外,你也不需要捕獲任何例外。
雖然 Dart 提供了 Exception 和 Error 型別,但是你可以丟擲任何非空物件:
throw Exception('Something bad happened.');
throw 'Waaaaaaah!';
使用 try
、on
以及 catch
關鍵字來處理例外:
try {
breedMoreLlamas();
} on OutOfLlamasException {
// A specific exception
buyMoreLlamas();
} on Exception catch (e) {
// Anything else that is an exception
print('Unknown exception: $e');
} catch (e) {
// No specified type, handles all
print('Something really unknown: $e');
}
The try
keyword works as it does in most other languages.
Use the on
keyword to filter for specific exceptions by type,
and the catch
keyword to get a reference to the exception object.
如果你無法完全處理該例外,請使用 rethrow
關鍵字再次丟擲例外:
try {
breedMoreLlamas();
} catch (e) {
print('I was just trying to breed llamas!');
rethrow;
}
要執行一段無論是否丟擲例外都會執行的程式碼,請使用 finally
:
try {
breedMoreLlamas();
} catch (e) {
// ... handle exception ...
} finally {
// Always clean up, even if an exception is thrown.
cleanLlamaStalls();
}
Code example
程式碼範例
在下面實現 tryFunction()
方法。它應該會執行一個不可靠的方法,然後做以下操作:
-
如果
untrustworthy()
丟擲了ExceptionWithMessage
,則呼叫logger.logException
並傳入使用例外型別和訊息(嘗試使用on
和catch
)。 -
如果
untrustworthy()
丟擲了一個Exception
,則呼叫logger.logException
並傳入使用例外型別(這次請嘗試使用on
)。 -
如果
untrustworthy()
丟擲了其他物件,請不要捕獲該例外。 -
捕獲並處理完所有內容後,呼叫
logger.doneLogging
(嘗試使用finally
)。
{$ begin main.dart $}
typedef VoidFunction = void Function();
class ExceptionWithMessage {
final String message;
const ExceptionWithMessage(this.message);
}
// Call logException to log an exception, and doneLogging when finished.
abstract class Logger {
void logException(Type t, [String? msg]);
void doneLogging();
}
void tryFunction(VoidFunction untrustworthy, Logger logger) {
// Invoking this method might cause an exception. Catch and handle
// them using try-on-catch-finally.
untrustworthy();
}
{$ end main.dart $}
{$ begin solution.dart $}
typedef VoidFunction = void Function();
class ExceptionWithMessage {
final String message;
const ExceptionWithMessage(this.message);
}
abstract class Logger {
void logException(Type t, [String? msg]);
void doneLogging();
}
void tryFunction(VoidFunction untrustworthy, Logger logger) {
try {
untrustworthy();
} on ExceptionWithMessage catch (e) {
logger.logException(e.runtimeType, e.message);
} on Exception {
logger.logException(Exception);
} finally {
logger.doneLogging();
}
}
{$ end solution.dart $}
{$ begin test.dart $}
class MyLogger extends Logger {
Type? lastType;
String lastMessage = '';
bool done = false;
void logException(Type t, [String? message]) {
lastType = t;
lastMessage = message ?? lastMessage;
}
void doneLogging() => done = true;
}
void main() {
final errs = <String>[];
var logger = MyLogger();
try {
tryFunction(() => throw Exception(), logger);
if ('${logger.lastType}' != 'Exception' && '${logger.lastType}' != '_Exception') {
errs.add('Untrustworthy threw an Exception, but a different type was logged: ${logger.lastType}.');
}
if (logger.lastMessage != '') {
errs.add('Untrustworthy threw an Exception with no message, but a message was logged anyway: \'${logger.lastMessage}\'.');
}
if (!logger.done) {
errs.add('Untrustworthy threw an Exception, and doneLogging() wasn\'t called afterward.');
}
} catch (e) {
_result(false, ['Untrustworthy threw an exception, and an exception of type ${e.runtimeType} was unhandled by tryFunction.']);
}
logger = MyLogger();
try {
tryFunction(() => throw ExceptionWithMessage('Hey!'), logger);
if (logger.lastType != ExceptionWithMessage) {
errs.add('Untrustworthy threw an ExceptionWithMessage(\'Hey!\'), but a different type was logged: ${logger.lastType}.');
}
if (logger.lastMessage != 'Hey!') {
errs.add('Untrustworthy threw an ExceptionWithMessage(\'Hey!\'), but a different message was logged: \'${logger.lastMessage}\'.');
}
if (!logger.done) {
errs.add('Untrustworthy threw an ExceptionWithMessage(\'Hey!\'), and doneLogging() wasn\'t called afterward.');
}
} catch (e) {
_result(false, ['Untrustworthy threw an ExceptionWithMessage(\'Hey!\'), and an exception of type ${e.runtimeType} was unhandled by tryFunction.']);
}
logger = MyLogger();
bool caughtStringException = false;
try {
tryFunction(() => throw 'A String', logger);
} on String {
caughtStringException = true;
}
if (!caughtStringException) {
errs.add('Untrustworthy threw a string, and it was incorrectly handled inside tryFunction().');
}
logger = MyLogger();
try {
tryFunction(() {}, logger);
if (logger.lastType != null) {
errs.add('Untrustworthy didn\'t throw an Exception, but one was logged anyway: ${logger.lastType}.');
}
if (logger.lastMessage != '') {
errs.add('Untrustworthy didn\'t throw an Exception with no message, but a message was logged anyway: \'${logger.lastMessage}\'.');
}
if (!logger.done) {
errs.add('Untrustworthy didn\'t throw an Exception, but doneLogging() wasn\'t called afterward.');
}
} catch (e) {
_result(false, ['Untrustworthy didn\'t throw an exception, but an exception of type ${e.runtimeType} was unhandled by tryFunction anyway.']);
}
if (errs.isEmpty) {
_result(true);
} else {
_result(false, errs);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
This exercise looks tricky, but it's really one big `try` statement.
Just call `untrustworthy` inside the `try`, and
then use `on`, `catch`, and `finally` to catch exceptions and
call methods on the logger.
{$ end hint.txt $}
this
在構造方法中使用 Dart 提供了一個方便的快捷方式,用於為構造方法中的屬性賦值:在宣告構造方法時使用 this.propertyName
。
class MyColor {
int red;
int green;
int blue;
MyColor(this.red, this.green, this.blue);
}
final color = MyColor(80, 80, 128);
此技巧同樣也適用於命名引數。屬性名為引數的名稱:
class MyColor {
...
MyColor({required this.red, required this.green, required this.blue});
}
final color = MyColor(red: 80, green: 80, blue: 80);
在上面的程式碼中,red
、green
和 blue
被標記為 required
,因為這些 int
數值不能為空。如果你指定了預設值,你可以忽略 required
。
對於可選引數,預設值為期望值:
MyColor([this.red = 0, this.green = 0, this.blue = 0]);
// or
MyColor({this.red = 0, this.green = 0, this.blue = 0});
Code example
程式碼範例
使用 this
語法向 MyClass
新增一行構造方法,並接收和分配全部(三個)屬性。
可以忽略以下程式碼一開始在 DartPad 中的錯誤。
{$ begin main.dart $}
class MyClass {
final int anInt;
final String aString;
final double aDouble;
// Create a constructor here.
}
{$ end main.dart $}
{$ begin solution.dart $}
class MyClass {
final int anInt;
final String aString;
final double aDouble;
MyClass(this.anInt, this.aString, this.aDouble);
}
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
final errs = <String>[];
try {
final obj = MyClass(1, 'two', 3);
if (obj.anInt != 1) {
errs.add('Called MyClass(1, \'two\', 3) and got an object with anInt of ${obj.anInt} instead of the expected value (1).');
}
if (obj.anInt != 1) {
errs.add('Called MyClass(1, \'two\', 3) and got an object with aString of \'${obj.aString}\' instead of the expected value (\'two\').');
}
if (obj.anInt != 1) {
errs.add('Called MyClass(1, \'two\', 3) and got an object with aDouble of ${obj.aDouble} instead of the expected value (3).');
}
} catch (e) {
_result(false, ['Called MyClass(1, \'two\', 3) and got an exception of type ${e.runtimeType}.']);
}
if (errs.isEmpty) {
_result(true);
} else {
_result(false, errs);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
This exercise has a one-line solution.
Just declare the constructor with
`this.anInt`, `this.aString`, and `this.aDouble`
as its parameters in that order.
{$ end hint.txt $}
Initializer lists
有時,當你在實現建構函式時,您需要在建構函式體執行之前進行一些初始化。例如,final 修飾的欄位必須在建構函式體執行之前賦值。在初始化列表中執行此操作,該列表位於建構函式的簽名與其函式體之間:
Point.fromJson(Map<String, double> json)
: x = json['x']!,
y = json['y']! {
print('In Point.fromJson(): ($x, $y)');
}
初始化列表也是放置斷言的便利位置,它僅會在開發期間執行:
NonNegativePoint(this.x, this.y)
: assert(x >= 0),
assert(y >= 0) {
print('I just made a NonNegativePoint: ($x, $y)');
}
Code example
程式碼範例
完成下面的 FirstTwoLetters
的建構函式。使用的初始化列表將 word
的前兩個字元分配給 letterOne
和 LetterTwo
屬性。要獲得額外的信用,請新增一個 斷言
以捕獲少於兩個字元的單詞。
可以忽略以下程式碼一開始在 DartPad 中的錯誤。
{$ begin main.dart $}
class FirstTwoLetters {
final String letterOne;
final String letterTwo;
// Create a constructor with an initializer list here:
FirstTwoLetters(String word)
...
}
{$ end main.dart $}
{$ begin solution.dart $}
class FirstTwoLetters {
final String letterOne;
final String letterTwo;
FirstTwoLetters(String word)
: assert(word.length >= 2),
letterOne = word[0],
letterTwo = word[1];
}
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
final errs = <String>[];
try {
final result = FirstTwoLetters('My String');
if (result.letterOne != 'M') {
errs.add('Called FirstTwoLetters(\'My String\') and got an object with letterOne equal to \'${result.letterOne}\' instead of the expected value (\'M\').');
}
if (result.letterTwo != 'y') {
errs.add('Called FirstTwoLetters(\'My String\') and got an object with letterTwo equal to \'${result.letterTwo}\' instead of the expected value (\'y\').');
}
} catch (e) {
errs.add('Called FirstTwoLetters(\'My String\') and got an exception of type ${e.runtimeType}.');
}
bool caughtException = false;
try {
FirstTwoLetters('');
} catch (e) {
caughtException = true;
}
if (!caughtException) {
errs.add('Called FirstTwoLetters(\'\') and didn\'t get an exception from the failed assertion.');
}
if (errs.isEmpty) {
_result(true);
} else {
_result(false, errs);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
Two assignments need to happen:
letterOne should be word[0], and letterTwo should be word[1].
{$ end hint.txt $}
命名構造方法
為了允許一個類別具有多個構造方法, Dart 支援命名構造方法:
class Point {
double x, y;
Point(this.x, this.y);
Point.origin()
: x = 0,
y = 0;
}
為了使用命名構造方法,請使用全名呼叫它:
final myPoint = Point.origin();
Code example
程式碼範例
給 Color
類新增一個叫做 Color.black
的方法,它將會把三個屬性的值都設為 0。
可以忽略以下程式碼一開始在 DartPad 中的錯誤。
{$ begin main.dart $}
class Color {
int red;
int green;
int blue;
Color(this.red, this.green, this.blue);
// Create a named constructor called "Color.black" here:
}
{$ end main.dart $}
{$ begin solution.dart $}
class Color {
int red;
int green;
int blue;
Color(this.red, this.green, this.blue);
Color.black()
: red = 0,
green = 0,
blue = 0;
}
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
final errs = <String>[];
try {
final result = Color.black();
if (result.red != 0) {
errs.add('Called Color.black() and got a Color with red equal to ${result.red} instead of the expected value (0).');
}
if (result.green != 0) {
errs.add('Called Color.black() and got a Color with green equal to ${result.green} instead of the expected value (0).');
}
if (result.blue != 0) {
errs.add('Called Color.black() and got a Color with blue equal to ${result.blue} instead of the expected value (0).');
}
} catch (e) {
_result(false, ['Called Color.black() and got an exception of type ${e.runtimeType}.']);
return;
}
if (errs.isEmpty) {
_result(true);
} else {
_result(false, errs);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
The declaration for your constructor should be `Color.black() {}`.
Inside the braces, set red, green, and blue to zero.
{$ end hint.txt $}
工廠構造方法
Dart 支援工廠構造方法。它能夠返回其子類別甚至 null 物件。要建立一個工廠構造方法,請使用 factory
關鍵字。
class Square extends Shape {}
class Circle extends Shape {}
class Shape {
Shape();
factory Shape.fromTypeName(String typeName) {
if (typeName == 'square') return Square();
if (typeName == 'circle') return Circle();
throw ArgumentError('Unrecognized $typeName');
}
}
Code example
程式碼範例
填寫名為 IntegerHolder.fromList
的工廠構造方法,使其執行以下操作:
-
若列表只有一個值,那麼就用它來建立一個
IntegerSingle
。 -
如果這個列表有兩個值,那麼按其順序建立一個
IntegerDouble
。 -
如果這個列表有三個值,那麼按其順序建立一個
IntegerTriple
。 -
否則,丟擲一個
Error
。
{$ begin main.dart $}
class IntegerHolder {
IntegerHolder();
// Implement this factory constructor.
factory IntegerHolder.fromList(List<int> list) {
TODO();
}
}
class IntegerSingle extends IntegerHolder {
final int a;
IntegerSingle(this.a);
}
class IntegerDouble extends IntegerHolder {
final int a;
final int b;
IntegerDouble(this.a, this.b);
}
class IntegerTriple extends IntegerHolder {
final int a;
final int b;
final int c;
IntegerTriple(this.a, this.b, this.c);
}
{$ end main.dart $}
{$ begin solution.dart $}
class IntegerHolder {
IntegerHolder();
factory IntegerHolder.fromList(List<int> list) {
if (list.length == 1) {
return IntegerSingle(list[0]);
} else if (list.length == 2) {
return IntegerDouble(list[0], list[1]);
} else if (list.length == 3) {
return IntegerTriple(list[0], list[1], list[2]);
} else {
throw Error();
}
}
}
class IntegerSingle extends IntegerHolder {
final int a;
IntegerSingle(this.a);
}
class IntegerDouble extends IntegerHolder {
final int a;
final int b;
IntegerDouble(this.a, this.b);
}
class IntegerTriple extends IntegerHolder {
final int a;
final int b;
final int c;
IntegerTriple(this.a, this.b, this.c);
}
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
final errs = <String>[];
bool _throwed = false;
try {
IntegerHolder.fromList([]);
} on UnimplementedError {
_result(false, ['Test failed. Did you implement the method?']);
return;
} on Error {
_throwed = true;
} catch (e) {
_result(false, ['Called IntegerSingle.fromList([]) and got an exception of type ${e.runtimeType}.']);
return;
}
if (!_throwed) {
errs.add('Called IntegerSingle.fromList([]) and didn\'t throw Error.');
}
try {
final obj = IntegerHolder.fromList([1]);
if (obj is! IntegerSingle) {
errs.add('Called IntegerHolder.fromList([1]) and got an object of type ${obj.runtimeType} instead of IntegerSingle.');
} else {
if (obj.a != 1) {
errs.add('Called IntegerHolder.fromList([1]) and got an IntegerSingle with an \'a\' value of ${obj.a} instead of the expected (1).');
}
}
} catch (e) {
_result(false, ['Called IntegerHolder.fromList([]) and got an exception of type ${e.runtimeType}.']);
return;
}
try {
final obj = IntegerHolder.fromList([1, 2]);
if (obj is! IntegerDouble) {
errs.add('Called IntegerHolder.fromList([1, 2]) and got an object of type ${obj.runtimeType} instead of IntegerDouble.');
} else {
if (obj.a != 1) {
errs.add('Called IntegerHolder.fromList([1, 2]) and got an IntegerDouble with an \'a\' value of ${obj.a} instead of the expected (1).');
}
if (obj.b != 2) {
errs.add('Called IntegerHolder.fromList([1, 2]) and got an IntegerDouble with an \'b\' value of ${obj.b} instead of the expected (2).');
}
}
} catch (e) {
_result(false, ['Called IntegerHolder.fromList([1, 2]) and got an exception of type ${e.runtimeType}.']);
return;
}
try {
final obj = IntegerHolder.fromList([1, 2, 3]);
if (obj is! IntegerTriple) {
errs.add('Called IntegerHolder.fromList([1, 2, 3]) and got an object of type ${obj.runtimeType} instead of IntegerTriple.');
} else {
if (obj.a != 1) {
errs.add('Called IntegerHolder.fromList([1, 2, 3]) and got an IntegerTriple with an \'a\' value of ${obj.a} instead of the expected (1).');
}
if (obj.b != 2) {
errs.add('Called IntegerHolder.fromList([1, 2, 3]) and got an IntegerTriple with an \'a\' value of ${obj.b} instead of the expected (2).');
}
if (obj.c != 3) {
errs.add('Called IntegerHolder.fromList([1, 2, 3]) and got an IntegerTriple with an \'a\' value of ${obj.b} instead of the expected (2).');
}
}
} catch (e) {
_result(false, ['Called IntegerHolder.fromList([1, 2, 3]) and got an exception of type ${e.runtimeType}.']);
return;
}
if (errs.isEmpty) {
_result(true);
} else {
_result(false, errs);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
Inside the factory constructor,
check the length of the list and create an
IntegerSingle, IntegerDouble, or IntegerTriple as appropriate.
{$ end hint.txt $}
重新導向構造方法
有時一個構造方法僅僅用來重新導向到該類別的另一個構造方法。重新導向方法沒有主體,它在冒號(:
)之後呼叫另一個構造方法。
class Automobile {
String make;
String model;
int mpg;
// The main constructor for this class.
Automobile(this.make, this.model, this.mpg);
// Delegates to the main constructor.
Automobile.hybrid(String make, String model) : this(make, model, 60);
// Delegates to a named constructor
Automobile.fancyHybrid() : this.hybrid('Futurecar', 'Mark 2');
}
Code example
程式碼範例
還記得我們之前提到的 Color
類嗎?建立一個叫做 black
的命名構造方法,但這次我們不要手動分配屬性,而是將 0 作為引數,重新導向到預設的構造方法。
可以忽略以下程式碼一開始在 DartPad 中的錯誤。
{$ begin main.dart $}
class Color {
int red;
int green;
int blue;
Color(this.red, this.green, this.blue);
// Create a named constructor called "black" here and redirect it
// to call the existing constructor
}
{$ end main.dart $}
{$ begin solution.dart $}
class Color {
int red;
int green;
int blue;
Color(this.red, this.green, this.blue);
Color.black() : this(0, 0, 0);
}
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
final errs = <String>[];
try {
final result = Color.black();
if (result.red != 0) {
errs.add('Called Color.black() and got a Color with red equal to ${result.red} instead of the expected value (0).');
}
if (result.green != 0) {
errs.add('Called Color.black() and got a Color with green equal to ${result.green} instead of the expected value (0).');
}
if (result.blue != 0) {
errs.add('Called Color.black() and got a Color with blue equal to ${result.blue} instead of the expected value (0).');
}
} catch (e) {
_result(false, ['Called Color.black() and got an exception of type ${e.runtimeType}.']);
return;
}
if (errs.isEmpty) {
_result(true);
} else {
_result(false, errs);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
Your constructor should redirect to `this(0, 0, 0)`.
{$ end hint.txt $}
Const 構造方法
如果你的類產生的物件永遠都不會更改,則可以讓這些物件成為編譯時常量。為此,請定義 const
構造方法並確保所有例項變數都是 final 的。
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final int x;
final int y;
const ImmutablePoint(this.x, this.y);
}
Code example
程式碼範例
修改 Recipe
類,使其例項成為常量,並建立一個執行以下操作的常量構造方法:
-
該方法有三個引數:
ingredients
、calories
和milligramsOfSodium
。(按照此順序) -
使用
this
語法自動將引數值分配給同名的物件屬性。 -
在
Recipe
的構造方法宣告之前,用const
關鍵字使其成為常量。
可以忽略以下程式碼一開始在 DartPad 中的錯誤。
{$ begin main.dart $}
class Recipe {
List<String> ingredients;
int calories;
double milligramsOfSodium;
}
{$ end main.dart $}
{$ begin solution.dart $}
class Recipe {
final List<String> ingredients;
final int calories;
final double milligramsOfSodium;
const Recipe(this.ingredients, this.calories, this.milligramsOfSodium);
}
{$ end solution.dart $}
{$ begin test.dart $}
void main() {
final errs = <String>[];
try {
const obj = Recipe(['1 egg', 'Pat of butter', 'Pinch salt'], 120, 200);
if (obj.ingredients.length != 3) {
errs.add('Called Recipe([\'1 egg\', \'Pat of butter\', \'Pinch salt\'], 120, 200) and got an object with ingredient list of length ${obj.ingredients.length} rather than the expected length (3).');
}
if (obj.calories != 120) {
errs.add('Called Recipe([\'1 egg\', \'Pat of butter\', \'Pinch salt\'], 120, 200) and got an object with a calorie value of ${obj.calories} rather than the expected value (120).');
}
if (obj.milligramsOfSodium != 200) {
errs.add('Called Recipe([\'1 egg\', \'Pat of butter\', \'Pinch salt\'], 120, 200) and got an object with a milligramsOfSodium value of ${obj.milligramsOfSodium} rather than the expected value (200).');
}
} catch (e) {
_result(false, ['Tried calling Recipe([\'1 egg\', \'Pat of butter\', \'Pinch salt\'], 120, 200) and received a null.']);
}
if (errs.isEmpty) {
_result(true);
} else {
_result(false, errs);
}
}
{$ end test.dart $}
{$ begin hint.txt $}
To make the constructor const, you'll need to make all the properties final.
{$ end hint.txt $}
下一步是什麼?
我們希望你能夠喜歡這個 codelab 來學習或測試你對 Dart 語言中一些最有趣的功能的知識。下面是一些有關現在該做什麼的建議:
-
嘗試閱讀 其他 Dart codelab。
-
閱讀 Dart 語言之旅。
-
在 DartPad 上進行練習。