目录

修复常见的类型问题

如果您在类型检查时遇到问题,此页面可以提供帮助。要了解更多信息,请阅读Dart 的类型系统,并查看其他资源

问题排查

#

Dart 强制执行健全的类型系统。这意味着您不能编写变量的值与其静态类型不同的代码。类型为 int 的变量不能存储带小数点的数字。Dart 在编译时运行时检查变量值是否与其类型匹配。

您不会遇到变量中存储的值与变量的静态类型不同的情况。与大多数现代静态类型语言一样,Dart 通过静态(编译时)动态(运行时)检查的组合来实现这一点。

例如,以下类型错误在编译时被检测到

✗ 静态分析:失败dart
List<int> numbers = [1, 2, 3];
List<String> string = numbers;

由于 List<int>List<String> 都不是彼此的子类型,因此 Dart 在静态上排除了这种情况。

您可以在以下部分中看到静态分析错误和其他错误类型的其他示例。

无类型错误

#

如果您没有看到预期的错误或警告,请确保您使用的是最新版本的 Dart,并且您已正确配置您的IDE 或编辑器

您还可以使用命令行和 dart analyze 命令在您的程序上运行分析。

要验证分析是否按预期工作,请尝试将以下代码添加到 Dart 文件中。

✗ 静态分析:失败dart
bool b = [0][0];

如果配置正确,分析器会生成以下错误

error - A value of type 'int' can't be assigned to a variable of type 'bool'. Try changing the type of the variable, or casting the right-hand type to 'bool'. - invalid_assignment

静态错误和警告

#

本节介绍如何修复从分析器或 IDE 中看到的一些错误和警告。

静态分析无法捕获所有错误。有关修复仅在运行时出现的错误的帮助,请参阅运行时错误

未定义的成员

#
error - The <member> '...' isn't defined for the type '...' - undefined_<member>

这些错误可能在以下情况下出现

  • 已知变量在静态上是某个超类型,但代码假定为子类型。
  • 泛型类具有有界类型参数,但该类的实例创建表达式省略了类型参数。

示例 1:已知变量在静态上是某个超类型,但代码假定为子类型

#

在以下代码中,分析器会抱怨 context2D 未定义

✗ 静态分析:失败dart
var canvas = querySelector('canvas')!;
canvas.context2D.lineTo(x, y);
error - The getter 'context2D' isn't defined for the type 'Element'. Try importing the library that defines 'context2D', correcting the name to the name of an existing getter, or defining a getter or field named 'context2D'. - undefined_getter

修复:用显式类型声明或向下转型替换成员的定义

#

querySelector() 的返回类型是 Element?! 将其转换为 Element),但代码假定它是子类型 CanvasElement(定义了 context2D)。canvas 字段声明为 var,这允许 Dart 推断 canvasElement

您可以使用显式向下转型来修复此错误

✔ 静态分析:成功dart
var canvas = querySelector('canvas') as CanvasElement;
canvas.context2D.lineTo(x, y);

否则,在无法使用单一类型的情况下使用 dynamic

✔ 静态分析:成功dart
dynamic canvasOrImg = querySelector('canvas, img');
var width = canvasOrImg.width;

示例 2:省略的类型参数默认为其类型边界

#

考虑以下具有扩展 Iterable有界类型参数泛型类

dart
class C<T extends Iterable> {
  final T collection;
  C(this.collection);
}

以下代码创建此类的新实例(省略类型参数)并访问其 collection 成员

✗ 静态分析:失败dart
var c = C(Iterable.empty()).collection;
c.add(2);
error - The method 'add' isn't defined for the type 'Iterable'. Try correcting the name to the name of an existing method, or defining a method named 'add'. - undefined_method

虽然 List 类型具有 add() 方法,但 Iterable 没有。

修复:指定类型参数或修复下游错误

#

当实例化泛型类时没有显式类型参数时,如果显式给出,则每个类型参数默认为其类型边界(本例中为 Iterable),否则默认为 dynamic

您需要根据具体情况来修复此类错误。了解原始设计意图会有所帮助。

显式传递类型参数是帮助识别类型错误的一种有效方法。例如,如果您更改代码以指定 List 作为类型参数,则分析器可以检测到构造函数参数中的类型不匹配。通过提供适当类型的构造函数参数来修复错误,例如列表字面量

✔ 静态分析:成功dart
var c = C<List>([]).collection;
c.add(2);

无效的方法覆盖

#
error - '...'  isn't a valid override of '...' - invalid_override

当子类通过指定原始类的子类来收紧方法的参数类型时,通常会发生这些错误。

示例

#

