配置环境和"hello world" demo
前面已经配置过flutter的环境了,能够在控制台直接使用flutter来创建和编译运行一个flutter项目。其实flutter sdk已经包含了dart sdk,其在fluuter的bin/cache目前下,名叫dart-sdk,在dart-sdk目录下也有一个bin目录,这个bin目录中就有一个dart命令命令,将dart-sdk/bin配置到环境变量的path就能在任意位置使用dart命令。
最佳配置应该是跟flutter的配置方法一样,先在环境变量配置文件中配置一个DART_HOME,然后再将dart-sdk的bin给连接到path上去。
export DART_HOME=$FLUTTER_HOME/bin/cache/dart-sdk
export path=$path:$DART_HOME/bin
这样的好处再明显不过了,如果flutter的位置变了,只需要将FLUTTER_HOME修改就行,DART_HOME和path所引用的都只是相对路径,不需要修改。将dart-sdk的bin配置到path后重启一下控制台程序,就可以发现dart命令了,执行一下dart --version,可以如果没有报找不到命令,并且正确输出了dart的版本号,就说明配置好了。
还是用世界性的编程题目——"hello world"来测一下,创建一个目录,在这个目录下创建一个test.dart文件,在其中写入hello world的dart程序。
// demo dart
void main() {
print('hello world!');
}
回到控制台,执行命令`dart test.dart`,可见控制台输出了"hello world!"
Dart的变量数据类型
  看了dart的代码,发现它其实也是一门类C的语言,所有的代码都是由变量与方法组成,程序控制结构也包括了顺序结构,判断分支结构与循环结构。
Dart用var来声明一个变量,这跟JS很像,并且还可以自动类型推断,比如var number = 20;就是声明了一个int的变量,并且赋值了20,当然也可以给变量指定类型,比如String name = "Benson"。Dart跟Java和C/C++一样,每行的最后以一个分号结尾,不加分号会编译失败,同样,注释也是使用双左斜杠"//"。
Dart还支持动态类型,一般来说当一个变量声明时就确定了它的类型了,除了像Java这种面向对象语言可以将变量声明为Object类型的,那这个类型就可以用来赋值为任何对象了,包括基本数据类型,因为Java对所有基本数据类型都给也了其包装类型,如double的包装类型是Double。而Dart直接支持动态类型,只需要使用dynamic来声明一个变量即可。如下demo
// dynamic type demo
main() {
dynamic t = "Benson";
print(t);
t = 20;
print(t);
}
这段代码是可以编译并正常运行的,执行dart命令来编译并运行,会输出
Benson
20
Dart跟python一样,是门完全面向对象语言,任何变量或常量都是对象,因为所有的变量的默认值都是null
几乎所有的语言都支持了常量,那Dart有什么理由不支持呢?Dart同时支持final和const,其中final表示只可赋值一次,而const表示引用的是个字面量,在编译期就决定了它的真实值。
Dart支持的内置类型也非常丰富,主要包括以下各类
数值型:int和double
字符串:String,字符串可以用单引号'',也可以用双引号"",和其它语言一样,双引号可以内嵌表达式,表达式以$开头,而单引号的字符串不支持。
逻辑值:bool,只有true和false两个取值。
数组类型:List,Dart用范型List来表示数组,同时也可以当成列表来使用。
集合类型:Set,跟其他语言一样,其中的元素值只能有一个,不可重复。
Map:key-value映射,使用方式跟JS中的JSON类型类型
字符类型:Runes
函数
Dart语言的函数跟Java的特别相似,只是Dart的函数可以不需要声明函数的类型和参数的类型,当然也是可以声明的。Dart也跟Kotlin一样,当函数的实现只有一行代码时,可直接用表达式来写,例如
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
Dart语言的函数还支持命名可选参数,定义时如下
void enableFlags({bool bold, bool hidden}) {...}
而使用时,则是
enableFlags(bold: true, hidden: false);
当某些命名参数必填时,可用@required注解来修饰参数定义
Dart函数的位置可选参数使用[]来定义,如
String say(String from, String msg, [String device]) {...}
对于默认参数,在定义可选参数时,可直接在定义函数时,使用=来给出默认值,例如
void enableFlags({bool bold = false, bool hidden = true}) {...}
或者位置可选参数的默认值
String say(String from, String msg, [String device = "none"]) {...}
Dart语言作为比Java更面向对象的语言,和JS一样,将函数作为了一等对象,也就是所有的函数都是对象类型,且可以赋值给变量,并且Dart还支持匿名函数或者说是Lambda
main() {
var list = ["test1", "test2", "test3"];
list.forEach((item) {
print("item is $item");
});
}
这几行代码就演示了list.forEarch函数的参数就是一个函数,而这个参数在赋值时传入了一个lambda表达式。
Dart的逻辑控制跟Java几乎一模一样,都有if-else,for循环,while,do-while循环。Dart的异常处理跟Java也非常类似,只是写法不太一样,抛异常使用throw指令,可以抛一个任意对象,而catch指令则需要用on来指定异常的类型,如下
try {
doSomeThing();
} on Exception catch(e) {
print("...$e");
}
同样的,Dart可以使用finally来处理try的任意情况,这跟Java是一样的,不论是否有抛出异常,都会执行finally块中的内容。
类与接口
  Dart中的类的概念跟Java也几乎一样,每个类都继承自Object类,且每个类都有且只有一个父类,每个类可以有n个子类。Dart对象的创建跟方法一样,可以省略new关键词,并且传构造参数时,不仅可以像Java那样按位置传,还可以使用命名参数来传,如下两种构造方法是等价的。
