目录

可迭代的集合

本教程教您如何使用实现 Iterable 类的集合,例如 ListSet。可迭代对象是各种 Dart 应用程序的基本构建块,您可能已经在不知不觉中使用它们了。本教程可帮助您充分利用它们。

使用嵌入式 DartPad 编辑器,您可以通过运行示例代码和完成练习来测试您的知识。

要充分利用本教程,您应该具备 Dart 语法的基本知识。

本教程涵盖以下内容

  • 如何读取可迭代对象的元素。
  • 如何检查可迭代对象的元素是否满足条件。
  • 如何过滤可迭代对象的内容。
  • 如何将可迭代对象的内容映射到不同的值。

完成本教程的预计时间:60 分钟。

本教程中的练习有部分完成的代码片段。您可以使用 DartPad 通过完成代码并单击“运行”按钮来测试您的知识。不要编辑 main 函数或其下的测试代码

如果您需要帮助,请在每次练习后展开“提示”或“解决方案”下拉菜单。

什么是集合?

#

集合是表示一组对象的对象,这些对象称为元素。可迭代对象是一种集合。

集合可以为空,也可以包含多个元素。根据目的,集合可以具有不同的结构和实现。以下是一些最常见的集合类型

  • List: 用于按索引读取元素。
  • Set: 用于包含只能出现一次的元素。
  • Map: 用于使用键读取元素。

什么是可迭代对象?

#

Iterable 是可以按顺序访问的元素集合。

在 Dart 中,Iterable 是一个抽象类,这意味着您不能直接实例化它。但是,您可以通过创建新的 ListSet 来创建新的 Iterable

ListSet 都是 Iterable,因此它们具有与 Iterable 类相同的方法和属性。

Map 在内部使用不同的数据结构,具体取决于其实现。例如,HashMap 使用哈希表,其中使用键获取元素(也称为)。也可以通过使用 Map 的 entriesvalues 属性将 Map 的元素读取为 Iterable 对象。

此示例显示了一个 intList,它也是一个 intIterable

dart
Iterable<int> iterable = [1, 2, 3];

List 的区别在于,对于 Iterable,您无法保证按索引读取元素是高效的。与 List 不同,Iterable 没有 [] 运算符。

例如,请考虑以下代码,该代码是无效的

baddart
Iterable<int> iterable = [1, 2, 3];
int value = iterable[1];

如果您使用 [] 读取元素,则编译器会告诉您运算符 '[]' 未为类 Iterable 定义,这意味着您在这种情况下不能使用 [index]

您可以改用 elementAt() 读取元素,它会遍历可迭代对象的元素直到到达该位置。

dart
Iterable<int> iterable = [1, 2, 3];
int value = iterable.elementAt(1);

继续下一节,了解有关如何访问 Iterable 元素的更多信息。

读取元素

#

您可以使用 for-in 循环按顺序读取可迭代对象的元素。

示例:使用 for-in 循环

#

以下示例向您展示如何使用 for-in 循环读取元素。

void main() {
  const iterable = ['Salad', 'Popcorn', 'Toast'];
  for (final element in iterable) {
    print(element);
  }
}

示例:使用 first 和 last

#

在某些情况下,您只想访问 Iterable 的第一个或最后一个元素。

使用 Iterable 类,您无法直接访问元素,因此您不能调用 iterable[0] 来访问第一个元素。相反,您可以使用 first,它会获取第一个元素。

此外,使用 Iterable 类,您不能使用运算符 [] 来访问最后一个元素,但可以使用 last 属性。

void main() {
  Iterable<String> iterable = const ['Salad', 'Popcorn', 'Toast'];
  print('The first element is ${iterable.first}');
  print('The last element is ${iterable.last}');
}

在这个示例中,您了解了如何使用 firstlast 来获取 Iterable 的第一个和最后一个元素。 也可以查找满足条件的第一个元素。下一节将展示如何使用名为 firstWhere() 的方法来实现。

示例:使用 firstWhere()

#

您已经了解到可以按顺序访问 Iterable 的元素,并且可以轻松获取第一个或最后一个元素。

现在,您将学习如何使用 firstWhere() 来查找满足特定条件的第一个元素。此方法需要您传递一个谓词,它是一个函数,如果输入满足特定条件,则返回 true。

