一门语言之所以被称为类型可选,仅当以下条件成立时:

  • 类型在语法层面是可选的
  • 类型对运行时语义没有影响

如果程序中的变量没有显式地给予类型,则它的类型就是dynamicdynamic类型是一种特殊的类型,它告知类型检查器不要对变量本身的操作或给变量赋值等行为发出警告。相比之下,显式使用类型Object,意味着我们真正期望此变量中的每个对象都必须是有效的值。这两种情况看起来类似,但当我们对Object类型的表达式进行操作时,如果尝试使用不被所有对象支持的方法,则我们会得到警告。相反,使用类型dynamic能有效地使静态类型检查安静下来,它告诉类型检查器,我们明确知道自己在做什么。

Dart的接口类型给对象定义了一组可用的方法。Dart并没有声明接口的语法,接口是通过类声明引入的。每个类都引入了一个隐性接口,其签名是基于类的成员。对于传统接口声明的需求,我们定义一个纯抽象类就可以轻松解决。任何类都可以实现一个接口,即使该接口与类完全没有关联。

类不会继承implements子句内任何接口的实现,implements子句所做的,是使类与它所列出的接口建立明确的子类关系。这种关系会影响Dart类型检查器及运行时的行为。

当对象从一个变量传递到另一个变量时,会触发类型检查。这样的值传递发生于:

  • 执行赋值操作
  • 传递实参给函数
  • 函数返回结果

严格来说,在值传递的过程中,类型检查器并不强制要求变量之间有父接口关系。它检查的是可赋值性。可赋值性比继承关系更宽松。只要两个类型之间存在父子关系,Dart就认为它们可以相互赋值

Dart的类可以是泛化的,也就是说,它们能通过类型进行参数设置。泛型类可以指定实际的类型参数:List<String> l;这种类型被称为参数化类型。但是,给泛型类提供类型参数并不是必须的,如果我们选择使用泛型类且不提供类型参数,则类型dynamic类型将会被隐性使用,代替所有缺失的类型参数。

如果提供了错误数量的类型参数,则所有的类型参数都将失效。Map<String> m将引发一个静态警告,除了警告,它会被视为Map<dynamic,dynamic>

在Dart中,List<Apple>确实是一个List<Fruit>。更普遍的说法,如果G是一个由n个类型参数的泛型类,而Si是Ti的子类型,那么G <: G,其中T<:S表示T是S的子类型。这种行为被称为协变。

给定Ft和Fs=(S1,..,Sn)->S两个函数类型,在什么情况下可以说Ft是Fs的子类型呢?

类型理论告诉我们,上述论点成立的条件是T是S的子类型,而Si是Ti的子类型。即Fs的每个形式参数都必须是Ft中对应参数的子类型。需要注意的是,子类型关系的方向是反转的,这种现象被称为逆变,其中的逻辑是不可避免的。假设我们把一个类型为Ft的函数f传递给函数h(g)。如果形式参数g的类型是Fs,那么将Si子类型的实际参数传递给它可能是安全的。由此得出结论,函数f的每个类型为Ti的形式参数必须可以接收任意Si的子类型,因此Ti必须是Si的父类型。

严格的类型检查并不是Dart所追寻的目标。所以,Dart很自然地放弃了我们前面描述的类型推导,转而遵循最普遍的(尽管是错误的)直觉并同时采取协变规则,具体可以表述为:如果T是S的子类型,而Ti是Si的子类型,那么Ft就是Fs的子类型。而实际上,Dart的规则还有一点不同。如果T可以赋值给S并且Ti可以赋值给Si,那么Ft就是Fs的子类型。

函数类型Ft=(T1,...,Tn,[Tn+1,...,Tk]) -> TFs=(S1,...,Sj,[Sj+1,...,Sm]) -> S的子类型的条件是:

  • j>=n 且m<=k
  • 对i从1到m,Ti可指派给Si
  • S为void或T可指派给S