class Point {
num x, y;
Point(this.x, this.y);
}
var p1 = Point(1, 2);
var p2 = Point({'x':1, 'y': 2});
Dart可以使用对象的runtimeType属性来返回这个对象的Type对象,也就是它的对象类型。
关于Dart中如何来创建一个类,这跟Java也是非常相似的,
class Point {
num x;
num y;
num z = 0;
Point(num x, num y) {
this.x = x;
this.y = y;
}
}
由于Dart的所有变量默认值都是null,因此类中的属性默认值也都是null,当构造器传入参数对应其属性的话,则构造器可省略赋值语句,变成
class Point {
num x, y;
Point(this.x, this.y);
}
Dart还有一个叫作命名构造函数,其作用类似于Java的create生成方法,如下
class Point {
num x;
num y;
Point(this.x, this.y);
Point.fromJson(Map<String, num> map) {
this.x = map["x"];
this.y = map["y"];
}
Dart的继承也使用extends关键词,子类也默认不继承父类的构造函数,如要调用父类的构造函数,则需要在子类的构造函数中显式地调用父类的构造函数
class Point {
num x;
num y;
Point(this.x, this.y);
Point.fromJson(Map<String, num> map) {
this.x = map["x"];
this.y = map["y"];
}
}
class PointV3 extends Point {
num z;
PointV3(num x, num y, num z): super(x, y) {
this.z = z;
}
}
main() {
var p1 = Point(1, 2);
print("x=${p1.x},y=${p1.y}");
var p2 = Point.fromJson({"x":3, "y":4});
print("x=${p2.x},y=${p2.y}");
var p3 = PointV3(1, 2, 3);
print("x=${p3.x},y=${p3.y},z=${p3.z}");
}
Dart也支持抽象类和抽象方法,只不过抽象类中的抽象方法不再需要在方法前面声明abstract关键词了,只需要在类名前加abstract关键词,
abstract class Person {
say(String str);
}
class DXB extends Person {
say(String str) {
print("xiaobing say:$str");
}
}
main() {
var dxb = DXB();
dxb.say("Hello");
}
Dart同样支持接口,但支持方式比较奇葩,Dart让每个类都隐匿定义了一个接口,这个接口中包含了这个类所有的成员及其实现的接口。简单点理解就是如果类A要实现B的接口,而不是继承B的实现,就可以把B当成接口来implements,这样所有类都可以成为接口了,但前提是如果使用了implements,就不是使用的继承,也就是说需要在实现类中实现接口类中定义的所有方法
// person 类。 隐式接口里面包含了 greet() 方法声明。
class Person {
// 包含在接口里,但只在当前库中可见。
final _name;
// 不包含在接口里,因为这是一个构造函数。
Person(this._name);
// 包含在接口里。
String greet(String who) => 'Hello, $who. I am $_name.';
}
// person 接口的实现。
class Impostor implements Person {
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()));
}
和其他语言一样,Dart也使用enum关键词来支持枚举类型,其实我一直都觉得所有的面向对象语言的枚举设计都很鸡肋,要说枚举支持switch,那int也是支持的,所有地方的枚举都可以用int来代替,只能说编译期检查值范围是枚举唯一的用处了,但其实Java在Android中也是支持用注解来定义值范围的,比如用注解来标注传入的值必须是资源id类型或者特定字符串范围。
Dart同样也支持静态成员,静态成员属于类的,也用static来修饰。
Dart的范型
前面有提到Dart的集合类型,如Set、List和Map,这些集合类型都是带有范型参数的,与Java的范型有区别的是,Dart的范型是"固化的",简单点理解就是Dart的范型参数能够在支持期检查到,而Java的范型参数在编译期就会被擦除,最典型的现象就是Dart可以使用names is List<String>来判断集合的具体类型,而Java这样写直接就会报语法错误,只能判断name instanceOf List
说到范型就不得不说说协变和逆变了,所谓协变就是使用泛型集合get到的泛型值需要使用协变的父类型来引用,这样才能类型安全,而逆变则相反,是指add集合的值必须是逆变的子类
```
static class A {
}
static class B extends A {
}
static class C extends A {
}
static class D extends B {
}
static void test() {
List<? extends A> list1 = new ArrayList<>();
List<? super B> list2 = new ArrayList<>();
A a = new A();
B b = new B();
C c = new C();
D d = new D();
list1.add(a); // 语法错误
list1.add(b); // 语法错误
list1.add(c); // 语法错误
list1.add(d); // 语法错误
A a1 = list1.get(0);
B b1 = (B) list1.get(0);
C c1 = (C) list1.get(0);
D d1 = (D) list1.get(0);
list2.add(a); // 语法错误
list2.add(b);
list2.add(c); // 语法错误
list2.add(d);
A a2 = (A) list2.get(0);
B b2 = (B) list2.get(0);
C c2 = (C) list2.get(0);
D d2 = (D) list2.get(0);
}
```
在这段代码中,有一处是非常出乎人意料的,就是list1在add时,居然四种类型的值都会语法错误,这就是因为list1的泛型参数是协变的,协变是用来适配get的值使用父类引用的,因此A a1 = list1.get(0)就不需要强转。而list2.add(b)和list2.add(d)没有语法错误就是逆变在起作用,由于逆变可以add任意子类,因为在get时全部都要强转,而这些都是有可能会强转异常的。
Dart的导库
Dart跟Java一样,使用import关键词来导库,但库的定位不再是package指定的包名,而是一个URI,同时,导的库也可以使用as关键词来重命令,以此来避免库名重复导致的歧义。
Dart的导库还支持部分导入,用show关键词来选择性导入,用hide关键词来屏蔽导入。
Dart异步编程
Dart使用await、async和Future类型来提供异步支持。其中的Future比较类似于JS中的Promise,创建一个Promise时需要传入一个闭包,这个闭包中要执行耗时任务。同样Future创建时也传入一个匿名函数,这个匿名函数中可以执行耗时操作,同时Future还提供了then和catchError函数,也是要传入匿名函数,分别处理正常执行结果和异常结果,但执行then和catchError函数后,并不会马上执行耗时任务的匿名函数,这就是Dart提供的异步编译框架。
```
import 'dart:async';
Future<String> sayHello(String name) async {
return Future(() {
Future.delayed(const Duration(seconds: 1));
print("int call...");
return "hello $name";
});
}
main() async {
print("before call...");
sayHello("xiaobing")
.then((res) {
print(res);
})
.catchError((error) {
print(error);
});
print("after call...");
}
在上面这个例子中,打印结果是
before call...
after call...
int call...
hello xiaobing
分析可知先执行了main函数中的before call和after call,然后才执行耗时匿名函数,成功返回后将结果打印出来。
对于await和async的使用,可以想象这样一种场景,用网络请求登录,再获取用户信息,最后将用户信息保存到本地。这个场景中三个任务都是异步的,但三个任务是有先后关系的,如果只用Future,我们会写出如下嵌套层的代码
Future<String> login() {
return Future(() => "token");
}
Future<String> getUserInfo(String token) {
return Future(() => "xiaobing-$token");
}
Future<String> saveUserInfo(String name) {
return Future(() {
print("save $name");
return name;
});
}
main() {
login().then((token) => getUserInfo(token)
.then((name) => saveUserInfo(name).then((name) => print("saveUserInfo $name success..."))
));
}
但若使用async + await就可以避免这种嵌套写法
Future<String> login() {
return Future(() => "token");
}
Future<String> getUserInfo(String token) {
return Future(() => "xiaobing-$token");
}
Future<String> saveUserInfo(String name) {
return Future(() {
print("save $name");
return name;
});
}
main() async {
var token = await login();
var name = await getUserInfo(token);
var saveResult = await saveUserInfo(name);
print("saveUserInfo $saveResult success");
}
await指令是等待Future的耗时任务执行完,并将结果解包成普通类型,而aysnc是在使用await的函数上必须加的,因为await表示要等待,等待则必须异步,async则表示这个函数是个异步函数。