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 上进行练习。