位置:首页 > Dart > Dart 泛型里该优先用 dynamic 还是 Object?先搞清 raw type 在做什么

Dart 泛型里该优先用 dynamic 还是 Object?先搞清 raw type 在做什么

时间:2026-08-24  |  作者:极客少年  |  阅读:0

引言

先看一段代码:

在Dart泛型中应该优先使用dynamic还是Object?

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; 的简写形式。然后,类型推断是从变量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,感恩。

《夸克》非常好用的免费AI浏览器

下载APP查看

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多