Dart 语言基础

变量

基本类型和String

示例:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('基本数据类型'),
),
body: Center(
child: RaisedButton(
onPressed: _initData,
child: Text('roll')
),
),
),
);
}

void _initData(){
/// 基本类型
// Dart 没有 byte、char 和 float.
// int、double 都是 64 位。
bool done = true;
int num = 2;
double x = 3.14;

// final 跟 Java 里的 final 一样,
// 表示一个运行时常量(在程序运行的时候赋值,赋值后值不再改变)。
final bool visible = false;
final int amount = 100;
final double y = 2.7;

// const 表示一个编译时常量,在程序编译的时候它的值就确定了。
const bool debug = true;
const int sum = 42;
const double z = 1.2;

// // Dart 的类型推断功能
// // Dart 里所有的东西都是对象,包括 int、函数。
// var done = true;
// var num = 2;
// var x = 3.14;
//
// final visible = false;
// final amount = 100;
// final y = 2.7;
//
// const debug = true;
// const sum = 42;
// const z = 1.2;

debugPrint('$done');
debugPrint('$num');
debugPrint('$x');

debugPrint('$visible');
debugPrint('$amount');
debugPrint('$y');

debugPrint('$debug');
debugPrint('$sum');
debugPrint('$z');


/// String
// Dart 里的 String 跟 Java 中的一样,是不可变对象;
// 不同的是,
// 检测两个 String 的内容是否一样时,使用 == 进行比较;
// 如果要测试两个对象是否是同一个对象(indentity test),使用 identical 函数。
var str = ' str';
var str2 = str.toUpperCase();
var str3 = str.trim();
debugPrint(str); // str
debugPrint(str2); // STR
debugPrint(str3); // str

var b1 = identical(str, str2);
var b2 = (str == str2);
debugPrint('$b1'); // false
debugPrint('$b2'); // false

// assert:断言
// assert 判断的条件可以是任何可以转化为 boolean 类型的对象,即使是函数也可以(此时判断的是函数返回值)。
// 如果 assert 的判断为true, 则继续执行下面的语句。反之则会丢出一个异 AssertionError 。
assert(str == str2);
assert(!identical(str, str2));
}
}

集合

List

示例:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
void _initData(){
/// List,在 Dart 2 里,创建对象时可以省略 new 关键字,也推荐省略 new。
// 使用构造函数创建对象,跟 var list = new List<int>(); 一样。
var list = List<int>();
list.add(1);
list.add(2);
debugPrint('list:'+list.toString());

// 通过字面量创建对象,list 的泛型参数可以从变量定义推断出来。
// 推荐使用字面量方式创建对象
var list2 = [1, 2];
debugPrint('list2:'+list2.toString());

// 没有元素,显式指定泛型参数为 int
var list3 =<int>[];
list3.add(1);
list3.add(2);
debugPrint('list3:'+list3.toString());

// list4 指向的是一个常量,不能给它添加元素(不能修改它)
var list4 = const[1, 2];
// list4.add(3); // error
// list4 本身不是一个常量,所以它可以指向另一个对象
list4 = [4, 5]; // it's fine
debugPrint('list4:'+list4.toString());

// 相当于 const list5 = const[1, 2];
const list5 = [1, 2];
// list5.add(3);// error

// Dart 同样提供了 for-in 循环。
// 因为语音设计时就考虑到了这个需求,
// in 在 Dart 里是一个关键字
var list6 = [1, 3, 5, 7];
for (var e in list6){
debugPrint('list6:$e');
}
}

Set

示例:

1
2
3
4
5
6
7
8
9
void _initData(){
/// Set,只能通过 Set 的构造函数创建实例。
var set = Set<String>();
set.add('foo');
set.add('bar');
// 断言:判断集合中是否包含 'foo'
// 为 true 则继续运行,否则会丢出一个异 AssertionError 。
assert(set.contains('foo'));
}

Map

