Dart中的可选类型是如何工作的

开发 前端
Dart编程语言中最具创新的特性之一是可选类型的使用。本文件旨在解释可选类型是如何工作的。

Dart 语言是动态类型的。你可以编写、运行没有类型标注的任何程序,就像你使用Javascript的方式。

51CTO推荐专题:Google Dart新结构化编程语言

你可以在程序中添加类型标注:

◆ 添加类型不会阻止你程序的编译和运行——即使标注不完整或错误。

◆ 不论你添加了什么类型标注,你的程序都具有完全相同的语义。

然而,添加类型标注可以使你获益。类型提供了下面这些好处:

◆ 给人看的文档。明智地放置类型标注可以使别人更容易地阅读你的代码。

◆ 给机器看的文档。工具可以有多种方式利用类型标注。特别是,它们可以在 IDE 中帮助提供很好的特性,如名称补全和增强的导航。

◆ 早期的错误检测。Dart 提供了静态检查器,它可以警告你潜在的问题,而不用你自己查。另外,在开发模式中,Dart 自动把类型标注转换为运行时断言检查来辅助调试。

◆ 有时,在编译到 Javascript 时,类型可以帮助改进性能。

静态检查器

静态检查器(static checker)行为很像C中的链接。它在编译时警告你潜在的问题。这些警告中的很多是和类型相关的。静态检查器不会产生错误——不论检查器说什么你总是可以编译和运行你的代码。

检查器不会对每个可能的类型违反都敏感。它不是类型检查器(typechecker),因为Dart并不是按照典型的类型系统那样使用类型。检查器会抱怨那些非常可能是真实问题的地方,而不会强迫你去满足心胸狭隘的类型系统。

例如,考虑这个:

  1. String s1 = '9';  
  2. String s2 = '1';  
  3. ...  
  4. int n = s1 + s2;  
  5. print(n); 

这里明显是个问题。这种情况下静态检查器会产生一个警告。注意代码依然可以运行,n 被置为字符串'91'并打印出来。

然而,不像典型的强类型系统,这样的代码:

  1. Object lookup(String key) {...} // a lookup method in a heterogenous table  
  2. String s = lookup('Frankenstein'); 

检查器不会抱怨。因为这种情况下代码很有可能是对的,虽然缺少类型信息。你作为程序员通常知道程序的语义,而类型检查器(typechecker)不知道。你知道'Frankenstein'这个key在表中存储的是字符串,即使 lookup 方法声明返回的是Object。

Dynamic类型

没有提供类型的时候,Dart如何避免抱怨呢?这其中的关键就是 Dynamic 类型,这是程序员没有明确给出类型时候的默认类型。使用 Dynamic 类型让检查器闭嘴。

偶尔,你可能想要明确地使用 Dynamic 。

  1. Map<String, Dynamic> m = {  
  2.     'one': new Partridge(),  
  3.     'two': new TurtleDove(),  
  4.     ...,  
  5.     'twelve': new Drummer()}; 

我们本来也可以给m使用 Map<String, Object> ,但是那样的话,当我们获取内容的时候,它们将是Object的静态类型,而它只有很少的信息。因为map的内容除了Object外没有公共的super接口,我们可能更愿意使用 Dynamic 。如果我们像这样调用map 的值的方法:

  1. pearTree = m['one'].container(); 

如果内容是Object类型,我们会得到警告,因为Object不支持container方法。如果我们使用Dynamic类型,就不会产生警告。

范型

Dart 支持具体化范型(reified generics)。就是说,范型类型的对象在运行时携带它们的类型参数。传递类型参数给范型类型的构造函数是运行时操作。这如何与可选类型的要求相一致呢?

好吧,如果你不想总是考虑类型,范型并不强迫你。你可以创建范型类的实例,而不需要提供类型参数。例如,这样写没问题:

  1. new List(); 

当然,如果你想要,也可以这样写:

  1. new List<String>();  
  2. new List(); 

是下面这样的快捷方式:

  1. new List<Dynamic>(); 

在构造函数中,类型参数起到运行时角色。实际上,它们在运行时被传递,所以你可以做动态类型测试的时候使用它们。

  1. new List<String>() is List<Object>  // true: every string is an object   
  2. new List<Object>() is List<String>  // false: not all objects are strings 

Dart中的范型符合程序员的直觉。这是一些更有趣的情况:

  1. new List<String>() is List<int>     // false  
  2. new List<String>() is List          // true  
  3. new List<String>() is List<Dynamic> // same as line above  
  4. new List() is List<Dynamic>         // true, these are exactly the same  

与此相反,类型标注(例如变量前添加的类型或者函数和方法的返回类型)起到非运行时角色并且不影响程序的语义。***一个值得学习的情况:

  1. new List() is List<String>          // true as well! 

你可以不用类型写程序,但是你经常要传递数据到有类型的库中。为了防止类型妨碍你,没有类型参数的范型类型被认为是任何其它范型类型的替代品(子类型)。

检查模式

在开发过程中,Dart 程序可以在检查模式(checked mode)下运行。如果你在检查模式下运行程序,在参数传递、返回结果和执行赋值时,系统将自动执行某些类型的检查。如果检查失败,程序将在该处停止执行,并带有清晰的错误信息。所以,

  1. String s = new Object(); 

将会停止执行,因为Object不是String的子类型。然而,

  1. Object foo(){return "x";}  
  2. String s = foo(); 

