上一篇主要讲了Dart的类与函数,由于内容有太多,我就把剩下的内容分开写一篇文章。 这一篇我们讲Dart的泛型、异步、库等有关详解,内容较多,希望大家可以耐心看完。我也是花了很长时间研究的。喜欢的就点个赞,打个赏吧。 感谢大家支持。
如果您查看基本数组类型的API文档 List,您会看到该类型实际上是List<E>。<...>表示法将List标记为 泛型(或参数化)类型 - 具有正式类型参数的类型。按照惯例,大多数类型变量都有单字母名称,例如E,T,S,K和V.
类型安全通常需要泛型,但它们比仅允许代码运行有更多好处:
如果您希望列表只包含字符串,则可以将其声明为List<String>(将其读作“字符串列表”)。这样一来,工具可以检测到将非字符串分配给列表可能是一个错误。 例子:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
// 报错 The argument type 'int' can't be assigned to the parameter type 'String'.
names.add(42);
泛型允许您在多种类型之间共享单个接口和实现,同时仍然利用静态分析。 例如:创建了一个用于缓存对象的接口:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
您发现需要此接口针对字符串的做一个缓存,因此您需要创建另一个接口:
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
如果还有其他更改,就要写很多接口。 泛型可以省去创建所有这些接口的麻烦。你可以创建一个带有类型参数的接口。 示例如下:T是一个占位符,您可以将其视为开发人员稍后定义的类型。
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
list和map文字可以参数化。参数化文字就像你已经看到的文字一样,除了你在开始括号之前添加 <type>(对于list)或 <keyType, valueType>(对于map)。 以下是使用类型文字(typed literals)的示例:
var numbers = <String>['11', '22', '33'];
var pages = <String, String>{
'index.html': 'Homepage',
'store.html': 'Store',
'mine.html': 'Mine'
};
要在使用构造函数时指定一个或多个类型,请将类型放在类名称后面的尖括号<...>中。例如:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = Set<String>.from(names);
以下代码创建一个具有整数的key和View类型的value的map:
var views = Map<int, View>();
Dart的泛型类型是具体的
。也就说,它们在运行时会会携带类型信息。示例如下:(相反,Java中的泛型使用擦除
,这意味着在运行时删除泛型类型参数。在Java中,您可以测试对象是否为List,但您无法测试它是否是List<String>。)
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
print(names.runtimeType); // List<String>
实现泛型类型时,您可能希望限制其参数的类型。你可以在<>里面使用extends。 例如:
abstract class SomeBaseClass {
// 其他操作
}
class Foo<T extends SomeBaseClass> {
String toString() {
return "Instance of Foo<$T>";
}
}
class Extender extends SomeBaseClass {
//其他操作
}
现在可以使用SomeBaseClass或它的任何子类作为泛型参数。
例如:
void main() {
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
print(someBaseClassFoo.toString());// Instance of Foo<SomeBaseClass>
print(extenderFoo.toString());// Instance of Foo<SomeBaseClass>
}
也可以不指定泛型参数。 例如:
var foo = Foo();
//等同于print(foo.toString());
print(foo);// Instance of Foo<SomeBaseClass>
如果指定任何非SomeBaseClass类型会导致错误。
例如:var foo = Foo<Object>;
新版本的Dart的泛型方法,允许在方法和函数上使用类型参数。(但它同样适用于实例方法,静态方法,顶级函数,本地函数甚至lambda表达式。) 例如:
T first<T>(List<T> data) {
// 做一些初始工作或错误检查...
T tmp = data[0];
// 做一些额外的检查或处理...
return tmp;
}
在first(<T>)上的的泛型类型参数,允许你在以下几个地方使用类型参数T:
泛型方法可以声明类方法(实例和静态)以相同的方式获取泛型参数。
class C {
static int f < S,T >(int x)= > 3 ;
int m < S,T >(int x)= > 3 ;
}
泛型方法也适用于函数类型参数,本地函数和函数表达式。 // 将泛型方法作为参数[callback]。
void functionTypedParameter(T callback <T>(T thing)){}
// 声明一个本地泛型函数`本身`。
void localFunction(){
T itself<T>(T thing) => thing;
}
// 将泛型函数表达式绑定到局部变量。
void functionExpression(){
var lambda = <T>(T thing) => thing;
}
该import和library指令可以帮助您创建一个模块化的,可共享的代码库。库不仅提供API,还是隐私单元(以下划线(_)
开头的标识符仅在库内可见)。每个Dart应用程序都是一个库,即使它不使用library指令。可以使用包来分发库。
使用import指定一个库中的命名空间如何在另一个库汇总使用。
例如,Dart Web应用程序通常使用dart:html 库,它们可以像这样导入:
import 'dart:html';
对于内置库,URI具有特殊dart: 方案(scheme)。对于其他库,您可以使用文件系统路径或package: 方案(scheme),这个是由包管理器(如pub工具)提供的库。
例如:
import 'libs/mylib.dart';
如果导入两个具有冲突标识符的库,则可以为一个或两个库指定前缀。例如,如果test2.dart和test3.dart都有一个hello()函数,那么直接导入这两个文件会有冲突,这种情况下我们可以使用as关键字给库指定一个前缀:
test2.dart代码如下:
void hello() {
print('test2.dart : hello()函数');
}
test3.dart代码如下:
void hello(){
print('test3.dart : hello()函数');
}
现在要在test1.dart中导入这两个文件:
// 这样写会报错
// import 'test2.dart';
// import 'test3.dart';
// 正确写法:
import 'test2.dart';
// 给导入的库指定一个前缀 方便识别
import 'test3.dart' as test3;
调用方式:
void main(){
hello();//test2.dart : hello()函数
test3.hello();//test3.dart : hello()函数
}
导入库也可以使用相对路径。例如:lib/demo1/a.dart
, lib/demo2/b.dart
这两个文件。现在b.dart
这个文件需要引用a.dart
,可以使用import '../demo1/a.dart'
导入。
如果只想使用库的一部分,则可以有选择地导入库,可以使用show
或者hide
关键字。例如:show表示仅导入当前库,hide表示除了当前库之外全部导入。
// 仅导入mylib.dart里面的test2函数
// import 'libs/mylib.dart' show test2;
// 刚好和show相反 除了test2函数之外 其它的都导入
import 'libs/mylib.dart' hide test2;
//我们想导入mylib库,但是不想用里面的otherLib这个库 可以这样写
// import 'libs/mylib.dart' hide otherLib;
延迟加载(也称为延迟加载)
允许应用程序根据需要加载库,如果需要的话。以下是您可能使用延迟加载的一些情况:
deferred as
它导入一个库。当我们import一个库的时候,如果使用了as 不能同时使用deferred as
例如:// import 'libs/mylib.dart'; // 不能同时使用
import 'libs/mylib.dart' deferred as tests;
当您需要库时,使用库的标识符调用loadLibrary()。 例如(注意导包:import 'dart:async';):
Future hello() async {
await tests.loadLibrary();
tests.test2();
}
// 然后再去使用:
void main(){
hello(); // 结果是: mylib.dart:test2()函数
}
在上述的代码中,await关键字暂停执行,直到库被加载。 您可以在一个库上调用loadLibrary()多次,而不会出现问题。该库只加载一次。
使用延迟加载时请记住以下内容:
【说明】dart官网不推荐使用part ,这个仅作为了解。 使用part指令,可以将库拆分为多个Dart文件。part of表示隶属于某个库的一部分。 注意事项:
// library testlib2; 这个不能和part of同时使用 会报错
// part of 表示这个库是testlib库的一部分
part of testlib1;
part of testlib1
; 表示testlib2这个库是testlib库的yi部分。// 第1个库:
library testlib1;
// 可以不写
part 'testlib2.dart';
void run() {
print('testlib1库 : run()函数');
}
testlib2.dart内容:
part of testlib1;
class testLib2 {}
void start() {
print('testlib2库 : start()函数');
}
// 第1个库:
library testlib1;
// 可以不写
part 'testlib2.dart';
在A库中使用export关键字引入B库,当我们使用A库的时候,会自动引入B库,也就是说我们导入了A库,就可以使用B库了。 mylib.dart内容为:
// 这是一个库 命名为mylib
library mylib;
// 希望使用mylib的时候 自动使用otherlib.dart 可以使用export关键字引入其他库
export 'otherlib.dart';
// 导入otherlib2.dart
export 'otherlib2.dart';
class MyLib {
void test() {
print('mylib.dart: MyLib : test()函数');
}
}
void test2() {
print('mylib.dart: test2()函数');
}
otherlib.dart库内容为:
// otherlib库
library otherlib;
class otherLib {}
void test() {
print('otherLib库 : test()函数');
}
otherlib2.dart库内容为:
// otherlib2库
library otherlib2;
class otherLib2 {}
void test2() {
print('otherLib2库 : test2()函数');
}
库的最低要求是:pubspec.yaml
文件和lib
目录。
库的pubspec.yaml文件与普通应用程序包的文件格式相同。
lib目录:库代码位于lib 目录下,并且对其他包是公共的。您可以根据需要在lib下创建任何层次结构。
声明一个库的关键字是library。
例如在文件test.dart文件首行加上:library mylib; 表示这个库的名称是mylib
Dart库中包含许多返回Future或Stream对象的函数。这些函数是异步的:它们在设置可能耗时的操作(例如I / O)后返回,而不等待该操作完成。
Dart官网有关于异步的教学: 使用Future完成异步任务:https://www.dartlang.org/tutorials/language/futures 使用Streams(流)管理序列化数据:https://www.dartlang.org/tutorials/language/streams
async
和await
关键字支持异步编程,让你写异步代码看起来类似于同步代码。
当您需要完成Future的结果时,您有两个选择:
使用async
和await
异步的代码,但它看起来很像同步代码。例如,这里有一些代码await 用于等待异步函数的结果。例如:await lookUpVersion();
要使用async,代码必须在async函数中(标记为async的函数)。 例如:
Future checkVersion() async {
var version = await lookUpVersion();
// 其他操作
}
注意: 虽然async函数可能执行耗时的操作,但它不会等待这些操作。async函数只在遇到第一个await表达式时执行。然后它返回一个Future对象,仅在await表达式完成后才恢复执行。
使用try,catch,finally在使用await的代码中处理错误和清理代码。
try {
version = await lookUpVersion();
} catch (e) {
// 这里可以看到是什么错误。
}fianlly{
// 正确的解决方式写在这里
}
您可以在异步功能中多次使用await。例如,以下代码等待三次函数结果:
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
在await表达式中,表达式的值通常是Future; 如果不是,那么该值将自动包含在Future中。 这个Future对象表示返回一个对象的promise。 await表达式的值是返回的对象。 await表达式使执行暂停,直到该对象可用。
如果在使用await时遇到编译时错误,请确保await在async函数中。
例如,要在应用程序的main()函数中使用await
,main()方法必须标记为async
:以下是一个完整的示例代码:
`import 'dart:async';`
// 要在应用程序的main()函数中使用await,main()方法必须标记为async
Future main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
Future checkVersion() async {
print('checkVersion()');
var version = await lookUpVersion();
}
Future<String> lookUpVersion(){
print('lookUpVersion()');
}
运行结果是:
checkVersion()
lookUpVersion()
lookUpVersion()
In main: version is null
方法被async修饰的函数是异步函数。给一个函数添加async关键字,使得返回值是一个Future。
void main(){
lookUpVersion(); //输出结果:lookUpVersion()同步方法 返回值是:1.0.0
lookUpVersion2(); // 输出结果:lookUpVersion2()异步方法 返回值是:1.0.0
lookUpVersion3(); // 输出结果:lookUpVersion3()异步方法 没有返回值
}
例如,看下面这个返回值是String的同步函数:
String lookUpVersion() {
print('lookUpVersion()同步方法 返回值是:1.0.0');
return '1.0.0';
}
如果将其更改为异步函数 - 例如,因为Future的实现将非常耗时 - 返回的值是Future:
Future<String> lookUpVersion2() async{
print('lookUpVersion2()异步方法 返回值是:1.0.0');
return '1.0.0';
}
如果您的函数没有返回有用的值,请设置其返回类型Future<void> 例如:
Future<void> lookUpVersion3() async {
print('lookUpVersion3()异步方法 没有返回值');
}
当您需要完成Future的结果时,您有两个选择:
异步for循环的格式:await for(var或具体类型 标识符 in 表达式){}
例如:我们读取本地的一个文件内容,实例代码如下:
import 'dart:io';
import 'dart:convert';
void main() {
test();
}
// await for循环的使用示例
// 这里是读取本地文件的内容
Future test() async {
var config=File('d:\\test.txt');
// 打开io流进行文件读取
Stream<List<int>> inputStream = config.openRead();
var lines = inputStream
// 设置编码格式为utf-8
.transform(utf8.decoder)
.transform(LineSplitter());
try {
await for (var line in lines) {
print('从Stream中获取到的内容是: ${line} \r文本内容长度为:'+ '${line.length}\r=======');
}
print('文件现在没有关闭。。。');
} catch (e) {
print(e);
}
}
表达式的值必须有Stream类型,执行过程如下:
要停止监听Stream,你可以使用break或者return语句跳出for循环B并且从Stream中取消订阅。 如果在实现异步for循环时遇到编译时错误,确保await for在一个async函数中。 例如,要在应用程序的main()函数中使用await for循环,main()方法必须标记为async:以下是一个完整的示例代码:
Future main() async {
// ...
await for (var request in requestServer) {
handleRequest(request);
}
// ...
}
有关异步编程的更多信息,请查看库浏览的 Dart库之旅 --- dart:async 部分,另请参阅文章 Dart语言异步支持:阶段2(该页面可能过期了) 和 Dart语言规范。
大多数计算机,甚至在移动平台上,都有多核CPU。为了利用所有这些核心,开发人员传统上使用并发运行的共享内存线程。但是,共享状态并发容易出错,并且可能导致代码复杂化。 所有Dart代码都在隔离区内运行,而不是线程。每个隔离区都有自己的内存堆,确保不会从任何其他隔离区访问隔离区的状态。
Dart是单线程模型,但是使用Isolates可以用于多线程。
这个库主要用于服务端的开发。如果你不使用Dart做服务端开发,仅作为了解即可。 源码可以看Github:https://github.com/dart-lang/isolate 官方API文档: https://api.dartlang.org/stable/2.1.0/dart-isolate/dart-isolate-library.html
使用isolate 需要先导入包:import 'dart:isolate';
下面来一个简单的示例代码: // 在另一个隔离区()中同步读取“D://file.json” // 结果是{msg: [{title: 你好1, contents: yes}, {title: 你好2, contents: NO}]} main() async { // 在其他隔离(isolate)中同步读取文件,然后对其进行解码。 print(await readIsolate()); }
// 同步读取'D//file.json'(在同一个线程中) Map readSync() { JsonCodec().decode(new File('D://file.json').readAsStringSync()); }
// 在另一个隔离区()中同步读取“D://file.json” Future readIsolate() async { final response = new ReceivePort(); await Isolate.spawn(_isolate, response.sendPort); return response.first; }
/// 期望通过[Isolate.spawn]创建 void _isolate(SendPort sendPort) { sendPort.send(readSync()); }
下面是file.json文件的内容: { "msg": [ { "title": "你好1", "contents": "yes" }, { "title": "你好2", "contents": "NO" } ] }
当您需要懒惰地生成一系列值时,请考虑使用生成器函数。Dart支持两种生成器功能。
要实现同步生成器函数,请将函数体标记为sync*,并使用yield语句来传递值。
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
如果您的生成器是递归的,您可以使用yield*以下方法来提高其性能:
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
要实现异步生成器函数,请将函数体标记为async*,并使用yield语句来传递值。
Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
在Dart中,函数是对象,就像字符串一样,数字是对象。一个类型定义,或功能型的别名,给出了一个函数类型声明字段时,您可以使用和返回类型的名称。当函数类型分配给变量时,typedef会保留类型信息。
以下代码,它不使用typedef:我们可以看到compare是一个函数,但它是哪一种类型的函数?不是很清楚。
class SortedCollection {
Function compare;
SortedCollection(int f(Object a, Object b)) {
compare = f;
}
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
// compare是一个函数,但它是哪一种类型的函数?
assert(coll.compare is Function);
}
接下来使用typedef改造一下: 我们将代码更改为使用显式名称并保留类型信息,开发人员和工具都可以使用该信息。
typedef Compare = int Function(Object a, Object b);
class SortedCollection {
Compare compare;
SortedCollection(this.compare);
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
assert(coll.compare is Function);
assert(coll.compare is Compare);
}
目前:typedef仅限于函数类型。
因为typedef只是别名,Dart提供了一种检查任何函数类型的方法。 例如:
typedef Compare<T> = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
assert(sort is Compare<int>); // True
}
使用元数据提供有关代码的其他信息。元数据注解以字符开头@,后跟对编译时常量(如deprecated)的引用或对常量构造函数的调用。
元数据可以出现在库,类,typedef,类型参数,构造函数,工厂,函数,字段,参数或变量声明之前以及导入或导出指令之前。您可以使用反射在运行时检索元数据。
所有Dart代码都有两个注解:@deprecated
和 @override
。
以下是使用@deprecated 注解的示例:
class Television {
/// _Deprecated: Use [turnOn] instead._
@deprecated
void activate() {
turnOn();
}
// Turns the TV's power on.
void turnOn() {
//...
}
}
您可以定义自己的元数据注释。
这是一个定义带有两个参数的@todo注释的示例:
library todo;
class Todo {
final String who;
final String what;
const Todo(this.who, this.what);
}
以下是使用@todo注释的示例:
import 'todo.dart';
@Todo('seth', 'make this do something')
void doSomething() {
print('do something');
}