Contents

Dart cheatsheet codelab

Dart语言旨在让其他语言的编码人员易于学习,但具有一些独特功能. 该代码实验室-基于Google工程师为Google工程师编写的Dart语言速查表 -引导您了解这些语言中最重要的功能.

该代码实验室中的嵌入式编辑器具有部分完成的代码段. 您可以使用这些编辑器通过完成代码并单击" 运行"按钮来测试您的知识. 如果需要帮助,请单击" 提示"按钮. 要运行代码格式化程序( dartfmt ),请点击格式 . " 重置"按钮将删除您的工作并将编辑器恢复到其原始状态.

String interpolation

要将表达式的值放在字符串中,请使用${expression} . 如果表达式是标识符,则可以省略{} .

以下是一些使用字符串插值的示例:

String   Result
'${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) {
  // 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 if (str == null) {
      _result(false, ['Test failed. Did you forget to return a value?']);
    } 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 $}
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 $}

Null-aware operators

Dart提供了一些方便的运算符来处理可能为null的值. 一个是??=赋值运算符,它仅在该变量当前为null时才为该变量赋值:

int a; // The initial value of a is null.
a ??= 3;
print(a); // <-- Prints 3.

a ??= 5;
print(a); // <-- Still prints 3.

另一个可识别空值的运算符是?? ,除非表达式的值为null,否则它将在其左侧返回表达式,在这种情况下,它将求值并在右侧返回表达式:

print(1 ?? 3); // <-- Prints 1.
print(null ?? 12); // <-- Prints 12.

Code example

尝试放置??=?? 操作员要在下面工作.

{$ begin main.dart $}
String foo = 'a string';
String bar; // Unassigned objects are null by default.

// 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; // Unassigned objects are null by default.

// 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 $}

Conditional property access

为了保护对可能为null的对象的属性或方法的访问,请在点( . )前面打一个问号( ? ):

myObject?.someProperty

上面的代码等效于以下代码:

(myObject != null) ? myObject.someProperty : null

您可以连锁使用?. 在一个表达式中一起:

myObject?.someProperty?.someMethod()

如果myObjectmyObject.someProperty为null,则前面的代码返回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具有对列表,地图和集合的内置支持. 您可以使用文字创建它们:

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

尝试将以下变量设置为指示的值.

{$ begin main.dart $}
// Assign this a list containing 'a', 'b', and 'c' in that order:
final aListOfStrings = 

// Assign this a set containing 3, 4, and 5:
final aSetOfInts = 

// Assign this a map of String to int so that aMapOfStringsToInts['myKey'] returns 12:
final aMapOfStringsToInts = 

// Assign this an empty List<double>:
final anEmptyListOfDouble = 

// Assign this an empty Set<String>:
final anEmptySetOfString = 

// Assign this an empty Map of double to int:
final anEmptyMapOfDoublesToInts = 
{$ 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 $}

Arrow syntax

您可能已经在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 =>
  
  // Adds 1 to _value1:
  void incrementValue1() => 
  
  // Returns a string containing each item in the
  // list, separated by commas (e.g. 'a,b,c'): 
  String joinWithCommas(List<String> strings) =>
}
{$ 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 one 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).'); 
    } 
  } 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).'); 
    } 
  } 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\').'); 
    } 
  } 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 $}

Cascades

要对同一对象执行一系列操作,请使用级联( .. ). 我们都看到过这样的表达式:

myObject.someMethod()

它在myObject上调用someMethod() ,表达式的结果是someMethod()的返回值.

这是带有级联的相同表达式:

myObject..someMethod()

尽管它仍然在myObject上调用someMethod() ,但表达式的结果不是返回值,而是对myObject的引用! 使用级联,可以将原本需要单独语句的操作链接在一起. 例如,考虑以下代码:

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

使用层叠时,代码变得更短,并且您不需要button变量:

querySelector('#confirm')
..text = 'Confirm'
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));

Code example

使用级联,以创建单个语句套anIntaStringaList一个属性BigObject1'String!' ,和[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 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());
  } catch (e) {
    _result(false, [
      'Caught an exception of type ${e.runtimeType} while running fillBigObject'
    ]);
    return;
  }

  if (obj == null) {
    _result(false, ['It looks like fillBigObject returned a null!']);
  } else {
    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 == null) {
      errs.add('The value of aList was null.');
    }

    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 {
  List<int> _values = [];

  void addValue(int value) {
    _values.add(value);
  }

  // A computed property.
  int get count {
    return _values.length;
  }
}