dart
String element = iterable.firstWhere((element) => element.length > 5);

例如,如果要查找第一个长度超过 5 个字符的 String,则必须传递一个谓词,当元素大小大于 5 时返回 true。

运行以下示例,查看 firstWhere() 的工作方式。您认为所有函数都会给出相同的结果吗?

bool predicate(String item) {
  return item.length > 5;
}

void main() {
  const items = ['Salad', 'Popcorn', 'Toast', 'Lasagne'];

  // You can find with a simple expression:
  var foundItem1 = items.firstWhere((item) => item.length > 5);
  print(foundItem1);

  // Or try using a function block:
  var foundItem2 = items.firstWhere((item) {
    return item.length > 5;
  });
  print(foundItem2);

  // Or even pass in a function reference:
  var foundItem3 = items.firstWhere(predicate);
  print(foundItem3);

  // You can also use an `orElse` function in case no value is found!
  var foundItem4 = items.firstWhere(
    (item) => item.length > 10,
    orElse: () => 'None!',
  );
  print(foundItem4);
}

在此示例中,您可以看到编写谓词的三种不同方式

  • 作为表达式:测试代码有一行使用箭头语法 (=>)。
  • 作为代码块:测试代码在括号之间有多行,并且有一个 return 语句。
  • 作为函数:测试代码位于一个外部函数中,该函数作为参数传递给 firstWhere() 方法。

没有正确或错误的方法。使用最适合您的方式,并使您的代码更易于阅读和理解。

最后一个示例使用可选的命名参数 orElse 调用 firstWhere(),当找不到元素时提供替代方案。在这种情况下,由于没有元素满足提供的条件,因此返回文本 'None!'

练习:练习编写测试谓词

#

以下练习是一个失败的单元测试,其中包含一个部分完成的代码片段。您的任务是通过编写代码来使测试通过来完成练习。您不需要实现 main()

本练习介绍 singleWhere()。此方法的工作方式与 firstWhere() 类似,但在此情况下,它期望 Iterable 中只有一个元素满足谓词。如果 Iterable 中有多个或没有元素满足谓词条件,则该方法会抛出一个 StateError 异常。

您的目标是为 singleWhere() 实现满足以下条件的谓词

  • 元素包含字符 'a'
  • 元素以字符 'M' 开头。

测试数据中的所有元素都是 字符串;您可以查看类文档以获取帮助。

// Implement the predicate of singleWhere
// with the following conditions
// * The element contains the character `'a'`
// * The element starts with the character `'M'`
String singleWhere(Iterable<String> items) {
  return items.singleWhere(TODO('Implement the outlined predicate.'));
}

// The following code is used to provide feedback on your solution.
// There is no need to read or modify it.
void main() {
  const items = [
    'Salad',
    'Popcorn',
    'Milk',
    'Toast',
    'Sugar',
    'Mozzarella',
    'Tomato',
    'Egg',
    'Water',
  ];

  try {
    final str = singleWhere(items);
    if (str == 'Mozzarella') {
      print('Success. All tests passed!');
    } else {
      print(
        'Tried calling singleWhere, but received $str instead of '
        'the expected value \'Mozzarella\'',
      );
    }
  } on StateError catch (stateError) {
    print(
      'Tried calling singleWhere, but received a StateError: ${stateError.message}. '
      'singleWhere will fail if 0 or many elements match the predicate.',
    );
  } on UnimplementedError {
    print(
      'Tried running `singleWhere`, but received an error. '
      'Did you implement the function?',
    );
  } catch (e) {
    print('Tried calling singleWhere, but received an exception: $e');
  }
}
提示

您的解决方案可能会使用 String 类中的 containsstartsWith 方法。

解决方案
dart
String singleWhere(Iterable<String> items) {
  return items.singleWhere(
          (element) => element.startsWith('M') && element.contains('a'));
}

检查条件

#

使用 Iterable 时,有时您需要验证集合的所有元素是否都满足某个条件。

您可能想尝试使用像这样的 for-in 循环来编写解决方案

baddart
for (final item in items) {
  if (item.length < 5) {
    return false;
  }
}
return true;

但是,您可以使用 every() 方法完成相同的操作

dart
return items.every((item) => item.length >= 5);

