语言简介
Dart 语言是为客户端开发而设计,优先考虑多平台的开发,它的主要特点:
- 编译型:为了支持多平台而开发的各种编译器
- 强类型:使用静态类型检查,也支持 dynamic 类型及运行时类型检查
- GC :由 Dart 运行时环境负责分配和管理内存
- 面向对象:基于 mixin 继承机制
Dart 平台
Dart 通过编译技术来支持各种不同的平台:
- 原生平台:对于移动和桌面平台,Dart 拥有具有实时编译功能(JIT)的 Dart VM ,和用于生成机器代码的 AOT 编译器。开发的时候,Dart VM 通过 JIT 提供增量重编译、热重载、运行时数据收集及各种调试能力。部署到生产环境的时候通过 AOT 编译器将代码编译成对应平台的机器码。
- Web 平台:针对 Web 应用程序,Dart 有开发时编译器(dartdevc)和生产时编译器(dart2js),两者都可以将 dart 代码编译成 js 代码。
Hello World
1
2
3
4
| // hello.dart
void main() {
print("Hello World!");
}
|
通过 dart run hello.dart
就可以运行上面的代码。
在 Windows 上,我们可以通过 dart compile exe hello.dart
将程序编译成 exe 文件。
编译后生成的是可执行的 hello.exe,我们用 dumpbin.exe /dependents hello.exe
查看它的依赖可以发现它只依赖操作系统的库,其它的全部编译到了 exe 里:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Image has the following dependencies:
IPHLPAPI.DLL
PSAPI.DLL
WS2_32.dll
RPCRT4.dll
SHLWAPI.dll
ADVAPI32.dll
SHELL32.dll
dbghelp.dll
bcrypt.dll
CRYPT32.dll
KERNEL32.dll
|
我们可以用 dart compile js hello.dart
将 dart 编译成 js 代码,用 node 执行生成的 js 文件可以得到同样的输出,把生成的代码放到浏览器里也是可以执行的。
变量
和大多数 GC 语言一样,Dart 中的变量都只是引用到一个对象,每个对象都是一个类的实例。数字、函数以及 null 都是对象。除了 null 外,所有的类都继承于 Object 类。
Dart 的变量具有*词法作用域*
Dart 是强类型的,没有隐式类型转换,下面的代码会报错,无法从 double 转成 int 。
1
| int number = 5.5; // error A value of type 'double' can't be assigned to a variable of type 'int'.
|
只能用 double
类提供的 toInt
方法进行转换
1
2
| int number = 5.5.toInt();
print(number);
|
Dart 有自动类型推导,在声明变量时指定类型是可选的,下面的 number
自动推导成 int
类型。
1
2
| var number = 10;
print(number.runtimeType);
|
Dart 支持声明接收任意类型的变量,在运行时动态改变。
1
2
3
4
| Object any = "ANY";
print(any.runtimeType);
any = 2;
print(any.runtimeType);
|
空安全
Dart 中的类型默认是非空的,除非声明它们可为空。
1
| int number = null;// Error: The value 'null' can't be assigned to a variable of type 'int' because 'int' is not nullable.
|
要想声明一个变量可为空,就在类型声明后面加上 ?
1
2
| int? number = null;
print(number);
|
Dart 的空安全据说是完全可靠的,如果一个变量不可为空,那么它永远不为空。
默认值
对于不可空类型,在使用之前必须初始化
1
2
| int number;
print(number);// Error: Non-nullable variable 'number' must be assigned before it can be used.
|
1
2
3
| int number;
number = 5;
print(number);
|
对于可空类型,不初始化默认为 null
1
2
| int? number;
print(number);
|
Late 变量
Dart 2.12 添加了 late
变量修饰符,它的作用有两个:
- 延迟初始化不可空变量
- 延迟初始化变量
有时 Dart 推导非空类型初始化时会失败,如:
1
2
3
4
5
| int number;
void main() {
number = 10;
print(number); // Error: Field 'number' should be initialized because its type 'int' doesn't allow null.
}
|
这个时候可以加上 late
修复这个问题
1
2
3
4
5
| late int number;
void main() {
number = 10;
print(number);
}
|
当在声明变量同时初始化时,加上 late
可以延迟初始化,主要有两种场景用到:
- 这个变量可能不会被用到,或者初始化它花的时间比较长
- 需要初始化一个实例变量,但是在构造函数里需要用到
this
1
2
3
4
5
6
7
8
9
10
| int delayInit() {
print("delay init");
return 10;
}
void main() {
late int number = delayInit();
print("do some things");
print(number);
}
|
1
2
3
| do some things
delay init
10
|
Final 和 Const
final
修饰的变量只可以被赋值一次,=const= 修饰的变量是一个编译期常量,必须在编译期计算出来。
1
2
| final int number = 5;
number = 10; // Error: Can't assign to the final variable 'number'.
|
final
可以赋值一次
1
2
3
4
| final int? number;
number = 10;
print(number); // 10
number = 20;// Error: Final variable 'number' might already be assigned at this point.
|
const
修饰的变量必须在编译期就确定
1
2
3
4
5
6
7
8
| int f() {
return 10;
}
void main() {
const int number = f(); // Error: Method invocation is not a constant expression.
print(number);
}
|
右边的值必须在编译器确定
1
2
3
4
| void main() {
const int number = 10;
print(number);
}
|
const
也可以创建常量值,对应的变量值是可以改变的,即使它引用过 const 值
1
2
3
4
| var number = const [];
print(number);
number = [1, 2, 3];
print(number);
|
内置类型
Numbers
Dart 支持两种内置类型:=int= 和 double
,如果一个数字包括了小数点,它就是 double
,否者就是 int
1
2
3
4
| var n = 1;
print(n.runtimeType);
var m = 1.1;
print(m.runtimeType);
|
整型字面量可以自动转换成 double
类型,反之就不行。
1
2
| double m = 1;
print(m);
|
int
和 double
的父类型是 num
1
2
3
| num n = 1;
n += 1.5;
print(n);
|
Number 和 String 之间的转换如下:
1
2
3
4
5
6
7
8
9
10
| // String => int
var one = int.parse("1");
print(one);
// String => double
var onePointOne = double.parse("1.1");
print(onePointOne);
// int => String
print(1.toString());
// double => String
print(3.14159.toStringAsFixed(2));
|
Strings
Dart 字符串是 UTF-16 编码的字符序列,可以使用单引号和双引号创建字符串,或者使用三引号创建多行字符串。
1
2
3
4
5
6
7
8
9
| var s1 = "first";
var s2 = 'second';
var s3 = """first
second
""";
var s4 = '''first
second
''';
print(s4);
|
使用 ${表达式}
在字符串中插入表达式,如果表达式是一个标识符,可以省略 {}
,如果是一个对象,会自动调用对象的 toString
方法。当然,加上 \
就可以当成原本的字符。
1
2
3
| String s = "Jack";
print("My name is $s");
print("My name is \$s");
|
1
2
| My name is Jack
My name is $s
|
字符串前面加 r
创建 raw 字符串
1
2
| String s = r"This is \";
print(s);
|
字符串操作
使用 contains
startsWith
endsWith
indexOf
在字符串内搜索
1
2
3
4
5
| String s = "Hello, my name is Jack";
print(s.contains("name"));
print(s.startsWith("Hello"));
print(s.endsWith("Jack"));
print(s.indexOf("my"));
|
使用 substring
提取子串,=split= 分割字符串,=toUpperCase= toLowerCase
转换大小写,=trim= 移除首尾空格
1
2
3
4
5
6
| String s = "Hello World";
print(s.substring(0, 5));
print(s.split(" "));
print(s.toUpperCase());
print(s.toLowerCase());
print(" hello ".trim());
|
1
2
3
4
5
| Hello
[Hello, World]
HELLO WORLD
hello world
hello
|
使用 replaceAll
替换部分字符串
1
2
| String s = "Hello World";
print(s.replaceAll(RegExp("World"), "Jack"));
|
构造字符串可以使用 StringBuffer
, writeAll
第二个可选参数为分割字符
1
2
3
4
5
6
| var sb = StringBuffer();
sb
..write("Use StringBuffer")
..writeAll(["A", "B", "C"], "/")
..write("!");
print(sb.toString());
|
更多的 API: String
布尔类型
Dart 使用 bool
关键字表示布尔类型,布尔类型的只有 true
和 false
两个对象。
Dart 的类型安全不允许其它类型隐式转换成布尔类型,比如数字 0 在 if
里并不是 fasle
。
Lists
Dart 中的 List 对象表示的就是数组类型,下标从 0 开始,可以用 []
访问
1
2
3
4
| var list = [1, 2, 3];
print(list.length);
print(list[1]);
print(list.runtimeType);
|
上面的声明会被推导成 List<int>
类型
扩展操作符(=…=) 和空扩展操作符(=…?=)提供了一种将多个元素插入集合的简洁方法。
1
2
3
4
5
6
7
| var list1 = [1, 2, 3];
var list2 = [0, ...list1];
var list3 = [...list1, 4];
var list4 = [0, ...list1, 4];
print(list2);
print(list3);
print(list4);
|
1
2
3
| [0, 1, 2, 3]
[1, 2, 3, 4]
[0, 1, 2, 3, 4]
|
如果扩展操作符右边可能为 null
,就使用空扩展操作符避免异常。
1
2
3
| var list;
var list1 = [1, ...?list];
print(list1);
|
Dart 还可以在构建集合时使用 if
或者 for
。
1
2
3
4
5
6
7
8
| var n = 3;
var list = [
1,
2,
if (n == 0) 3
else 4
];
print(list);
|
1
2
3
| var list = [1, 2, 3];
var list1 = [for (var i in list) i];
print(list1);
|
List 常用操作
add
addAll
添加元素,=removeAt= 删除单个元素,=clear= 删除所有元素
1
2
3
4
5
6
7
8
| var list = [1];
list.add(2);
list.addAll([3, 4]);
print(list);
list.removeAt(0);
print(list);
list.clear();
print(list);
|
1
2
3
| [1, 2, 3, 4]
[2, 3, 4]
[]
|
indexOf
查找一个对象对应的下标值
1
2
| var list = ["Apple", "Orange"];
print(list.indexOf("Orange"));
|
sort
排序
1
2
3
| var list = [3, 1, 2, 5, 4];
list.sort();
print(list);
|
Sets
Dart 中的 Set 是无序元素的集合
1
2
3
4
5
6
| var set = {"Apple", "Orange"};
print(set.runtimeType);
var set1 = <String>{};
print(set1.runtimeType);
Set<int> set2 = {};
print(set2.runtimeType);
|
1
2
3
| _CompactLinkedHashSet<String>
_CompactLinkedHashSet<String>
_CompactLinkedHashSet<int>
|
Set 和 Map 的声明类似,如果声明时不指定元素,默认会推导成 Map
1
2
| var itIsMap = {};
print(itIsMap.runtimeType);
|
1
| _InternalLinkedHashMap<dynamic, dynamic>
|
Set 同样支持 ...
和 ...?
操作符,以及集合内的 if
和 for
Set 常用操作
使用 from
从 List 构造 Set
1
| print(Set.from([1, 2, 3]));
|
add
addAll
添加元素,=remove= 移除一个元素,=clear= 移除所有元素
1
2
3
4
5
6
7
8
| Set<int> set = {};
set.add(1);
set.addAll([2, 3]);
print(set);
set.remove(2);
print(set);
set.clear();
print(set);
|
1
2
3
| {1, 2, 3}
{1, 3}
{}
|
contains
和 containsAll
检查一个或多个元素是否在 Set 中
1
2
3
| var set = {1, 2, 3, 4};
print(set.contains(3));
print(set.containsAll([1, 4]));
|
intersection
求交集 ,
1
2
3
4
| var set1 = {1, 2, 3};
var set2 = {2, 3, 4};
print(set1.intersection(set2));
print(set1.difference(set2));
|
更多操作看相关的 API : https://api.dart.cn/stable/dart-core/Set-class.html
Maps
Map 就是 key-value 对象,key value 可以为任意类型
1
2
3
4
| var map = {
"first" : 1,
"second" : 2
};
|
可以使用 []
操作 Map
1
2
3
4
5
6
7
| var map = {
"first" : 1
};
print(map["first"]);
map["second"] = 2;
print(map);
print(map["third"]);
|
1
2
3
| 1
{first: 1, second: 2}
null
|
Map 同样支持 ...
和 ...?
操作符以及 if
for
Map 的 API 直接看相关文档:Map api docs
函数
Dart 的函数也是对象,它的类型是 Function ,它是一级对象,可以复制给变量,也可以作为参数
1
2
3
4
5
| void f() {
}
void main() {
print(f.runtimeType);
}
|
Dart 的函数都有返回值,如果不写,默认返回 null
,定义时可以没有返回类型,但是推荐在公开的 API 上加上返回类型
1
2
3
4
5
6
7
| f() {
return 10;
}
void main() {
print(f());
}
|
如果函数体只有一个表达式,可以使用 =>
1
2
| f() => 10;
print(f());
|
参数
Dart 函数有两种参数,必要参数和可选参数,必要参数放到前面。可选参数可以是命名的或位置的,可选参数如果不传就为 null
,如果可选参数类型为不可空类型,就需要给可选参数提供默认值。
命名参数
命名参数默认为可选参数,使用 {}
包起来,除非被标记为 required
1
2
3
4
5
6
| f({required String? first, String? second}) {
print(first);
print(second);
}
f(first: "FIRST");
f(first: "FIRST", second: "SECOND");
|
1
2
3
4
| FIRST
null
FIRST
SECOND
|
位置参数
位置参数使用 []
包起来
1
2
3
4
5
6
7
| f(String must, [String? first]) {
print(must);
print(first);
}
f("must");
f("MUST", "FIRST");
|
1
2
3
4
| must
null
MUST
FIRST
|
main 函数
main 函数作为入口,返回值为 void ,有一个 List<String>
的可选参数。
1
2
3
| void main() {
print("Hello World");
}
|
1
2
3
| void main(List<String> args) {
print(args);
}
|
匿名函数
格式与命名函数类似
1
2
3
4
| var f = () {
return 10;
};
print(f());
|
词法闭包
函数可以封闭定义到它作用域的变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| /// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(int addBy) {
return (int i) => addBy + i;
}
void main() {
// Create a function that adds 2.
var add2 = makeAdder(2);
// Create a function that adds 4.
var add4 = makeAdder(4);
print(add2(3) == 5);
print(add4(3) == 7);
}
|
运算符
算术运算符:=+= -
-表达式
*
/
~/(除并向下取整)
%
++
--
关系运算符:==== !=
>
<
>=
<=
判断两个对象是否相同一般用 ==
就可以了,及少数情况需要 identical
类型判断符:
- as ,类型转换
- is ,为指定类型返回 true
- is! ,为指定类型返回 flase
赋值运算符:=== ??=
1
2
3
4
| // Assign value to a
a = value;
// 如果 b 是 null ,b 为 value ,否则 b 保持原值
b ??= value;
|
算数运算符同样可以和赋值运算符结合,如:=+== -=
等
逻辑运算符:
按位和移位运算符:=&= |
^
~
<<
>>
>>>
条件表达式:=?:= ??
根据布尔表达式赋值时用 ?:
1
| var a = isPub ? "A" : "B";
|
根据 null
赋值时用 ??
1
| String playerName(String? name) => name ?? "Default";
|
级联运算符:=..= ?..
可以在同一对象上连续调用多个对象的变量或方法
1
2
3
4
| var paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
|
等价于:
1
2
3
4
| var paint = Paint();
paint.color = Colors.black;
paint.strokeCap = StrokeCap.round;
paint.strokeWidth = 5.0;
|
控制流
条件
if-else 进行条件判断,else 是可选的,if 语句中的条件必须是布尔值
1
2
3
4
5
6
7
8
| var n = 1;
if (n <= 0) {
print("<=0");
} else if (n <= 1) {
print("<=1");
} else {
print("else");
}
|
Dart 支持 switch-case
,非空的 case
子句必须有 break
;
1
2
3
4
5
6
7
8
9
10
| var command = 'OPEN';
switch (command) {
case 'OPEN':
print("Open");
// ERROR: Missing break
case 'CLOSED':
print("Close");
break;
}
|
Dart 支持空的 case
循环
普通的 for
循环
1
2
3
| for (var i = 0; i < 2; i++) {
print(i);
}
|
for-in 遍历可迭代对象,如 List
1
2
3
4
| var list = [1, 2];
for (final e in list) {
print(e);
}
|
forEach
也可以作用于可迭代对象
1
2
| var list = [1, 2];
list.forEach(print);
|
while
, do-while
和其它语言一样
break
中断循环, continue
继续循环。
断言
Dart 的 assert
失败会抛出 AssertionError
异常,一般在生产环境会忽略
异常
Dart 可以抛出和捕获异常,推荐抛出 Error 和 Exception 类型的异常
抛出异常:
1
| throw "Any type exception";
|
使用 try
on
catch
来捕获异常
1
2
3
4
5
6
7
8
9
10
11
12
| try {
breedMoreLlamas();
} on OutOfLlamasException {
// A specific exception
buyMoreLlamas();
} on Exception catch (e) {
// Anything else that is an exception
print('Unknown exception: $e');
} catch (e) {
// No specified type, handles all
print('Something really unknown: $e');
}
|
on
指定异常类型,=catch= 捕获异常对象,catch 的第二参数可以为堆栈信息。
1
2
3
4
5
6
7
8
| try {
// ···
} on Exception catch (e) {
print('Exception details:\n $e');
} catch (e, s) {
print('Exception details:\n $e');
print('Stack trace:\n $s');
}
|
rethrow
可以再次抛出异常
finally
语句会始终执行
类
Dart 是基于 mixin 继承机制的面向对象语言,除了 null 外,所有的类都继承自 Object 类。
使用类
访问类成员
使用 .
运算符访问类的成员,使用 ?.
同样可以访问类的成员,但是可以避免左边表达式为 null
产生问题。
1
2
3
4
5
6
7
8
9
10
| var p = Point(2, 2);
// Get the value of y.
assert(p.y == 2);
// Invoke distanceTo() on p.
double distance = p.distanceTo(Point(4, 4));
// If p is non-null, set a variable equal to its y value.
var a = p?.y;
|
构造函数
可以使用构造函数创建一个对象,构造函数可以为类名或者类名.标识符的方式:
1
2
| var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
|
类可以提供常量构造函数,来提供编译时常量
1
| var p = const ImmutablePoint(2, 2);
|
实现类
实例变量
1
2
3
4
5
| class Point {
double? x;
double? y;
double z = 0;
};
|
未初始化的实例变量被初始化成 null
所有实例变量都会有一个 get 方法,非 final
的实例变量和 later final
声明但是为初始化的实例变量还会有一个 set 方法
1
2
3
4
5
6
7
8
9
10
11
| class Point {
double? x; // Declare instance variable x, initially null.
double? y; // Declare y, initially null.
}
void main() {
var point = Point();
point.x = 4; // Use the setter method for x.
print(point.x == 4); // Use the getter method for x.
print(point.y == null); // Values default to null.
}
|
构造函数
声明一个和类名一样的函数即为构造函数
1
2
3
4
5
6
7
8
9
10
| class Point {
double x = 0;
double y = 0;
Point(double x, double y) {
// There's a better way to do this, stay tuned.
this.x = x;
this.y = y;
}
}
|
一种简化的方法:
1
2
3
4
5
6
7
8
| class Point {
double x = 0;
double y = 0;
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}
|
构造函数可以有初始化列表
1
2
3
4
5
6
7
8
| // Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, double> json)
: x = json['x']!,
y = json['y']! {
print('In Point.fromJson(): ($x, $y)');
}
// 等号右边不能用 this
|
如果没有声明构造函数,类会自动生成一个无参数构造函数,并会调用父类的无参数构造函数。
子类不会继承父类的构造函数。
可以为一个类声明多个命名式构造函数,让语义更加清晰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| const double xOrigin = 0;
const double yOrigin = 0;
class Point {
double x = 0;
double y = 0;
Point(this.x, this.y);
// Named constructor
Point.origin()
: x = xOrigin,
y = yOrigin;
}
|
子类和父类构造函数调用顺序:
- 子类构造函数的初始化列表
- 父类构造函数
- 当前类的构造函数
默认会调用父类的无参数构造函数,除非显示调用父类构造函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| class Person {
String? firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
// Person does not have a default constructor;
// you must call super.fromJson(data).
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
void main() {
var employee = Employee.fromJson({});
print(employee);
// Prints:
// in Person
// in Employee
// Instance of 'Employee'
}
|
1
2
3
| in Person
in Employee
Instance of 'Employee'
|
传递给父类的参数不能有 this ,因为子类构造函数还没有执行
构造函数可以重定向
1
2
3
4
5
6
7
8
9
| class Point {
double x, y;
// The main constructor for this class.
Point(this.x, this.y);
// Delegates to the main constructor.
Point.alongXAxis(double x) : this(x, 0);
}
|
如果类生成的对象是不可变的,可以定义常量构造函数
1
2
3
4
5
6
7
| class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
|
使用 factory
标记的构造函数会让该构造函数称为工厂构造函数,意味着使用工厂构造函数不一定总是返回新的实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| class Logger {
final String name;
bool mute = false;
// _cache is library-private, thanks to
// the _ in front of its name.
static final Map<String, Logger> _cache =
<String, Logger>{};
factory Logger(String name) {
return _cache.putIfAbsent(
name, () => Logger._internal(name));
}
factory Logger.fromJson(Map<String, Object> json) {
return Logger(json['name'].toString());
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
|
方法
实例方法可以访问实例变量和 this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| import 'dart:math';
class Point {
double x = 0;
double y = 0;
Point(this.x, this.y);
double distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
|
Dart 可以重写部分操作符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| class Vector {
final int x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
// Operator == and hashCode not shown.
// ···
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
print(v + w == Vector(4, 5));
print(v - w == Vector(0, 1));
}
|
Getter 和 Setter 是读写对象属性的特殊方法,可以用 get
set
关键字为额外属性定义 Getter 和 Setter 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| class Rectangle {
double left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// Define two calculated properties: right and bottom.
double get right => left + width;
set right(double value) => left = value - width;
double get bottom => top + height;
set bottom(double value) => top = value - height;
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
print(rect.left == 3);
rect.right = 12;
print(rect.left == -8);
}
|
抽象方法是定义接口而没有具体的实现,只会存在于抽象类中,直接用 ;
代替方法体可以声明一个抽象方法
1
2
3
4
5
6
7
8
9
10
11
| abstract class Doer {
// Define instance variables and methods...
void doSomething(); // Define an abstract method.
}
class EffectiveDoer extends Doer {
void doSomething() {
// Provide an implementation, so the method is not abstract here...
}
}
|
抽象类
使用关键字 abstract
标识的类可以让该类称为抽象类,抽象类无法实例化。如果想让抽象类可以实例化,可以定义一个工厂构造函数
1
2
3
4
5
6
7
| // This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
// Define constructors, fields, methods...
void updateChildren(); // Abstract method.
}
|
隐式接口
如果想要 A 类支持调用 B 类的 API 且不想继承 B 类,则可以实现 B 类的接口
一个类可以通过关键字 implements
来实现一个或多个接口并实现每个接口定义的 API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // A person. The implicit interface contains greet().
class Person {
// In the interface, but visible only in this library.
final String _name;
// Not in the interface, since this is a constructor.
Person(this._name);
// In the interface.
String greet(String who) => 'Hello, $who. I am $_name.';
}
// An implementation of the Person interface.
class Impostor implements Person {
String get _name => '';
String greet(String who) => 'Hi $who. Do you know who I am?';
}
String greetBob(Person person) => person.greet('Bob');
void main() {
print(greetBob(Person('Kathy')));
print(greetBob(Impostor()));
}
|
1
2
| Hello, Bob. I am Kathy.
Hi Bob. Do you know who I am?
|
要实现多个接口,可以用逗号分割每个接口类
1
| class Point implements Comparable, Location {...}
|
继承
使用 extends
来创建一个子类,使用 super
关键字在子类中引用父类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
|
子类可以重写父类的实例方法,可以使用 @override
注解表示重写了一个成员
1
2
3
4
5
6
7
8
9
10
| class Television {
// ···
set contrast(int value) {...}
}
class SmartTelevision extends Television {
@override
set contrast(num value) {...}
// ···
}
|
重写的方法需要满足下面几点:
- 返回值类型必须是父类方法返回值的相同类型或父类型
- 参数类型同返回值类型
- 父类方法接收 n 个位置参数,重写方法也要接收 n 个位置参数
- 泛型方法不能重写非泛型方法,非泛型方法不能重写泛型方法
noSuchMethod
如果调用了对象上不存在的方法或实例变量,会触发 noSuchMethod
方法,可以重写这个方法
只有下面一个条件成立时,才能调用到 noSuchMethod
:
- 接收方是静态的 dynamic 类型
- 接收方具有静态类型,定义了未实现的方法,并且接收方的动态类型实现了
noSuchMethod
方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| class A {
@override
void noSuchMethod(Invocation invocation) {
print("A: noSuchMethod");
}
}
class B {
@override
void noSuchMethod(Invocation invocation) {
print("B: noSuchMethod");
}
void foo();
}
void main() {
dynamic a = A();
a.method();
B b = B();
b.foo();
}
|
1
2
| A: noSuchMethod
B: noSuchMethod
|
扩展方法
扩展方法用来向一些已有的库添加额外的方法,比如下面向 String 添加一个 addPrefix 方法
1
2
3
4
5
6
7
8
9
| extension MyExt on String {
String addPrefix(String prefix) {
return prefix + ": " + this;
}
}
void main() {
print("Hello".addPrefix("Jack"));
}
|
枚举
使用 enum
关键字定义枚举类型 ,每一个枚举值都有一个 index ,=values= 可以获取枚举值列表
1
2
3
4
5
| enum Color { red, green, blue }
void main() {
print(Color.red.index);
print(Color.values);
}
|
1
2
| 0
[Color.red, Color.green, Color.blue]
|
枚举的限制:
- 枚举不能称为子类,不可以 mixin ,也不可以实现枚举
- 不能实例化一个枚举
Mixin
使用 mixin
关键字定义 mixin ,使用 with
关键字使用 mixin ,普通类也可以作为 mixin 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| class Stream {
}
mixin Readable {
void read() {
print("Readable: read");
}
}
mixin Writable {
void write() {
print("Writable: write");
}
}
class ReadWriteStream extends Stream with Readable, Writable{
}
void main() {
var stream = ReadWriteStream();
stream.read();
stream.write();
}
|
1
2
| Readable: read
Writable: write
|
类变量和方法
使用 static
关键字声明类的变量和方法
1
2
3
4
5
6
7
8
9
10
11
12
| class A {
static String name = "A";
static String GetName() {
return name;
}
}
void main() {
print(A.name);
print(A.GetName());
}
|
泛型
Dart 的方法和类都支持泛型,通过 extends
限制泛型类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| class A<T extends num> {
num add(T first, T second) {
return first + second;
}
}
num add<T extends num>(T first, T second) {
return first + second;
}
void main() {
print(add(2, 2.5));
var a = A();
print(a.add(2.5, 3));
}
|
库
使用 import
和 library
关键字可以创建一个模块化和可共享的库以 _
开头的成员只在库内部可见。
使用库
使用 import
导入库,它的参数是代码库的 URI ,对于内置库,使用 dart:xxxx
形式,对于其它库,可以使用 package:xxxxx
或者直接是文件路径
1
2
| import "dart:html"
import "package:test/test.dart"
|
如果库有冲突的标识符,可以指定前缀
1
2
3
4
5
6
7
| import "package:lib1";
import "package:lib2" as lib2;
// lib1
A a = A();
// lib2
lib2.A lib2a = lib2.A();
|
可以只导入库的一部分
1
2
3
4
| // 只导入 foo
import "package:lib1" show foo
// 导入所有,除了 foo
import "packaeg:lib2" hide foo
|
实现库
这一部分会单独写一篇文章
异步
Dart 中有两种异步对象: Future
和 Stream
,返回这两种对象的函数都是异步函数。
Future
一个 Future<T>
表示一个异步操作的结果,它有两种状态:
当调用一个异步函数时,返回一个未完成态的 Future ,这个 Future 正在等待异步函数的结果 T
,如果异步函数完成,Future 会到完成状态并拿到结果,如果异步函数出现错误,
Future 同样会到达完成状态并获取到对应的错误。
Future<T>
的 then
方法会注册一个回调,当 Future 到达完成状态时会调用这个回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| Future<String> delayEcho(String msg) {
return Future<String>.delayed(const Duration(seconds: 1), () => msg);
}
Future<void> delayError() {
return Future.delayed(const Duration(seconds: 1), () => throw "Delay Error");
}
void main() {
var result = delayEcho("Hello");
result.then((msg) => print("Received: " + msg));
var error = delayError();
error.then((value) => print("finished"), onError: (error) => print("Error: " + error));
}
|
1
2
| Received: Hello
Error: Delay Error
|
async 和 await
async
可以让一个函数称为异步函数,在异步函数里可以用 await
等待 Future 结果,用 try-catch
捕获 Future 的错误。main 函数也可以成为一个异步函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| Future<String> delayEcho(String msg) {
return Future<String>.delayed(const Duration(seconds: 1), () => msg);
}
Future<void> delayError() {
return Future.delayed(const Duration(seconds: 1), () => throw "Delay Error");
}
void main() async {
var msg = await delayEcho("Use Await");
print(msg);
try {
await delayError();
} catch (e) {
print("Error: $e");
}
}
|
1
2
| Use Await
Error: Delay Error
|
Stream
Stream 提供了一种异步的数据序列,包括用户生成的事件或从文件中读取的数据。
可以使用 async
关键字和 异步循环 await for
从 Stream 中获取值
1
2
3
4
5
| void main() async {
await for (final request in requestServer) {
handleRequest(request);
}
}
|
生成器
当需要延迟生成一连串的值时,可以使用生成器函数:
- 同步生成器返回一个
Iterable
对象 - 异步生成器返回一个
Stream
对象
通过 sync*
关键字实现一个同步生成器函数,使用 yield
语句来传递值通过 async*
关键字实现一个异步生成器函数,使用 yield
语句来传递值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| Iterable<int> g(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
Stream<int> ga(int n) async* {
int k = 0;
while (k < n) yield k++;
}
void main() async {
for (final n in g(3)) {
print(n);
}
await for (final n in ga(3)) {
print(n);
}
}
|
元数据
使用元数据可以为代码增加额外的信息。元数据注解以 @
开头,后面跟一个编译时常量,或者调用一个常量构造函数。
自定义元数据注解
1
2
3
4
5
6
7
8
9
10
| class Todo {
final String who;
final String what;
const Todo(this.who, this.what);
}
@Todo("Jack", "change function name")
void f() {
}
|