Code example

想象一下,您有一个购物车类,其中包含一个私有的List<double>价格. 添加以下内容:

  • 一个称为total的吸气剂,返回价格的总和
  • 只要新列表不包含任何负价,一个用新列表替换列表的设置器(在这种情况下,设置器应抛出InvalidPriceException ).
{$ 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 == null) {
    _result(false, ['Tried setting prices with a list of three values, but _prices ended up being null.']);
    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 $}

Optional positional parameters

Dart具有两种功能参数:positional和named. 位置参数是您可能熟悉的一种:

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()个整数,然后返回由逗号分隔的那些数字的字符串. 以下是一些函数调用和返回值的示例:

函数调用   返回值
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]) {

}
{$ 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\').'); 
    } 
  } 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\').'); 
    } 
  } 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\').'); 
    } 
  } 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 paramters 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 $}

Optional named parameters

使用花括号语法,可以定义具有名称的可选参数.

void printName(String firstName, String lastName, {String suffix}) {
  print('$firstName $lastName ${suffix ?? ''}');
}
// ···
  printName('Avinash', 'Gupta');
  printName('Poshmeister', 'Moneybuckets', suffix: 'IV');

如您所料,这些参数的值默认情况下为null,但是您可以提供默认值:

void printName(String firstName, String lastName, {String suffix = ''}) {
  print('$firstName $lastName $suffix');
}

函数不能同时具有可选的位置参数和可选的命名参数.

Code example

将一个copyWith()实例方法添加到MyDataObject类. 它应该带有三个命名参数:

  • int newInt
  • String newString
  • double newDouble

调用时, copyWith()应该基于当前实例返回一个新的MyDataObject ,并将先前参数(如果有)中的数据复制到对象的属性中. 例如,如果newInt不为null,则将其值复制到anInt .

{$ 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 == null) {
      errs.add('Tried copyWith(newInt: 12, newString: \'New!\', newDouble: 3.0) and it returned null');
    } else {
      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 == null) {
      errs.add('Tried copyWith() and it returned null');
    } else {
      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 $}

Exceptions

Dart代码可以引发并捕获异常. 与Java相反,Dart的所有异常都是未经检查的异常. 方法不声明它们可能引发哪些异常,也不需要捕获任何异常.

Dart提供了ExceptionError类型,但是您可以抛出任何非null对象:

throw Exception('Something bad happened.');
throw 'Waaaaaaah!';

处理异常时,请使用tryoncatch关键字:

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');
}

try关键字的工作方式与大多数其他语言相同. 使用on关键字按类型过滤特定的异常,使用catch关键字获取对异常对象的引用.

如果您不能完全处理该异常,请使用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() . 它应该执行不可信的方法,然后执行以下操作:

  • 如果logger.logException untrustworthy()抛出ExceptionWithMessagelogger.logException使用异常类型和消息调用logger.logException (尝试使用oncatch ).
  • 如果logger.logException untrustworthy()抛出Exception ,请使用异常类型调用logger.logException (尝试对此使用on ).
  • If untrustworthy() throws any other object, don’t catch the exception.
  • 在捕获并处理logger.doneLogging所有内容之后,请调用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 $}

Using this in a constructor

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({this.red, this.green, this.blue});
}

final color = MyColor(red: 80, green: 80, blue: 80);

对于可选参数,默认值可以按预期运行:

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添加一个单行构造函数this. 语法,用于接收和分配该类的所有三个属性的值.

{$ 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 == null) {
      errs.add('Called MyClass(1, \'two\', 3) and got a null in response.');
    } else {
      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, num> 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的前两个字符分配给letterOneLetterTwo属性. 为了增加信誉,请添加assert以捕获少于两个字符的单词.

{$ 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)
      : 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 == null) {
      errs.add('Called FirstTwoLetters(\'My String\') and got a null in response.');
    } else {
      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 $}

Named constructors

为了允许类具有多个构造函数,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的构造函数,该函数将所有三个属性设置为零.

{$ 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 == null) {
      errs.add('Called Color.black() and got a null in response.');
    } else {
      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 $}

Factory constructors

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();

    print('I don\'t recognize $typeName');
    return null;
  }
}

Code example