工作正常,因为foo在运行时返回的实际对象就是String,尽管其类型签名说foo返回的是Object。当对象赋值给变量时,Dart 检查对象的运行时类型是否为变量(静态)声明类型的子类型。

本质上,检查模式就像是在对每次赋值、返回等进行子类型检查的调试器下运行。一些更复杂的例子:

  1. <int>[0,1, 1][2] = new Object(); // fails in checked mode  
  2.    
  3. bar(int n) { return n *2;}  
  4. ...  
  5. bar(3.2); // returns 6.4 in production, but fails in checked mode 

在检查模式下,每次把参数传递给函数时,都要检查参数的运行时类型是否是形式参数声明类型的子类型。我们可以很容易地纠正这个:

  1. bar(num n) { return n *2;}  
  2. ...  
  3. bar(3.2); // works fine  
  4.    
  5. int i_bar(num n) { return n *2;}  
  6. ...  
  7. i_bar(3.2); // fails in checked mode  
  8.             // because returned value is not an int 

注意***一行。检查发生在返回值上,即使函数的结果并没有进行赋值。

让我们回到之前的Frankenstein例子上。

  1. Object lookup(String key) {...} // a lookup method in a heterogenous table  
  2. String s = lookup('Frankenstein'); 

如果我们假设的lookup方法返回一个String是正确的,那么检查模式会平滑地执行。如果不是,那么它将捕获到我们的这个错误。在生产模式(production mode)下,代码都会运行,不会抱怨。假设lookup方法真的返回了一个非String对象,一个Frankenstein类的实例。那么变量 s 将容纳那个实例。Dart 绝不会神奇地强制它为一个字符串。如果Dart那样做就会意味着类型标注正在改变我们程序的行为,类型就不再是可选的了。

当然,如果你根本就不用类型,检查模式不会妨碍你。

  1. my_add(s1, s2) { return s1 + s2;}  
  2.    
  3. my_add(3, 4); // 7  
  4. my_add("3", "4"); // "34" 

所有这些检查会带来很大的性能损失,所以通常不能用在生产环境中。这些检查的好处是它们可以在源头上捕获动态类型的错误,更容易地调试问题。虽然总可以在测试过程中发现大多数这类问题,但是检查模式有利于缩小它们的范围。

使用类型

如何使用类型取决于你。如果你讨厌类型,你不必使用它们。你不会得到任何类型的警告,你可以用你在其它动态语言中感到舒适的方式开发。然而你依然可以从类型中获益,因为Dart的库中有类型签名,它们告诉你它们期望什么和返回什么。如果你在检查模式中运行,传递了错误的参数给类库,检查模式将在你犯错的地方发现它们。

如果你喜欢类型,你可以在任何地方使用它们,很像是静态类型语言。然而,即使那样你也不会获得同样级别的静态检查。Dart的规则比较宽松。我们期望为这些人提供额外的工具来更加严格地解释类型标注。

我们不建议太极端地使用方式。应该在有意义的地方使用类型。你能做的最有价值的事情是添加类型到你类库中公有成员的声明上。接下来,再对私有成员做同样的事。即使没有别人需要维护代码,如果你离开代码几周或几个月后又回来,你会发现它是有帮助的。在这两种情况下,你不一定要在方法体或函数体中添加类型。库的使用者从类型签名中获得价值,即使它们不是100%准确。

在函数体中,并不总是需要标注声明。有时代码足够简单,真的无所谓,类型反而可能会造成混乱。

通常,你应该设计代码,别让考虑类型影响你。在某些情况下,有几种替代的设计,其中的某种比其它更适合使用类型。例如,你可以用传递函数替代它,而不是用传递字符串表示要调用的函数名,这样代码会更有效并更容易检查类型。Dart 同样防止以其他方式无端地使用反射(reflection)。然而,当真正有意义时,你应该毫不犹豫地使用反射。

原文:http://han.guokai.blog.163.com/blog/static/136718271201110194459405/

【编辑推荐】

  1. Dart语言惯用语——Dart中特有的代码味道
  2. 瞬间秒杀所有人的Dart语言版Hello World
  3. Dart VS JavaScript之JavaScript的先天残疾
  4. Google新结构化编程语言Dart可运行在Jvm上
  5. 众家评说谷歌新编程语言Dart
责任编辑:陈贻新 来源: 韩国恺的博客
相关推荐

2022-09-16 00:11:45

PyTorch神经网络存储

2017-11-17 09:13:31

Java注解

2022-05-18 08:00:00

JavaScriptFetch数据

2022-04-14 09:01:39

React源码Flow

2024-01-16 07:33:02

SwiftTypeScript可选绑定

2011-08-08 13:45:58

jQuery

2021-05-10 17:20:55

AIOps开发人员人工智能

2023-01-31 16:43:31

​Node.js事件循环

2023-03-06 00:27:02

Kubernetesscheduler系统

2010-08-02 16:56:03

ICMP协议

2023-04-18 14:53:48

2023-04-18 15:09:50

2021-08-03 14:29:30

ARPANET互联网协议TCP

2015-09-02 10:33:54

红包类型optionals

2023-04-19 08:13:02

EpollLinux

2024-02-22 08:00:00

SoraOpenAI

2022-08-08 08:00:00

人工智能机器学习计算机应用

2021-02-26 14:40:16

Kubernetes调度器

2022-02-11 10:27:28

面部识别算法人工智能

2022-08-12 07:00:00

NFC安全性RFID
点赞
收藏

51CTO技术栈公众号