在以下示例中,add() 方法的参数类型为 int,它是 num 的子类型,而 num 是父类中使用的参数类型。

✗ 静态分析:失败dart
abstract class NumberAdder {
  num add(num a, num b);
}

class MyAdder extends NumberAdder {
  @override
  num add(int a, int b) => a + b;
}
error - 'MyAdder.add' ('num Function(int, int)') isn't a valid override of 'NumberAdder.add' ('num Function(num, num)'). - invalid_override

考虑以下将浮点值传递给 MyAdder 的场景

✗ 运行时:失败dart
NumberAdder adder = MyAdder();
adder.add(1.2, 3.4);

如果允许覆盖,则代码会在运行时引发错误。

修复:扩大方法的参数类型

#

子类的方法应接受超类的方法接受的每个对象。

通过扩大子类中的类型来修复示例

✔ 静态分析:成功dart
abstract class NumberAdder {
  num add(num a, num b);
}

class MyAdder extends NumberAdder {
  @override
  num add(num a, num b) => a + b;
}

有关详细信息,请参阅重写方法时使用正确的输入参数类型


缺少类型参数

#
error - '...'  isn't a valid override of '...' - invalid_override

示例

#

在以下示例中,Subclass 扩展了 Superclass<T>,但未指定类型参数。分析器会推断出 Subclass<dynamic>,这会导致 method(int) 上的无效覆盖错误。

✗ 静态分析:失败dart
class Superclass<T> {
  void method(T param) { ... }
}

class Subclass extends Superclass {
  @override
  void method(int param) { ... }
}
error - 'Subclass.method' ('void Function(int)') isn't a valid override of 'Superclass.method' ('void Function(dynamic)'). - invalid_override

修复:为泛型子类指定类型参数

#

当泛型子类忽略指定类型参数时,分析器会推断出 dynamic 类型。这很可能会导致错误。

您可以通过在子类上指定类型来修复示例

✔ 静态分析:成功dart
class Superclass<T> {
  void method(T param) { ... }
}

class Subclass extends Superclass<int> {
  @override
  void method(int param) { ... }
}

考虑在 严格的原始类型 模式下使用分析器,这可确保您的代码指定泛型类型参数。以下是在项目的 analysis_options.yaml 文件中启用严格的原始类型的示例

yaml
analyzer:
  language:
    strict-raw-types: true

要了解有关自定义分析器行为的更多信息,请参阅自定义静态分析


意外的集合元素类型

#
error - A value of type '...' can't be assigned to a variable of type '...' - invalid_assignment

当您创建一个简单的动态集合,并且分析器以您不期望的方式推断类型时,有时会发生这种情况。当您稍后添加不同类型的值时,分析器会报告问题。

示例

#

以下代码使用几个 (String, int) 对初始化一个 Map。分析器推断该 Map 的类型为 <String, int>,但代码似乎假设为 <String, dynamic><String, num>。当代码添加一个 (String, double) 对时,分析器会报错

✗ 静态分析:失败dart
// Inferred as Map<String, int>
var map = {'a': 1, 'b': 2, 'c': 3};
map['d'] = 1.5;
error - A value of type 'double' can't be assigned to a variable of type 'int'. Try changing the type of the variable, or casting the right-hand type to 'int'. - invalid_assignment

修复:显式指定类型

#

可以通过显式地将 Map 的类型定义为 <String, num> 来修复此示例。

✔ 静态分析:成功dart
var map = <String, num>{'a': 1, 'b': 2, 'c': 3};
map['d'] = 1.5;

或者,如果您希望此 Map 接受任何值,请将类型指定为 <String, dynamic>


构造函数初始化列表 super() 调用

#
error - The superconstructor call must be last in an initializer list: '...'. - super_invocation_not_last

super() 调用不是构造函数初始化列表中的最后一个时,会发生此错误。

示例

#
✗ 静态分析:失败dart
HoneyBadger(Eats food, String name)
    : super(food),
      _name = name { ... }
error - The superconstructor call must be last in an initializer list: 'Animal'. - super_invocation_not_last

修复:将 super() 调用放在最后

#

如果编译器依赖于 super() 调用出现在最后,它可以生成更简单的代码。

通过移动 super() 调用来修复此错误

✔ 静态分析:成功dart
HoneyBadger(Eats food, String name)
    : _name = name,
      super(food) { ... }

参数类型 ... 不能分配给参数类型 ...

#
error - The argument type '...' can't be assigned to the parameter type '...'. - argument_type_not_assignable

在 Dart 1.x 中,dynamic 既是顶层类型(所有类型的超类型),又是底层类型(所有类型的子类型),具体取决于上下文。这意味着例如将一个具有 String 类型参数的函数赋值给一个期望具有 dynamic 类型参数的函数类型的位置是有效的。