示例:

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
void _initData(){
/// Map
var map = Map<String,int>();
// 添加
map['foo'] = 1;
map['bar'] = 3;
debugPrint('添加:'+map.toString());
// 修改
map['foo'] = 4;
debugPrint('修改:'+map.toString());

// 对应的 key 不存在时,返回 null
if(map['foobar'] == null) {
debugPrint('map does not contain foobar');
}

var map2 = const {
'foo': 2,
'bar': 4,
};
debugPrint('map2:'+map2.toString());

var map3 = <String,String>{};
debugPrint('map3:'+map3.toString());
}

dynamic 和 Object

示例:

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
26
27
28
29
30
31
void _initData(){
/// Object 和 dynamic 都可以接收任意类型的参数,但两者的区别非常大。
/// Dart 里所有东西都是对象。所有这些对象的父类就是 Object。
// 使用 Object 时,只是在说接受任意类型,需要的是一个 Object。
// 类型系统会保证其类型安全。
Object o = 'string';
o = 66;
// 我们只能调用 Object 支持的方法
debugPrint(o.toString());

// 使用 dynamic 则是告诉编译器,不用做类型检测。
// 当调用一个不存在的方法时,会执行 noSuchMethod() 方法,
// 默认情况下(在 Object 里实现)它会抛出 NoSuchMethodError。
dynamic obj = 'string';
// 可以编译通过,但在运行时会抛出 NoSuchMethodError
obj['foo'] = 6;

/// 为了在运行时检测进行类型检测,Dart 提供了一个关键字 is:
dynamic obj1 = <String,int>{};
if(obj1 is Map<String,int>){
// 进行过类型判断后,Dart 知道 obj1 是一个 Map<String,int>,
// 所以这里不用强制转换 obj1 的类型,即使我们声明 obj1 为 Object。 
obj1['foo'] = 66;
debugPrint(obj1.toString());
}

// 虽然 Dart 也提供了 as 进行类型的强制转换,
// 但为了更安全的转换,更推荐使用 is
var map = obj1 as Map<String,int>;
debugPrint(map.toString());
}

语句

示例:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
void _initData(){
// if / else
var success = true;
if(success){
print('done');
}else{
print('fail');
}

// for 循环
for(var i = 0;i < 5;i++){
print('for:');
print(i);
}

// do-while 循环
var sum = 0;
var j = 1;
do{
sum += j;
// ++ 或–- 表示自增 自减
// 在赋值运算里面 如果写在前面 这时候先运算 再赋值,
// 如果写在后面 先赋值后运行运算
++j;
print('do / while:');
print(sum);
print(j);
}while(j < 5);

// while 循环
while(sum-- > 0){
print('while:');
print(sum);
}

// switch 也支持 String 和 enum。
var type = 1;
switch(type){
case 0:
// ...   
break;
case 1:
// ..  
break;
case 2:
//  ...   
break;
default:
// ...  
break;
}
}

函数

示例:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('函数'),
),
body: Center(
child: RaisedButton(
onPressed: _initData,
child: Text('roll')
),
),
),
);
}

void _initData(){
/// Dart 不支持函数的重载。
print('支持可选参数');
print(calculate(1)); // 1
print(calculate(1,2)); // 3

print('支持默认参数');
print(calculate2(1)); // 2

print('支持具名参数');
print(calculate3(x:1,y:2)); // 3
// 具名参数的顺序可以是任意的 
print(calculate3(y:3,x:4)); // 7
// 所有的具名参数都是可选的,这个调用是合法的,
// 但它会导致 calculate3() 在运行时抛异常  
// print(calculate3());

print('函数还可以在函数的内部定义');
// return 的内部 adder 函数
var adder = makeAdder(2);
print(adder(1, 2)); // 5
}

/// Dart 支持可选参数
int calculate(int x,[int y]){
// int 也可以是 null
if(y != null){
return x + y ;
}
return x ;
}

/// Dart 支持默认参数
int calculate2(int x,[int y = 1]){
return x + y ;
}
/// Dart 支持具名参数
/// 具名参数也可以有默认参数
int calculate3({int x, int y = 0 }) {
return x + y;
}