使用 every() 方法会使代码更具可读性、更紧凑且更不容易出错。

示例:使用 any() 和 every()

#

Iterable 类提供了两个可用于验证条件的方法

  • any():如果至少有一个元素满足条件,则返回 true。
  • every():如果所有元素都满足条件,则返回 true。

运行此练习以查看它们的实际效果。

void main() {
  const items = ['Salad', 'Popcorn', 'Toast'];

  if (items.any((item) => item.contains('a'))) {
    print('At least one item contains "a"');
  }

  if (items.every((item) => item.length >= 5)) {
    print('All items have length >= 5');
  }
}

在此示例中,any() 验证是否至少有一个元素包含字符 a,而 every() 验证是否所有元素的长度都等于或大于 5。

运行代码后,尝试更改 any() 的谓词,使其返回 false

dart
if (items.any((item) => item.contains('Z'))) {
  print('At least one item contains "Z"');
} else {
  print('No item contains "Z"');
}

您还可以使用 any() 来验证 Iterable 中是否没有元素满足特定条件。

练习:验证可迭代对象是否满足条件

#

以下练习提供使用上一个示例中描述的 any()every() 方法的实践。在这种情况下,您将使用一组用户,由具有成员字段 ageUser 对象表示。

使用 any()every() 来实现两个函数

  • 第 1 部分:实现 anyUserUnder18()
    • 如果至少有一个用户的年龄为 17 岁或更小,则返回 true
  • 第 2 部分:实现 everyUserOver13()
    • 如果所有用户的年龄都为 14 岁或更大,则返回 true
bool anyUserUnder18(Iterable<User> users) {
  // TODO: Implement the anyUserUnder18 function.
}

bool everyUserOver13(Iterable<User> users) {
  // TODO: Implement the everyUserOver13 function.
}

class User {
  final String name;
  final int age;

  User(
    this.name,
    this.age,
  );
}

