Dart 泛型里该优先用 dynamic 还是 Object?先搞清 raw type 在做什么
时间:2026-08-24 | 作者:极客少年 | 阅读:0引言
先看一段代码:
void main() {
List a = [1, 2, 3];
}
你认为,此段代码中的变量a在运行时是什么类型的?
strict-raw-types
dart静态分析器有一个选项叫做strict-raw-types。先看看什么是strict-raw-types,也就是严格原始类型:
本文件规定了通过静态分析选项启用的“严格原始类型”模式。作为一项静态分析选项,我们仅打算在Dart分析器中实现此功能。在此功能下,省略类型参数的类型被定义为“原始类型”。Dart会使用类型参数的边界来填充此类类型参数;若无边界,则使用
dynamic类型填充。
以上是机翻,英语好的可以看看原文:
此文档详细说明了通过静态分析选项启用的“严格原始类型”模式。作为一种静态分析选项,我们仅打算在Dart分析器中实现此功能。在该功能下,省略了类型参数的类型被定义为“原始类型”。Dart会用其边界填充此类类型参数,如果没有边界,则填充为dynamic。
在实际项目中,我也尝试去理解过,以此来确保我的代码严格遵守dart的类型系统。相信很多人也在网上学习过dart中的dynamic和Object的区别,比如说:
Object是一个类,是所有类的基类,它提供了一些通用方法。需要显式调用方法来操作。dynamic是一种特殊类型,用于表示动态类型,可以在运行时具有不同的类型值。- 使用
Object通常是为了访问通用方法,而使用dynamic通常是为了处理动态的、不确定类型的情况。
其实dynamic 和 Object 之间的微妙差异在`Dart 语言规范中有所体现,以下是最新的 Dart 语言规范第 20.7 节
dynamic 和 Object 之间存在一个微妙差异。根据最新的Dart 语言规范第 20.7 节:
dynamic类型是一种静态类型,和Object一样,它是所有其他类型的超类型。但它与其他类型的不同之处在于,静态分析会假定对其进行的每次成员访问,都存在一个具有能允许该访问的签名的对应成员。
同样的是机翻,说人话就是,dynamic其实和Object一样都是其他类型的超类,只是dynamic类型的变量在静态分析时,调用什么方法或者属性都可以,静态分析器都会假设该成员存在。
看代码:
void main(List<String> args) {
final d = [];
final o = <Object>[];
d.add(Object());
o.add(Object());
d[0].missingMethod(); // 会导致运行时的NoSuchMethodError.
// NoSuchMethodError: Class 'Object' has no instance method 'missingMethod'.
o[0].missingMethod(); // 静态分析器直接报错.
}
以下是原文:
The dynamic type is a static type that serves as a supertype for all other types, similar to Object. However, it diverges from other types in that the static analysis presumes that every member access has a corresponding member with a signature that permits the given access.
所以,dynmaic实际上是静态类型系统的“逃逸口”,dynamic 允许开发者在编译期绕过类型检查,但必须在运行时承担类型不匹配的风险。过度使用会导致代码失去静态类型系统的保护。这也是为什么会有strict-raw-types这条原则的原因。
回到问题
回到文章开头的问题,我们也可以在规范中找到明确的答案:
If no static type annotation has been provided, the type system considers declarations to ha ve type dynamic. If a generic type is used but type arguments are not provided, the type arguments default to type dynamic.This means that given a generic declaration G
. . ., where Pi is a formal type parameter declaration, i ∈ 1..n, the type G is equivalent to G .
翻译过来就是:
如果没有提供静态类型注解,类型系统会认为这些声明的类型为 dynamic。如果使用了泛型类型但没有提供类型参数,那么类型参数默认是 dynamic 类型。这意味着,对于一个泛型声明 G
...,其中 Pi 是形式类型参数声明,i 的取值范围是从 1 到 n,那么类型 G 等同于 G 。
所以回头再看这段代码:
void main() {
List a = [1, 2, 3];
}
开发者常常认为类型推断是从赋值语句的右侧来确定变量a的类型。看起来变量a似乎是List类型。但实际上,Dart会用 dynamic(或者对应类型参数的边界类型)来填充省略的类型参数,比如 List 中的 E。List a; 纯粹是 List; 的简写形式。然后,类型推断是从变量a流向赋值语句右侧的表达式。这在另一个例子中会更加明显:
void main() {
List a = [1, 2, 3]..forEach((e) => print(e.length));
var b = [4, 5, 6]..forEach((e) => print(e.length));
}
第一条语句不会导致任何静态分析错误,因为列表的类型会被推断为List。相反,当对int类型调用length getter时,代码会在运行时抛出no-such-method。
然而,第二条语句允许列表的类型从其元素推断为List,这会导致静态分析类型错误,提示int上未定义length getter。
原始类型(Raw types)还可能导致意外的动态派发(dynamic dispatch):
void main() {
List a = [1, 2, 3]; // 原始类型,等价于 List
a.forEach((e) => print(e.isEven)); // 回调参数 e 的类型是 dynamic
}
开发者可能没有意识到,回调函数的参数e是dynamic类型,对isEven的调用属于动态派发(运行时才会检查方法是否存在)。
启用严格原始类型检查旨在鼓励开发者填充省略的类型参数,避免默认使用dynamic。当唯一合适的类型确实是dynamic时,显式将其作为类型参数(如List)可以避免原始类型,同时让代码中的动态行为更加明确。
结束语
通过以上阅读,我们对strict-raw-types的初衷也有一定的了解,也知道了为什么会有这条原则,希望对日后的开发工作有所帮助。最后,附上如何开记strict-raw-types:
analyzer:
language:
strict-raw-types: true
也希望大家关注我的微信公众号:OpenFlutter,感恩。








