Dart
基础数据类型
Dart 主要类型有以下几种:
-
num
数值有两个子类型:- int : 任意长度的整数
- double : 双精度浮点数
-
String
Dart 中的 String 赋值可以使用单双引号,区别在于单引号不可使用
$
进行值插入。
除此之外,还可以使用三引号,进行多行字符串赋值。(与 Kotlin 类似,唯一不同即 Kotlin 没有单引号)。
String 字符串中,不同于 Java 的是\
默认具有转义效果,若要避免\
的转义作用,需要在字符串声明之前加上r
,即String s = r"换行\n"
。 -
bool
bool 值为其对应的值,非 bool 值都为 false
List
Map
变量声明
语言 | 变量 | 编译时常量 | 常量 |
---|---|---|---|
Dart | 具体类型、var | const (变量) | final (变量) |
Kotlin | var | const val | val |
函数
Dart 中,函数也是对象,没有指定返回值时返回 null
,即 函数可以作为函数的参数
。
函数声明有以下几种方式:
// 正常声明方式
String sayHello(String name){
println("Hello, $name");
}
// 类型可选,即可声明:
sayHello(name){
println("Hello, $name");
}// 此种方式不提倡,应明确函数的输入类型和输出类型,方便调用
// 在函数只返回一个简单表达式时,可以简略为一个 '=>' (Kotlin 中为 '=')
sayHello(name) => println("Hello, $name");
// 函数也是对象,也存在匿名函数
var sayHello = (name) => println("Hello, $name");
函数别名
// 声明一个函数别名
// typedef 返回值类型 别名名称(参数列表)
typedef void sayHello(String hellow)
// 它可以接收一个参数和返回值与别名相同的函数作为具体执行内容
// Kotlin 中为 (参数列表) -> 返回值类型
可选参数
Dart 支持两种可选参数:
-
位置可选参数(已弃用)
使用
[]
,默认值采用:
赋值,即// 声明 String sayHello(String name, [String greetings : 'Hello']) => println("$greetings, $name") // 调用 sayHello('name', 'hello')
但这种情况下,一旦位置可选参数多了之后很难辨别哪个对象对应哪个可选参数;
-
命名可选参数
使用
{}
,默认值使用=
赋值,即// 声明 String sayHello(String name, {String greetings = 'Hello'}) => print("$greetings, $name") // 调用 sayHello('name', greetings = 'hello')
这两种可选参数方式不可同时使用
PS : 作为一个后来者的语言, Kotlin 可选参数两种方式都支持但不需要 {}
、 []
操作符和流程控制语句
Dart 大多数内容和 C++、 Java 没什么区别,下面是几个有别于前两者的操作符
取整操作符 ~/
C++、 Java 之所以没有取整操作符,是因为在整数相除时默认结果为整数,而 Dart 中返回的是真实的结果:
int a = 3;
int b = 2;
print(a / b); // 1.5
print(a ~/ b); // 1
级联
当需要对单一对象进行一系列操作时,可以使用级联操作符 ..
(对应 Kotlin 中的 apply 扩展函数):
class Person {
String firstName;
String lastName;
}
Person p = Person(); // new 关键字可以省略
p .. firstName = 'firstName'
.. lastName = 'lastName';
if
if
通常用法与常规一致,在判断非 bool 值时, check 模式下会抛出异常, production 模式下会被当成 false.
循环
通常 for 循环与常规一致,若迭代对象为容器,则可以使用 forEach((T) -> function)
方法进行迭代。
Swicth Case
switch 的参数可以是 String 或者 num;如果分句的内容为空,则可以省略 break
达到落空(执行下一分句内容)效果;如果不为空仍想实现落空效果,需要手动指定标签和使用 continue
返回标签:
switch(0){
case 0:
case 1:
print('落空');
break;
case 2:
print('非空落空');
continue closed;
case 3:
print('x');
break;
closed:
case 4:
print('非空落空执行');
break;
}
异常
Dart 中,非空对象都可以作为异常抛出,如果 catch 没有指定类型,那么可以处理任何类型的异常(类似于 var)。其余与 Java 类似。
类和对象
Dart 与 Java 一致,所有对象都是类的实例,且所有类都是 Object
的子类(Kotlin 为 Any
)。
在 Dart2 中,创建对象可以省略 new
和 const
关键字。
构造函数中的值传递及构建常量
class Person {
String firstName;
String lastName;
// 值传递
Person(this.firstName, String lastName){
this.lastName = lastName;
}
// final 属性除了默认赋值之外,只能通过下两种方法进行初始化
// 初始化列表
Person(String a, String b) : firstName = a, lastName = b;
// 常量对象
const Person(this.firstName, this.lastName);
// 重定向构造函数
Person.polar(this.lastName, this.firstName)
Person(String firstName, String lastName) : this.polar(lastName, firstName)
}
构造函数执行流程:
class Base {
int a;
int b;
Base(this.a, int c) {
b = c;
}
}
class Ex : Base {
int d;
Ex(a, b , this.d) : super(a, b){
}
}
流程为:
Ex 对象被创建 -> 调用 Ex 初始化列表 -> 调用 Base 初始化列表 -> 调用 Base 构造方法内容 -> 调用 Ex 构造方法
工厂构造函数:
工厂构造函数类似于 static 静态变量,无法访问 this 指针。
工厂构造函数由 factory
前缀开头,它可能没有初始化列表或者初始化形式参数,相反,他们必须有一个返回一个对象的函数体。
class A {
String name;
static A cache;
factory A(String name){
if(cache == null){
A.cache = new A.newObject(name);
}
return A.cache;
}
}
Get Set 方法
类的每个字段都对应一个隐式的 Getter 和 Setter,调用与 Kotlin 一致。
可以使用 get
和 set
关键字扩展默认调用;如果字段为 final
或 const
时只有 Getter 方法。
class Person {
String firstName;
String lastName;
// get 扩展
String get firstName => firstName + lastName;
// set 扩展
set lastName => lastName = value
}
抽象类
Dart 中接口和类时统一的,类就是接口,同 Java 使用 abstract
方法来定义抽象类并且抽象类不能被实例化。
虽然都是接口但继承和实现仍与 Java 中类似。继承关键字为 extends
,实现关键字为 implements
。
- 当使用实现时,子类无法访问父类的参数,有点类似于 C++ 中的纯虚函数,但可以实现多个类的多个函数;
- Dart 与 Java 一致,采用单继承方式,子类可以继承父类非私有变量。
混入
Mixins
是用来在不同类中进行代码重用的一种方式。
声明一个 Mixins
类,需要创建一个继承自 Object 的、没有构造方法(即抽象类)的类:
abstract class MixinsExample {
bool isSelected = false;
void printCurrentState(){
if(isSelected){
print('Current state is selected');
}else{
print('Current state is unselected');
}
}
}
通过 with
关键字使用:
class Content with MixinsExample{
...
}
StringBuffer
字符串拼接类:
StringBuffer sb = new StringBuffer();
sb.write("first");
sb.writeAll(['array1', 'array2']);
print(sb.toString());
sb.clear();
容器
Dart 中的容器主要包括 List、Set、Map,与 Java 中的用法基本一致。
List
// 新建
List<int> count = new List<int>();
// 通过数组赋值
List<int> from = [3, 2, 1];
// 通过泛型指定
varfrom = <int>[3, 2, 1];
count.add(0);
count.addAll(from);
count.length; // 4
count[0]; // 0
// 排序,方法返回值 < 0 表示小于, = 0 表示等于, > 0 表示大于
count.sort((a, b) => a - b); // [0, 1, 2, 3]
count.remove(3); // 3
count.indexOf(2); // 2
count.removeAt(1); // 1
count.removeWhere((i) => i == 0); // 0
count.clear(); // count.length 为 0
Set
Set<String> set = new Set<String>();
set.addAll(['first', 'second', "third"]);
set.length; // 3
set.remove('second'); // true
set.contains('third'); // true
Set<String> newSet = new Set.from(['third', 'forth']);
// 交集
Set<String> intersection = set.inersection(newSet);
// 子集
intersection.isSubsetOf(set); // true
Map
Map<String, List<String>> map = {
'key1': ['array01', 'array02'],
'key2': ['array11', 'array12']
};
// 通过泛型指定
var map = <String, List<String>>{
'key1': ['array01', 'array02'],
'key2': ['array11', 'array12']
};
Map<String, String> sMap = new Map<String, String>();
sMap['key'] = 'value';
sMap.containsKey('key'); // true
sMap.keys; // ['key']
sMap.values; // ['value']
sMap.remove('key'); // 'value'
容器的迭代
List、Map 都继承自 EfficientLengthIterable,是可以进行迭代的,而 Set 自身实现了 forEach 方法,所以以上三种容器都可以通过 forEach((T) => function)
和 for(t in collection)
进行迭代。
导入库
导入的唯一必须参数是指定库的 URI。
对于内置库,导入需要使用特殊的 URI: dart:scheme;
对于其他库,可以使用文件的系统路径或 package:scheme 这样的格式。
前缀
当导入两个库中有冲突的命名时,可以通过添加前缀的方式来区分:
// 假设这两个库中都有一个 Element 类
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
Element element1 = Element();
Element element2 = lib2.Element();
部分导入
import 'package:lib1/lib1.dart' show func;
import 'package:lib2/lib2.dart' hide func;
延迟导入
在以下情况时可能需要延迟导入:
- 减少启动时间
- A/B 测试
- 加载较少使用的功能
import 'package:greetings/hello.dart' deferred as greeting;
Future greet() async{
// 可多次调用但只会导入一次
await gretting.loadLibrary();
gretting.greet();
}
异步
Dart 库有很多返回值为 Future 和 Stream 对象的方法,这些方法都是异步的,他们通常在设置完耗时操作(例如 I/O,网络请求)后直接返回,而不等待该方法执行完成。
Future
当需要使用 Future 执行完成之后的值,有两个选择:
-
使用
async
和await
使用
async
和await
是异步的,但从代码上看仿佛是一个同步操作,更便于理解:// await 必须在 async 方法中 Future checkVersion() async{ // 异步操作只在遇到第一个 await 表达式才会执行,然后返回一个 Future 对象 // 余下部分只在 await 表达式执行完成后才恢复执行 var version = await lookUpVersion(); ... }
-
使用 Future 的API
使用 Future 的then(function)
来设置在 Future 完成之后需要运行的代码:// 返回一个 Future 对象 HttpRequest.getString(url) .then((String result) { print(result); }).catchError(e){ ... };
then(function)
方法也提供了一个有效的按顺序执行异步方法的方式:// 以下方法全都为返回 Future 对象的异步方法 constlyQuery(url) .then((value) => expensiveWork(value)) .then((_) => lengthyComputation()) .then((_) => print('Done')) .catchError(e){ ... }; // 同等的使用 async 和 await 的方法 request() async { try{ final value = await constlyQuery(url); await expensiveWork(value); await lengthyComputation(); print('Done'); }catch(e){ ... } } // 有时不需要关注异步任务的执行顺序,只需要在全部执行完成后继续执行 Future delete() async => ... Future add() async => ... Future select() async => ... doJobs() async { await Future.wait([ delete(), add(), select() ]); print('Done'); }
Stream
如果想要从 Stream 获取值,也有两个选项:
-
使用 async 和一个异步的循环(await for)
Future main() async { await for(varOfType identifier in expression){ ... } }
异步循环的表达式必须有 Stream 类型,执行流程如下:
- 等待流读取出一个值;
- 将值放入循环主体内执行;
- 循环执行1、2两步直至 Stream 关闭。
如果需要退出监听,使用
break
或者return
,退出循环并且取消 Stream 的监听 -
使用 Stream API
通过 Stream 类的
listen()
方法,传入一个处理每个文件或目录的函数来订阅文件列表:void main(List<String> arguments) { // ... FileSystemEntity.isDirectory(searchPath).then((isDir) { if (isDir) { final startingDir = Directory(searchPath); startingDir .list( recursive: argResults[recursive], followLinks: argResults[followLinks]) .listen((entity) { if (entity is File) { searchFile(entity, searchTerms); } }); } else { searchFile(File(searchPath), searchTerms); } }); } // 同等的使用 async 和 await for 的方法 Future main(List<String> arguments) async { // ... if (await FileSystemEntity.isDirectory(searchPath)) { final startingDir = Directory(searchPath); await for (var entity in startingDir.list( recursive: argResults[recursive], followLinks: argResults[followLinks])) { if (entity is File) { searchFile(entity, searchTerms); } } } else { searchFile(File(searchPath), searchTerms); } } // 处理成功和失败 Future readFileAwaitFor() async { var config = File('config.txt'); Stream<List<int>> inputStream = config.openRead(); var lines = inputStream // 对值进行转换 .transform(utf8.decoder) .transform(LineSplitter()); try { await for (var line in lines) { print('Got ${line.length} characters from stream'); } print('file is now closed'); } catch (e) { print(e); } }
Generators
如果需要延迟发送一系列值,可以选择使用 generator function
:
-
同步生成 => 返回一个 Iterable 对象:
Iterable<int> naturalsTo(int n) sync* { int k = 0; while(k < n) // 使用 yield 提交数据 yield k++; }
-
异步生成 => 返回一个 Stream 对象:
Stream<int> naturalsTo(int n) async* { int k = 0; while(k < n) // 使用 yield 提交数据 yield k++; }
-
如果生成器是递归调用的,可以使用 yield* 来提升效率:
Iterable<int> naturalsTo(int n) sync* { if(n > 0){ yield n; yield* naturalsDownFrom(n - 1); } }
可调用的类
如果需要让类像一个方法一样被调用,实现 call
方法:
class FuctionLike{
call(String a, String b, String c) => "$a, $b, $c"
}
main(){
var fl = FunctionLike();
var out = fl('a', 'b', 'c'); // 'a, b, c'
}
隔离区 Isolates
为了充分发挥多核 CPU 的优势,通常并发执行一些共享内存的线程,但是共享状态的并发容易出错并很可能导致代码的复杂化。
Dart 代码都在隔离区内运行,为了确保不会从任何其他隔离区访问隔离区的状态,每个隔离区都有自己的内存堆。
注解
// 自定义注解
class Todo {
final String who;
final String what;
const Todo(this.who, this.what);
}
// 使用
@Todo('name', 'job')
void doSomething {
print('do something');
}