// The following code is used to provide feedback on your solution.
// There is no need to read or modify it.
void main() {
  final users = [
    User('Alice', 21),
    User('Bob', 17),
    User('Claire', 52),
    User('David', 14),
  ];

  try {
    final out = anyUserUnder18(users);
    if (!out) {
      print('Looks like `anyUserUnder18` is wrong. Keep trying!');
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `anyUserUnder18`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print('Tried running `anyUserUnder18`, but received an exception: $e');
    return;
  }

  try {
    // with only one user older than 18, should be false
    final out = anyUserUnder18([User('Alice', 21)]);
    if (out) {
      print(
          'Looks like `anyUserUnder18` is wrong. What if all users are over 18?');
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `anyUserUnder18`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
      'Tried running `anyUserUnder18([User("Alice", 21)])`, '
      'but received an exception: $e',
    );
    return;
  }

  try {
    final out = everyUserOver13(users);
    if (!out) {
      print(
        'Looks like `everyUserOver13` is wrong. '
        'There are no users under 13!',
      );
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `everyUserOver13`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
      'Tried running `everyUserOver13`, '
      'but received an exception: $e',
    );
    return;
  }

  try {
    final out = everyUserOver13([User('Dan', 12)]);
    if (out) {
      print(
        'Looks like `everyUserOver13` is wrong. '
        'There is at least one user under 13!',
      );
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `everyUserOver13`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
      'Tried running `everyUserOver13([User(\'Dan\', 12)])`, '
      'but received an exception: $e',
    );
    return;
  }

  print('Success. All tests passed!');
}
提示

请记住使用 Iterable 类中的 anyevery 方法。有关使用这些方法的帮助和示例,请参阅前面关于它们的讨论

解决方案
dart
bool anyUserUnder18(Iterable<User> users) {
  return users.any((user) => user.age < 18);
}

bool everyUserOver13(Iterable<User> users) {
  return users.every((user) => user.age > 13);
}

过滤

#

前面的章节介绍了 firstWhere()singleWhere() 等方法,这些方法可以帮助您找到满足特定谓词的元素。

但是,如果您想找到满足特定条件的所有元素,该怎么办?您可以使用 where() 方法来实现。

dart
var evenNumbers = numbers.where((number) => number.isEven);

在此示例中,numbers 包含一个具有多个 int 值的 Iterable,而 where() 查找所有偶数。

where() 的输出是另一个 Iterable,您可以将其用作迭代或应用其他 Iterable 方法。在下一个示例中,where() 的输出直接在 for-in 循环内使用。

dart
var evenNumbers = numbers.where((number) => number.isEven);
for (final number in evenNumbers) {
  print('$number is even');
}

示例:使用 where()

#

运行此示例以查看如何将 where()any() 等其他方法一起使用。

void main() {
  var evenNumbers = const [1, -2, 3, 42].where((number) => number.isEven);

  for (final number in evenNumbers) {
    print('$number is even.');
  }

  if (evenNumbers.any((number) => number.isNegative)) {
    print('evenNumbers contains negative numbers.');
  }

  // If no element satisfies the predicate, the output is empty.
  var largeNumbers = evenNumbers.where((number) => number > 1000);
  if (largeNumbers.isEmpty) {
    print('largeNumbers is empty!');
  }
}

在此示例中,where() 用于查找所有偶数,然后使用 any() 来检查结果是否包含负数。

在示例的后面,再次使用 where() 来查找所有大于 1000 的数字。因为没有,所以结果是一个空的 Iterable

示例:使用 takeWhile

#

takeWhile()skipWhile() 方法也可以帮助您从 Iterable 中筛选元素。

运行此示例,查看 takeWhile()skipWhile() 如何分割包含数字的 Iterable

void main() {
  const numbers = [1, 3, -2, 0, 4, 5];

  var numbersUntilZero = numbers.takeWhile((number) => number != 0);
  print('Numbers until 0: $numbersUntilZero');

  var numbersStartingAtZero = numbers.skipWhile((number) => number != 0);
  print('Numbers starting at 0: $numbersStartingAtZero');
}

在此示例中,takeWhile() 返回一个 Iterable,其中包含满足谓词的元素之前的所有元素。另一方面,skipWhile() 返回一个 Iterable,其中包含第一个满足谓词的元素之后(包括第一个不满足谓词的元素)的所有元素。

运行该示例后,将 takeWhile() 更改为获取元素,直到达到第一个负数。

dart
var numbersUntilNegative =
    numbers.takeWhile((number) => !number.isNegative);

请注意,条件 number.isNegative 使用 ! 取反。

练习:从列表中过滤元素

#

以下练习提供使用上一个练习中的 User 类练习 where() 方法。

使用 where() 来实现两个函数

  • 第 1 部分:实现 filterOutUnder21()
    • 返回一个 Iterable,其中包含所有年龄为 21 岁或以上的用户。
  • 第 2 部分:实现 findShortNamed()
    • 返回一个 Iterable,其中包含所有姓名长度为 3 个字符或更少的用户。
Iterable<User> filterOutUnder21(Iterable<User> users) {
  // TODO: Implement the filterOutUnder21 function.
}

Iterable<User> findShortNamed(Iterable<User> users) {
  // TODO: Implement the findShortNamed function.
}

class User {
  final String name;
  final int age;

  User(
    this.name,
    this.age,
  );
}

// The following code is used to provide feedback on your solution.
// There is no need to read or modify it.
void main() {
  final users = [
    User('Alice', 21),
    User('Bob', 17),
    User('Claire', 52),
    User('Dan', 12),
  ];

  try {
    final out = filterOutUnder21(users);
    if (out.any((user) => user.age < 21) || out.length != 2) {
      print(
        'Looks like `filterOutUnder21` is wrong, there are '
        'exactly two users with age under 21. Keep trying!',
      );
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `filterOutUnder21`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
      'Tried running `filterOutUnder21`, '
      'but received an exception: ${e.runtimeType}',
    );
    return;
  }

  try {
    final out = findShortNamed(users);
    if (out.any((user) => user.name.length > 3) || out.length != 2) {
      print(
        'Looks like `findShortNamed` is wrong, there are '
        'exactly two users with a three letter name. Keep trying!',
      );
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `findShortNamed`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
      'Tried running `findShortNamed`, '
      'but received an exception: ${e.runtimeType}',
    );
    return;
  }

  print('Success. All tests passed!');
}
提示

请记住利用 Iterable 类中的 where 方法。有关使用 where 的帮助和示例,请参阅前面关于它的讨论

解决方案
dart
Iterable<User> filterOutUnder21(Iterable<User> users) {
  return users.where((user) => user.age >= 21);
}

Iterable<User> findShortNamed(Iterable<User> users) {
  return users.where((user) => user.name.length <= 3);
}

映射

#

使用方法 map() 映射 Iterable 使您可以对每个元素应用函数,从而将每个元素替换为新元素。

dart
Iterable<int> output = numbers.map((number) => number * 10);

在此示例中,Iterable 数字的每个元素都乘以 10。

您还可以使用 map() 将元素转换为不同的对象 — 例如,将所有 int 转换为 String,您可以在以下示例中看到

dart
Iterable<String> output = numbers.map((number) => number.toString());

示例:使用 map 更改元素

#

运行此示例,了解如何使用 map()Iterable 的所有元素乘以 2。您认为输出会是什么?

void main() {
  var numbersByTwo = const [1, -2, 3, 42].map((number) => number * 2);
  print('Numbers: $numbersByTwo');
}

练习:映射到不同的类型

#

在前面的示例中,您将 Iterable 的元素乘以 2。该操作的输入和输出都是 intIterable

在本练习中,您的代码会获取一个 UserIterable,并且您需要返回一个 Iterable,其中包含字符串,其中包含每个用户的姓名和年龄。

Iterable 中的每个字符串都必须遵循以下格式:'{name} is {age}'—例如,'Alice is 21'

Iterable<String> getNameAndAges(Iterable<User> users) {
  // TODO: Implement the getNameAndAges function.
}

class User {
  final String name;
  final int age;

  User(
    this.name,
    this.age,
  );
}

// The following code is used to provide feedback on your solution.
// There is no need to read or modify it.
void main() {
  final users = [
    User('Alice', 21),
    User('Bob', 17),
    User('Claire', 52),
  ];

  try {
    final out = getNameAndAges(users).toList();
    if (!_listEquals(out, ['Alice is 21', 'Bob is 17', 'Claire is 52'])) {
      print(
        'Looks like `getNameAndAges` is wrong. Keep trying! '
        'The output was: $out',
      );
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `getNameAndAges`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print('Tried running the function, but received an exception: $e');
    return;
  }

  print('Success. All tests passed!');
}

bool _listEquals<T>(List<T>? a, List<T>? b) {
  if (a == null) return b == null;
  if (b == null || a.length != b.length) return false;
  for (var index = 0; index < a.length; index += 1) {
    if (a[index] != b[index]) return false;
  }
  return true;
}
提示

请记住利用 Iterable 类中的 map 方法。有关使用 map 的帮助和示例,请参阅前面关于它的讨论

要将多个值连接成一个字符串,请考虑使用字符串插值

解决方案
dart
Iterable<String> getNameAndAges(Iterable<User> users) {
  return users.map((user) => '${user.name} is ${user.age}');
}

练习:将所有内容整合在一起

#

是时候在最后一个练习中实践您所学的知识了。

本练习提供了 EmailAddress 类,该类具有一个接受字符串的构造函数。另一个提供的函数是 isValidEmailAddress(),它测试电子邮件地址是否有效。

构造函数/函数类型签名描述
EmailAddress()EmailAddress(String address)为指定的地址创建一个 EmailAddress
isValidEmailAddress()bool isValidEmailAddress(EmailAddress)如果提供的 EmailAddress 有效,则返回 true

编写以下代码

第 1 部分:实现 parseEmailAddresses()

  • 编写函数 parseEmailAddresses(),该函数接收一个包含电子邮件地址的 Iterable<String>,并返回一个 Iterable<EmailAddress>
  • 使用 map() 方法将 String 映射到 EmailAddress
  • 使用构造函数 EmailAddress(String) 创建 EmailAddress 对象。

第二部分:实现 anyInvalidEmailAddress()

  • 编写函数 anyInvalidEmailAddress(),该函数接收一个 Iterable<EmailAddress>,如果 Iterable 中有任何 EmailAddress 无效,则返回 true
  • any() 方法与提供的函数 isValidEmailAddress() 一起使用。

第三部分:实现 validEmailAddresses()

  • 编写函数 validEmailAddresses(),该函数接收一个 Iterable<EmailAddress> 并返回另一个仅包含有效地址的 Iterable<EmailAddress>
  • 使用 where() 方法来过滤 Iterable<EmailAddress>
  • 使用提供的函数 isValidEmailAddress() 来评估 EmailAddress 是否有效。
Iterable<EmailAddress> parseEmailAddresses(Iterable<String> strings) {
  // TODO: Implement the parseEmailAddresses function.
}

bool anyInvalidEmailAddress(Iterable<EmailAddress> emails) {
  // TODO: Implement the anyInvalidEmailAddress function.
}

Iterable<EmailAddress> validEmailAddresses(Iterable<EmailAddress> emails) {
  // TODO: Implement the validEmailAddresses function.
}

class EmailAddress {
  final String address;

  EmailAddress(this.address);

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is EmailAddress && address == other.address;

  @override
  int get hashCode => address.hashCode;

  @override
  String toString() => 'EmailAddress{address: $address}';
}

// The following code is used to provide feedback on your solution.
// There is no need to read or modify it.
void main() {
  const input = [
    '[email protected]',
    'bobgmail.com',
    '[email protected]',
  ];

  const correctInput = ['[email protected]', '[email protected]'];

  bool _listEquals<T>(List<T>? a, List<T>? b) {
    if (a == null) return b == null;
    if (b == null || a.length != b.length) return false;
    for (var index = 0; index < a.length; index += 1) {
      if (a[index] != b[index]) return false;
    }
    return true;
  }

  final Iterable<EmailAddress> emails;
  final Iterable<EmailAddress> correctEmails;
  try {
    emails = parseEmailAddresses(input);
    correctEmails = parseEmailAddresses(correctInput);
    if (emails.isEmpty) {
      print(
        'Tried running `parseEmailAddresses`, but received an empty list.',
      );
      return;
    }
    if (!_listEquals(emails.toList(), [
      EmailAddress('[email protected]'),
      EmailAddress('bobgmail.com'),
      EmailAddress('[email protected]'),
    ])) {
      print('Looks like `parseEmailAddresses` is wrong. Keep trying!');
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `parseEmailAddresses`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
      'Tried running `parseEmailAddresses`, '
      'but received an exception: $e',
    );
    return;
  }

  try {
    final out = anyInvalidEmailAddress(emails);
    if (!out) {
      print(
        'Looks like `anyInvalidEmailAddress` is wrong. Keep trying! '
        'The result should be false with at least one invalid address.',
      );
      return;
    }
    final falseOut = anyInvalidEmailAddress(correctEmails);
    if (falseOut) {
      print(
        'Looks like `anyInvalidEmailAddress` is wrong. Keep trying! '
        'The result should be false with all valid addresses.',
      );
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `anyInvalidEmailAddress`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
        'Tried running `anyInvalidEmailAddress`, but received an exception: $e');
    return;
  }

  try {
    final valid = validEmailAddresses(emails);
    if (emails.isEmpty) {
      print('Tried running `validEmailAddresses`, but received an empty list.');
      return;
    }
    if (!_listEquals(valid.toList(), [
      EmailAddress('[email protected]'),
      EmailAddress('[email protected]'),
    ])) {
      print('Looks like `validEmailAddresses` is wrong. Keep trying!');
      return;
    }
  } on UnimplementedError {
    print(
      'Tried running `validEmailAddresses`, but received an error. '
      'Did you implement the function?',
    );
    return;
  } catch (e) {
    print(
      'Tried running the `validEmailAddresses`, '
      'but received an exception: $e',
    );
    return;
  }

  print('Success. All tests passed!');
}

bool isValidEmailAddress(EmailAddress email) {
  return email.address.contains('@');
}
解决方案
dart
Iterable<EmailAddress> parseEmailAddresses(Iterable<String> strings) {
  return strings.map((s) => EmailAddress(s));
}

bool anyInvalidEmailAddress(Iterable<EmailAddress> emails) {
  return emails.any((email) => !isValidEmailAddress(email));
}

Iterable<EmailAddress> validEmailAddresses(Iterable<EmailAddress> emails) {
  return emails.where((email) => isValidEmailAddress(email));
}

下一步

#

恭喜,您完成了本教程!如果您想了解更多信息,以下是一些后续学习建议