目錄
keyboard_arrow_down keyboard_arrow_upDart 語言旨在讓從其他程式語言轉來的開發者們能夠輕鬆學習,但也有它的獨特之處。本篇將基於谷歌工程師編寫的 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 2.12 引入了健全的空安全,這意味著在您 啟用了空安全 時,除非變數顯式宣告為可空型別,否則它們將不能為空。換句話說,型別預設是不可為空的。
舉個例子,下面的程式碼在空安全下是有錯誤的,因為 int
型別的變數不能為 null
:
int a = null; // INVALID in null-safe Dart.
在 Dart 2.12 或更高版本的 Dart 中(需要限制 SDK 為 2.12 及以上),你可以透過在型別後新增 ?
來表示該型別可空:
int? a = null; // Valid in null-safe Dart.
在所有 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 上進行練習。