子类型需要n个必填参数,而父类型有j个必填参数。当类型为Ft的函数ft用在需要类型Fs的地方时,它总会被传入至少j个参数。因为Ft需要n个必填参数,所以第一项要求j>=n,使得ft保证会获得足够的必填参数。可能传入参数的最大数量是m,即Fs的总参数个数。函数ft将接受多达k个参数,其中有n个必填及k-n个可选。只要m不大于k,则传递给ft的参数个数就是可接受的。实际参数的类型必须是可以指派给ft的形式参数。因为实际最多只会传递m个参数,所以只要求前m个参数类型的可指派性。Dart允许你标识那种不打算返回结果的函数,它们可以使用特殊的返回类型void。如果Fs返回void,则类型检查器会假定它的返回结果不会被使用,因此也不会影响我们这里的子类关系。

函数类型Ft=(T1,...,Tn,{Tx1 x1,...,Txk xk}) -> TFs=(S1,...,Sj,{Sy1 y1,...,Sym ym})->S的子类型的条件是:

  • S为void或T可指派给S
  • 对i从1到n,Ti可以指派给Si
  • k>=m且对i从1到m,yi属于{x1,..,xk}
  • 对于所有的yi属于{y1,...,ym},如果yi=xj,则Txj可以指派给Syi

类型检测是用来测试对象是否属于讴歌类型的表达式e is T。强制类型转换同样是对一个表达式求值并测试结果对象是否属于某个类型,不同的是,它们的结果并不明确。相反,如果测试失败,则强制类型转换会抛出一个CastError,否则它将返回未改动的被检测的对象。t is T ? t : throw new CastError();

在检查模式中,每次发生值传递都会触发动态检查。这意味着,在每次的参数传递中,函数或方法返回结果以赋值操作时,Dart都自动执行一次动态类型测试。检查模式确保了赋给变量的动态值是变量的静态类型的成员。同样,实际参数的动态类型也会与形式参数的静态类型进行对比检测,而函数结果的动态类型则会与函数声明的返回类型进行对比检测。

检查模式的行为不同于静态类型检查规则。当赋值被执行静态检查时,我们使用可指派性原则,即只要双方存在子类或父类关系就允许赋值。而在检查模式所实现的动态检查中,赋值操作中的值的真实类型必须是变量的静态类型的子类,或者是null。

在运行时,类型参数是具体化的。当泛型类被实例化时,在运行时传递与储存的都是实际的类型参数,因此,new List<String>()创建的实例的类实际上市不同于new List<Object>()所创建实例的类的。

泛型的具体化也只在程序试图观察或确定运行时类型结构时才会影响程序的行为。这些情况包括:

  • 使用类型测试,强制转换或调用runtimeType来查询对象的类型
  • 给泛型类型的构造函数传递实际类型参数以设置对象的运行时类型
  • 使用反射检查或设置变量或函数的类型
  • 使用检查模式
  • 通过给函数对象的签名添加类型注解来确定其具体化类型

未定义的类型可能由多种原因导致:输入错误、忘记导入或在类型实际定义前就使用了它。在某些情况下,类型注释使用的名称可能表示一个已存在的函数或变量。在任何名称不表示实际类型声明的情况下,这就意味着我们有一个malformed类型。未定义类型是malformed类型的一种特殊形式。歧义类型指将名称相同的不同类型声明导入到同一个作用域,导致类型没有确切的定义,也被称为malformed类型。

Dart类型系统非严格性的不同来源,它们是:

  • 泛型的协变
  • 可赋值性规则中的类型向下转换
  • 函数类型的可赋值性
  • 隐私与接口类型的相互作用

Dart的类型是基于接口的,而不是基于实现的。每个类都引入了一个可被其他类实现的接口,即使它们的实现与本类完全无关。

Dart支持可选类型。使用类型与不使用类型的代码可以随意混合。Dart的类型注解对运行时行为不会产生任何影响,但对具体化类型的使用会产生影响。Dart非常慎重地不使用严格的类型系统。相反,出于为开发者的生产力考虑的原因,Dart试探性地从平衡灵活性与安全性的角度去设计类型规则。违反类型规则所导致的警告可以被选择性地禁用。类型警告从不妨碍程序的编译与运行。

在开发时,检查模式的使用引入了动态类型检查,使得类型注解被解释为断言。

Dart包含了协变的泛型类型。泛型与父接口声明是具体化的,并可以在运行时进行检测。

results matching ""

    No results matching ""