/// 可以使用注解 @required
/// 代表某个具名参数是必须的
int calculate4({@required int x, @required int y}) {
return x + y;
}
}

// typedef 在 Dart 里面用于定义函数类型的别名
typedef Adder = int Function(int,int);
Adder makeAdder(int extra){
// 写法一
int adder(int x, int y){
return x + y + extra;
}
return adder;

// 写法二
// return(int x,int y){
// return x + y + extra;
// };

// 写法三
// 如果只有一个语句,我们可以使用下面这种更为简洁的形式
// 可以使用 lambda 
// return(int x, int y) => x + y + extra;

// 写法四
// Dart 里面不仅变量支持类型推断,lambda 的参数也支持自动推断。
// 要返回的类型是 Adder,所以 Dart 知道 x, y 都是 int
// 上面的代码还可以进一步简化为:
// return (x, y) => x + y + extra;
}


异常

抛出异常

1
2
3
4
5
void _initData(){
throw Exception('the error');
// 跟 Java 不同的是,Dart 可以抛出任意类型的对象:
// throw 66;
}

捕获异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void _initData() {
try {
// ...
// 捕获特定类型的异常
// throw 66; // catch
// throw Exception('the error'); // Exception
} on FormatException catch (e) {
//  捕获特定类型的异常,但不需要这个对象
debugPrint('FormatException:$e');
} on Exception catch (e) {
// 捕获所有异常
debugPrint('Exception:$e');
}catch(e){
debugPrint('catch:$e');
}finally{
print('finally');
}
}

Dart 提供的更简洁的 Class 初始化方式:

1
2
3
4
5
6
class MyClass {
int x;
int y;

MyClass(this.x,this.y);
}

还可以使用初始化列表(initializer list)对对象进行初始化:

1
2
3
4
5
6
7
8
9
10
11
class MyClass {
int x;
int y;

// 由于是在 initializer list 中,
// Dart 知道第一个 x 是 this.x, 第二个 x 是构造函数的参数
// initializer list 会在构造函数的函数体运行前执行。
MyClass(int x,int y):x = x,y = y{
// ...
}
}

Dart 具有垃圾收集功能,对象的使用跟 Java 里几乎是一样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void main(){
var point = MyClass(1, 2);
point.x = 4;
print(point);
}

class MyClass {
int x;
int y;

MyClass(this.x,this.y);

// 所有的类都继承自 Object,toString() 是 Object 中的方法
@override
String toString() {
// 在字符串的内部可以通过 ${expression} 的方式插入值,
// 如果 expression 是一个变量,可以省略花括号
return "MyClass{x=$x, y=$y}";
}
}

Dart 使用 package 的概念来管理源码和可见性。
它没有 public、private 之类的访问权限控制符,默认情况下,所有的符号都是公开的。
如果不想某个变量对包的外部可见,可以使用下划线开头来给变量命名。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39

main() {
var point = _OffsetPoint(1,2,10);
// 使用 getter/setter 时,就像它是一个普通的成员变量  
print(point.getX);
print(point);
point.setX = 4;
print(point);

// 运行结果:
// 11
// OffsetPoint{x=11, y=12}
// OffsetPoint{x=14, y=12}
}

class _OffsetPoint{
int _x;
int _y;
int offset;

_OffsetPoint(int x,int y, int offset): _x = x, _y = y, offset = offset {}

// 定义一个 getter  
int get getX => _x + offset;
// getter 不能有参数,连括号都省掉了  
int get getY {
return _y + offset;
}

// 定义 setter  
void set setX (int x) =>_x=x;
void set setY(int y)=>_y=y;

@override
String toString() {
return "OffsetPoint{x=$getX, y=$getY}";
}
}

类的继承

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

