修复常见的类型问题
如果你在类型检查方面遇到问题, 此页面可以提供帮助。要了解更多信息,请阅读有关 Dart 的类型系统 的内容, 并查看 这些其他资源 。
故障排除
#Dart 强制执行健全的类型系统。 这意味着你不能编写变量的值与其静态类型不同的代码。 具有 int
类型的变量不能存储带有小数位的数字。 Dart 在 编译时 和 运行时 检查变量值与其类型是否匹配。
你不会遇到变量中存储的值与其静态类型不同的情况。与大多数现代静态类型语言一样,Dart 通过结合使用 静态(编译时) 和 动态(运行时) 检查来实现这一点。
例如,以下类型错误在编译时被检测到:
List<int> numbers = [1, 2, 3];
List<String> string = numbers;
由于 List<int>
和 List<String>
都不是对方的子类型, Dart 在静态分析阶段就排除了这种情况。
你可以在以下部分看到其他静态分析错误示例以及其他错误类型。
没有类型错误
#如果你没有看到预期的错误或警告, 请确保你正在使用最新版本的 Dart,并且你已正确配置了你的 IDE 或编辑器 。
你还可以使用命令行和 dart analyze
命令对程序运行分析。
要验证分析是否按预期工作, 请尝试将以下代码添加到 Dart 文件中。
bool b = [0][0];
如果配置正确,分析器会产生以下错误:
error - 'int' 类型的值无法赋值给 'bool' 类型的变量。尝试更改变量的类型,或将右侧类型强制转换为 'bool'。 - invalid_assignment
静态错误和警告
#本节介绍如何修复你可能从分析器或 IDE 中看到的某些错误和警告。
静态分析无法捕获所有错误。 有关修复仅在运行时出现的错误的帮助, 请参阅 运行时错误 。
未定义成员
#error - 类型 '...' 未定义 <member> '...' - undefined_<member>
这些错误可能在以下情况下出现:
- 变量在静态上已知为某个超类型,但代码假定为子类型。
- 泛型类具有有界类型参数,但类的实例创建表达式省略了类型参数。
示例 1:变量在静态上已知为某个超类型,但代码假定为子类型
#在以下代码中,分析器抱怨 context2D
未定义:
var canvas = querySelector('canvas')!;
canvas.context2D.lineTo(x, y);
error - 获取器 'context2D' 未为 'Element' 类型定义。尝试导入定义 'context2D' 的库,将名称更正为现有获取器的名称,或定义名为 'context2D' 的获取器或字段。 - undefined_getter
修复:用显式类型声明或向下转换替换成员的定义
#querySelector()
的返回类型是 Element?
( !
将其转换为 Element
), 但代码假定它是子类型 CanvasElement
(它定义了 context2D
)。 canvas
字段声明为 var
, 这允许 Dart 推断 canvas
为 Element
。
你可以通过显式向下转换来修复此错误:
var canvas = querySelector('canvas') as CanvasElement;
canvas.context2D.lineTo(x, y);
否则,在无法使用单一类型的情况下使用 dynamic
:
dynamic canvasOrImg = querySelector('canvas, img');
var width = canvasOrImg.width;
示例 2:省略的类型参数默认为其类型边界
#考虑以下具有扩展 Iterable
的有界类型参数的泛型类:
class C<T extends Iterable> {
final T collection;
C(this.collection);
}
以下代码创建此类的新的实例(省略类型参数)并访问其 collection
成员:
var c = C(Iterable.empty()).collection;
c.add(2);
error - 方法 'add' 未为 'Iterable' 类型定义。尝试将名称更正为现有方法的名称,或定义名为 'add' 的方法。 - undefined_method
虽然List 类型具有 add()
方法,但Iterable 没有。
修复:指定类型参数或修复下游错误
#当泛型类在没有显式类型参数的情况下实例化时,如果显式给出了类型边界(在此示例中为 Iterable
),则每个类型参数默认为其类型边界,否则默认为 dynamic
。
你需要针对具体情况来修复此类错误。了解原始设计意图会有所帮助。
显式传递类型参数是帮助识别类型错误的有效方法。例如,如果将代码更改为指定 List
作为类型参数,则分析器可以检测构造函数参数中的类型不匹配。通过提供适当类型的构造函数参数来修复错误,例如列表文字:
var c = C <List>([]) .collection;
c.add(2);
无效的方法覆盖
#error - '...' 不是 '...' 的有效覆盖 - invalid_override
这些错误通常发生在子类通过指定原始类的子类来收紧方法的参数类型时。
示例
#在以下示例中, add()
方法的参数类型为 int
,它是 num
的子类型, num
是父类中使用的参数类型。
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)') 不是 'NumberAdder.add' ('num Function(num, num)') 的有效覆盖。 - invalid_override
考虑以下场景,其中浮点值传递给 MyAdder
:
NumberAdder adder = MyAdder();
adder.add(1.2, 3.4);
如果允许覆盖,代码将在运行时引发错误。
修复:拓宽方法的参数类型
#子类的方法应该接受超类的方法所接受的每个对象。
通过拓宽子类中的类型来修复示例:
abstract class NumberAdder {
num add(num a, num b);
}
class MyAdder extends NumberAdder {
@override
num add(num a, num b) => a + b;
}
更多信息,请参阅 覆盖方法时使用正确的输入参数类型 。
缺少类型参数
#error - '...' 不是 '...' 的有效覆盖 - invalid_override
示例
#在以下示例中, Subclass
扩展了 Superclass<T>
但没有指定类型参数。分析器推断为 Subclass<dynamic>
,这导致在 method(int)
上出现无效覆盖错误。
class Superclass<T> {
void method(T param) { ... }
}
class Subclass extends Superclass {
@override
void method(int param) { ... }
}
error - 'Subclass.method' ('void Function(int)') 不是 'Superclass.method' ('void Function(dynamic)') 的有效覆盖。 - invalid_override
修复:为泛型子类指定类型参数
#当泛型子类忽略指定类型参数时, 分析器会推断为 dynamic
类型。这很可能会导致错误。
你可以通过在子类上指定类型来修复示例:
class Superclass<T> {
void method(T param) { ... }
}
class Subclass extends Superclass<int> {
@override
void method(int param) { ... }
}
考虑在严格的原始类型模式下使用分析器, 这确保你的代码指定了泛型类型参数。 以下是在你的项目 analysis_options.yaml
文件中启用严格原始类型的示例:
analyzer:
language:
strict-raw-types: true
要了解有关自定义分析器行为的更多信息, 请参阅 自定义静态分析 。
集合元素类型意外
#error - '...' 类型的数值无法赋值给 '...' 类型的变量 - invalid_assignment
当你创建一个简单的动态集合并且分析器以你意想不到的方式推断类型时,有时会发生这种情况。当你稍后添加不同类型的数值时,分析器会报告一个问题。
示例
#以下代码用几个(String
, int
)对初始化一个映射。分析器推断该映射的类型为 <String, int>
,但代码似乎假设为 <String, dynamic>
或 <String, num>
。当代码添加(String
, double
)对时,分析器会报错:
// 推断为 Map<String, int>
var map = {'a': 1, 'b': 2, 'c': 3};
map['d'] = 1.5;
error - 'double' 类型的数值无法赋值给 'int' 类型的变量。尝试更改变量的类型,或将右侧类型强制转换为 'int'。 - invalid_assignment
修复:显式指定类型
#可以通过显式将映射的类型定义为 <String, num>
来修复此示例。
var map = <String, num>{'a': 1, 'b': 2, 'c': 3};
map['d'] = 1.5;
或者,如果你希望此映射接受任何值,请将其类型指定为 <String, dynamic>
。
构造函数初始化列表 super()
调用
#error - 超级构造函数调用必须是初始化列表中的最后一个:'...'。 - super_invocation_not_last
当 super()
调用不是构造函数初始化列表中的最后一个时,会发生此错误。
示例
#HoneyBadger(Eats food, String name)
: super(food) ,
_name = name { ... }
error - 超级构造函数调用必须是初始化列表中的最后一个:'Animal'。 - super_invocation_not_last
修复:将 super()
调用放在最后
#如果编译器依赖于 super()
调用出现在最后,则可以生成更简单的代码。
通过移动 super()
调用来修复此错误:
HoneyBadger(Eats food, String name)
: _name = name,
super(food) { ... }
参数类型 ... 无法赋值给参数类型 ...
#error - 参数类型 '...' 无法赋值给参数类型 '...'。 - argument_type_not_assignable
在 Dart 1.x 中, dynamic
既是顶级类型(所有类型的超类型)又是底层类型(所有类型的子类型),具体取决于上下文。这意味着可以将例如参数类型为 String
的函数分配到期望参数类型为 dynamic
的函数类型的位置。
但是,在 Dart 2 中,使用除 dynamic
(或其他顶级类型,例如 Object?
)以外的参数类型会导致编译时错误。
示例
#void filterValues(bool Function(dynamic) filter) {}
filterValues((String x) => x.contains('Hello'));
error - 参数类型 'bool Function(String)' 无法赋值给参数类型 'bool Function(dynamic)'。 - argument_type_not_assignable
修复:添加类型参数 或 显式从 dynamic 强制转换
#如果可能,请通过添加类型参数来避免此错误:
void filterValues <T>(bool Function(T) filter) {}
filterValues <String>((x) => x.contains('Hello'));
否则使用强制转换:
void filterValues(bool Function(dynamic) filter) {}
filterValues((x) => (x as String).contains('Hello'));
类型推断错误
#在极少数情况下,Dart 的类型推断可能会为泛型构造函数调用中的函数文字参数推断错误的类型。这主要影响 Iterable.fold
。
示例
#在以下代码中, 类型推断将推断 a
的类型为 Null
:
var ints = [1, 2, 3];
var maximumOrNull = ints.fold(null, (a, b) => a == null || a < b ? b : a);
修复:提供适当的类型作为显式类型参数
#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
具有冲突的泛型接口。一些成员的有效覆盖定义将是不可能的。
abstract class C implements List<int>, Iterable<num> {}
修复:使用一致的泛型或避免重复传递接口
#abstract class C implements List<int> {}
运行时错误
#本节中讨论的错误是在 运行时 报告的。
无效的强制转换
#为了确保类型安全,Dart 需要在某些情况下插入运行时检查。考虑以下 assumeStrings
方法:
void assumeStrings(dynamic objects) {
List<String> strings = objects; // 运行时向下转换检查
String string = strings[0]; // 期望 String 值
}
对 strings
的赋值是隐式地将 dynamic
向下转换为 List<String>
(就像你写了 as List<String>
一样),因此,如果在运行时你传入 objects
的值是 List<String>
,则转换成功。
否则,转换将在运行时失败:
assumeStrings(<int>[1, 2, 3]);
Exception: 类型 'List<int>' 不是类型 'List<String>' 的子类型
修复:收紧或更正类型
#有时,缺乏类型,尤其是在空集合的情况下,意味着会创建一个 <dynamic>
集合,而不是你预期的类型化集合。添加显式类型参数可以提供帮助:
var list = <String>[];
list.add('a string');
list.add('another');
assumeStrings(list);
你也可以更精确地为局部变量设置类型,并让推断提供帮助:
List<String> list = [];
list.add('a string');
list.add('another');
assumeStrings(list);
在处理你未创建的集合(例如来自 JSON 或外部数据源)的情况下,可以使用 Iterable
实现(例如 List
)提供的 cast() 方法。
以下是一个首选解决方案的示例:收紧对象的类型。
Map<String, dynamic> json = fetchFromExternalSource();
var names = json['names'] as List;
assumeStrings(names.cast<String>());
附录
#协变关键字
#一些(很少使用的)编码模式依赖于通过使用子类型覆盖参数类型来收紧类型,这是无效的。在这种情况下,可以使用 covariant
关键字来告诉分析器你故意这样做。这将删除静态错误,而是检查运行时无效的参数类型。
以下显示了如何使用 covariant
:
class Animal {
void chase(Animal x) { ... }
}
class Mouse extends Animal { ... }
class Cat extends Animal {
@override
void chase(covariant Mouse x) { ... }
}
尽管此示例显示了在子类型中使用 covariant
, 但 covariant
关键字可以放置在超类或子类方法中。 通常,超类方法是放置它的最佳位置。 covariant
关键字应用于单个参数,并且也支持 setter 和字段。
除非另有说明,否则本网站上的文档反映的是 Dart 3.6.0。页面最后更新于 2025-02-05。 查看源代码 或 报告问题.