但是,在 Dart 2 中,使用 dynamic 以外的参数类型(或其他顶层类型,例如 Object?)会导致编译时错误。

示例

#
✗ 静态分析:失败dart
void filterValues(bool Function(dynamic) filter) {}
filterValues((String x) => x.contains('Hello'));
error - The argument type 'bool Function(String)' can't be assigned to the parameter type 'bool Function(dynamic)'. - argument_type_not_assignable

修复:添加类型参数显式地从 dynamic 转换

#

如果可能,通过添加类型参数来避免此错误

✔ 静态分析:成功dart
void filterValues<T>(bool Function(T) filter) {}
filterValues<String>((x) => x.contains('Hello'));

否则,请使用类型转换

✔ 静态分析:成功dart
void filterValues(bool Function(dynamic) filter) {}
filterValues((x) => (x as String).contains('Hello'));

不正确的类型推断

#

在极少数情况下,Dart 的类型推断可能会为泛型构造函数调用中的函数字面量参数推断错误的类型。这主要影响 Iterable.fold

示例

#

在以下代码中,类型推断会推断出 a 的类型为 Null

✗ 静态分析:失败dart
var ints = [1, 2, 3];
var maximumOrNull = ints.fold(null, (a, b) => a == null || a < b ? b : a);

修复:提供适当的类型作为显式类型参数

#
✔ 静态分析:成功dart
var ints = [1, 2, 3];
var maximumOrNull =
    ints.fold<int?>(null, (a, b) => a == null || a < b ? b : a);

冲突的超接口

#

一个 implements 多个超接口的类必须能够为每个超接口的每个成员实现有效的重写。具有给定名称的每个成员都需要跨超接口的兼容签名。

超接口不得包含冲突的泛型。一个类不能同时实现 C<A>C<B>,包括间接超接口。

示例

#

在以下代码中,类 C 具有冲突的泛型接口。某些成员的有效重写的定义将是不可能的。

✗ 静态分析:失败dart
abstract class C implements List<int>, Iterable<num> {}

修复:使用一致的泛型或避免重复传递接口

#
✔ 静态分析:成功dart
abstract class C implements List<int> {}

运行时错误

#

本节中讨论的错误在运行时报告。

无效的类型转换

#

为了确保类型安全,Dart 在某些情况下需要插入运行时检查。考虑以下 assumeStrings 方法

✔ 静态分析:成功dart
void assumeStrings(dynamic objects) {
  List<String> strings = objects; // Runtime downcast check
  String string = strings[0]; // Expect a String value
}

strings 的赋值是隐式地将 dynamic向下转型List<String>(就像您编写了 as List<String> 一样),因此如果您在运行时传递给 objects 的值是 List<String>,则转换会成功。

否则,转换将在运行时失败

✗ 运行时:失败dart
assumeStrings(<int>[1, 2, 3]);
Exception: type 'List<int>' is not a subtype of type 'List<String>'

修复:收紧或更正类型

#

有时,缺少类型,尤其是在空集合的情况下,意味着创建了一个 <dynamic> 集合,而不是您预期的类型化集合。添加显式类型参数可以帮助解决此问题

runtime-successdart
var list = <String>[];
list.add('a string');
list.add('another');
assumeStrings(list);

您也可以更精确地键入局部变量,并让推断提供帮助

runtime-successdart
List<String> list = [];
list.add('a string');
list.add('another');
assumeStrings(list);

如果您正在使用一个您没有创建的集合,例如来自 JSON 或外部数据源的集合,则可以使用 Iterable 实现(例如 List)提供的 cast() 方法。

这是一个首选解决方案的示例:收紧对象的类型。

runtime-successdart
Map<String, dynamic> json = fetchFromExternalSource();
var names = json['names'] as List;
assumeStrings(names.cast<String>());

附录

#

covariant 关键字

#

一些(很少使用的)编码模式依赖于通过使用子类型覆盖参数的类型来收紧类型,这是无效的。在这种情况下,您可以使用 covariant 关键字来告诉分析器您是有意这样做的。这将消除静态错误,而是在运行时检查无效的参数类型。

以下显示了您如何使用 covariant

✔ 静态分析:成功dart
class Animal {
  void chase(Animal x) { ... }
}

class Mouse extends Animal { ... }

class Cat extends Animal {
  @override
  void chase(covariant Mouse x) { ... }
}

尽管此示例显示在子类型中使用 covariant,但 covariant 关键字可以放置在超类或子类方法中。通常,超类方法是放置它的最佳位置。covariant 关键字应用于单个参数,并且也支持设置器和字段。