填写名为IntegerHolder.fromList的工厂构造IntegerHolder.fromList ,使其执行以下操作:

  • 如果列表具有一个值,请使用该值创建一个IntegerSingle .
  • 如果列表有两个值,请按顺序创建一个IntegerDouble值.
  • 如果列表具有三个值,请按顺序创建一个IntegerTriple值.
  • 否则,返回null.
{$ begin main.dart $}
class IntegerHolder {
  IntegerHolder();
  
  // Implement this factory constructor.
  factory IntegerHolder.fromList(List<int> list) {
  }
}

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 {
      return null;
    } 
  }
}

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>[];

  try {
    final obj = IntegerHolder.fromList([]);
    
    if (obj != null) {
      errs.add('Called IntegerSingle.fromList([]) and didn\'t get a null.');
    } 
  } catch (e) {
    _result(false, ['Called IntegerSingle.fromList([]) and got an exception of type ${e.runtimeType}.']);
    return;
  }

  try {
    final obj = IntegerHolder.fromList([1]);
    
    if (obj == null) {
      errs.add('Called IntegerHolder.fromList([1]) and got a null.');
    } else if (obj is! IntegerSingle) {
      errs.add('Called IntegerHolder.fromList([1]) and got an object of type ${obj.runtimeType} instead of IntegerSingle.');
    } else {
      if ((obj as IntegerSingle).a != 1) {
        errs.add('Called IntegerHolder.fromList([1]) and got an IntegerSingle with an \'a\' value of ${(obj as IntegerSingle).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 == null) {
      errs.add('Called IntegerHolder.fromList([1, 2]) and got a null.');
    } else 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 as IntegerDouble).a != 1) {
        errs.add('Called IntegerHolder.fromList([1, 2]) and got an IntegerDouble with an \'a\' value of ${(obj as IntegerDouble).a} instead of the expected (1).');
      }
      
      if ((obj as IntegerDouble).b != 2) {
        errs.add('Called IntegerHolder.fromList([1, 2]) and got an IntegerDouble with an \'b\' value of ${(obj as IntegerDouble).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 == null) {
      errs.add('Called IntegerHolder.fromList([1, 2, 3]) and got a null.');
    } else 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 as IntegerTriple).a != 1) {
        errs.add('Called IntegerHolder.fromList([1, 2, 3]) and got an IntegerTriple with an \'a\' value of ${(obj as IntegerTriple).a} instead of the expected (1).');
      }
      
      if ((obj as IntegerTriple).b != 2) {
        errs.add('Called IntegerHolder.fromList([1, 2, 3]) and got an IntegerTriple with an \'a\' value of ${(obj as IntegerTriple).b} instead of the expected (2).');
      }

      if ((obj as IntegerTriple).c != 3) {
        errs.add('Called IntegerHolder.fromList([1, 2, 3]) and got an IntegerTriple with an \'a\' value of ${(obj as IntegerTriple).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 $}

Redirecting constructors

有时,构造函数的唯一目的是重定向到同一类中的另一个构造函数. 重定向构造的身体是空的,与构造函数调用一个冒号后出现的( : ).

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的命名构造函数,而不是手动分配属性,而是将其重定向到默认构造函数,并使用零作为参数.

{$ 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 == null) {
      errs.add('Called Color.black() and got a null in response.');
    } else {
      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 constructors

如果您的类产生的对象永不改变,则可以使这些对象具有编译时常数. 为此,请定义const构造函数,并确保所有实例变量都是最终变量.

class ImmutablePoint {
  const ImmutablePoint(this.x, this.y);

  final int x;
  final int y;

  static const ImmutablePoint origin = ImmutablePoint(0, 0);
}

Code example

修改Recipe类,使其实例可以是常量,并创建一个常量构造函数,该常量将执行以下操作:

  • 具有三个参数: ingredientscaloriesmilligramsOfSodiummilligramsOfSodium顺序).
  • 使用this. 自动将参数值分配给同名对象属性的语法.
  • 是常量,在构造函数声明的Recipe之前使用const关键字.
{$ 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 == null) {
      errs.add('Tried calling Recipe([\'1 egg\', \'Pat of butter\', \'Pinch salt\'], 120, 200) and received a null.');
    } else {
      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 $}

What next?

我们希望您喜欢使用该代码实验室来学习或测试您对Dart语言最有趣的功能的了解. 以下是一些有关现在要做什么的建议:

by  ICOPY.SITE