void main(){
// 类的单继承
var log = Point3D(1,2,3);
print(log);

// 运行结果:
// Ponit2D(构造函数):1----2
// Point3D(构造函数):1----2----3
// Point3D(toString):5----2----3

// 类的多继承
var p=Point3D2(1,2,3);
p.bark();

// 运行结果:
// Ponit2D(构造函数):1----2
// woof
}

class Point2D {
int x;
int y;
Point2D(int x, int y){
this.x = x;
this.y = y;
print("Ponit2D(构造函数):$x----$y");
this.x = 5;
}
}

/// 类的单继承
/// 但是对象构造时它跟 Java、C++ 都不太一样:
/// - 先执行子类 initializer list,但只初始化自己的成员变量
/// - 初始化父类的成员变量
/// - 执行父类构造函数的函数体
/// - 执行子类构造函数的函数体
///
/// 基于这个初始化顺序,推荐是把 super() 放在 initializer list 的最后。
/// 此外,在 initializer list 里不能访问 this(也就是说,只能调用静态方法)。
class Point3D extends Point2D {
int z;
// 父类的构造函数只能在 initializer list 里调用  
Point3D(int x,int y,int z):z = z,super(x,y){
print("Point3D(构造函数):$x----$y----$z");
}

@override
String toString() {
// TODO: implement toString
return "Point3D(toString):$x----$y----$z";
}
}

/// 虽然 Dart 是单继承的,但它也提供了一定程度的多重继承支持:
/// 类的多继承。Dart 把支持多重继承的类叫做 mixin。
abstract class Bark{
void bark() {
print('woof');
}}

class Point3D2 extends Point2D with Bark {
int z;
// 父类的构造函数只能在 initializer list 里调用 
Point3D2(int x, int y, int z):z=z,super(x,y){}
}

// 没有其他类需要继承,所以直接 extends Bark 就可以了
class Foo extends Bark {}

泛型

示例:

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 Pair<S,T>{
S first;
T second;
Pair(this.first,this.second);
}

/// 跟 Java 不同,Dart 的泛型参数类型在运行时是保留的。
void main(){
var p = Pair('hello',2);
// is 当对象是相应类型时返回 true
// is! 当对象不是相应类型时返回 true
// 比如,如果 obj 实现了 T 所定义的借口,那么 obj is T 将返回 true。
print(p is Pair<String,int>);
// is! 也是 Dart 的运算符,
// 下面的语句跟 !(p is Pair<int, int>) 是一样的,  
print(p is! Pair<int,int>);
print(p is Pair);

// 运行结果:
// true
// true
// true
}

Future

示例:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import 'dart:io';

/// 关于 Future 的更多的细节:
/// https://dart.dev/articles/archive/event-loop
void main(){
// 这里的关键在于,
// foo 函数里面,file.exists() 执行完后,会马上执行下面的语句;
// 而 foo2 则会等待结果,然后才继续执行。

foo();
foo2();

// 运行结果
// foo: after file.exists() returned
// foo: file not exists
// foo2: file not exists
// foo2: after file.exists() returned

foo2();
foo();

// 运行结果
// foo: after file.exists() returned
// foo2: file not exists
// foo2: after file.exists() returned
// foo: file not exists
}

/// Dart 是单线程的,主线程由一个事件循环来执行(类似 Android 的主线程)。
/// 对于异步代码,通过 Future 来获取结果:
void foo(){
var file=File('path-to-your-file');
file.exists()
.then((exists)=>print('foo: file ${exists?'exists':'not exists'}'))
.catchError((e)=>print(e));
print('foo: after file.exists() returned');
}

/// Dart 2 提供了 async 函数,用来简化这种编程范式。
/// 下面这段代码的效果跟上面是一样的:
/// 但是要注意,两段代码并不是完全一样的:
void foo2() async {
var file=File('path-to-your-file');
try {
var exists = await file.exists();
print('foo2: file ${exists ? 'exists' : 'not exists'}');
print('foo2: after file.exists() returned');
}catch (e){
print(e);
}
}

备注

参考资料
Dart英文官方文档
Flutter学习指南:熟悉Dart语言