关键字
表头 | 表头 | 表头 | 表头 |
---|---|---|---|
abstract | else | import | show |
as | enum | in | static |
assert | export | interface | super |
async | extends | is | switch |
await | extension | late | sync |
break | external | library | this |
case | factory | mixin | throw |
catch | false | new | true |
class | final | null | try |
const | finally | on | typedef |
continue | for | operator | var |
covariant | Function | part | void |
default | get | required | while |
deferred | hide | rethrow | with |
do | if | return | yield |
dynamic | implements | set |
变量
下面的示例代码将创建一个变量并将其初始化:
var name="wxq";
变量仅存储对象的引用。这里名为 name 的变量存储了一个 String 类型对象的引用,“Bob” 则是该对象的值。
name 变量的类型被推断为 String,但是你可以为其指定类型。如果一个对象的引用不局限于单一的类型,可以将其指定为 Object(或 dynamic)类型。
Object name = 'Bob';
除此之外你也可以指定类型:
String name = 'Bob';
默认值
在 Dart 中,未初始化以及可空类型的变量拥有一个默认的初始值 null。(如果你未迁移至 空安全,所有变量都为可空类型。)即便数字也是如此,因为在 Dart 中一切皆为对象,数字也不例外。
int? lineCount;
assert(lineCount == null);
备注: assert() 的调用将会在生产环境的代码中被忽略掉。在开发过程中,assert(condition) 将会在 条件判断 为 false 时抛出一个异常。详情请查阅 Assert。
延迟初始化变量
Dart 2.12 新增了 late 修饰符,这个修饰符可以在以下情况中使用:
声明一个非空变量,但不在声明时初始化。
延迟初始化一个变量。
通常 Dart 的语义分析会在一个已声明为非空的变量被使用前检查它是否已经被赋值,但有时这个分析会失败。例如:在检查顶级变量和实例变量时,分析通常无法判断它们是否已经被初始化,因此不会进行分析。
如果你确定这个变量在使用前就已经被声明,但 Dart 判断失误的话,你可以在声明变量的时候使用 late 修饰来解决这个问题。
late String description;
void main() {
description = 'Feijoada!';
print(description);
}
若 late 标记的变量在使用前没有初始化,在变量被使用时会抛出运行时异常。
如果一个 late 修饰的变量在声明时就指定了初始化方法,那么它实际的初始化过程会发生在第一次被使用的时候。这样的延迟初始化在以下场景中会带来便利:
Dart 认为这个变量可能在后文中没被使用,而且初始化时将产生较大的代价。
你正在初始化一个实例变量,它的初始化方法需要调用 this。
在下面这个例子中,如果 temperature 变量从未被使用的话,那么 readThermometer() 将永远不会被调用:
// This is the program's only call to readThermometer().
late String temperature = readThermometer(); // Lazily initialized.
Final 和 Const
如果你不想更改一个变量,可以使用关键字 final 或者 const 修饰变量,这两个关键字可以替代 var 关键字或者加在一个具体的类型前。一个 final 变量只可以被赋值一次;一个 const 变量是一个编译时常量 (const 变量同时也是 final 的)。
备注:实例变量 可以是 final 的但不可以是 const,
下面的示例中我们创建并设置两个 final 变量:
final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';
你不能修改一个 final 变量的值:
name = 'Alice'; // Error: a final variable can only be set once.
使用关键字 const 修饰变量表示该变量为 编译时常量。如果使用 const 修饰类中的变量,则必须加上 static 关键字,即 static const(译者注:顺序不能颠倒)。在声明 const 变量时可以直接为其赋值,也可以使用其它的 const 变量为其赋值:
const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere
const 关键字不仅仅可以用来定义常量,还可以用来创建 常量值,该常量值可以赋予给任何变量。你也可以将构造函数声明为 const 的,这种类型的构造函数创建的对象是不可改变的。
var foo = const [];
final bar = const [];
const baz = []; // Equivalent to `const []`
如果使用初始化表达式为常量赋值可以省略掉关键字 const,比如上面的常量 baz 的赋值就省略掉了 const。详情请查阅 不要冗余地使用 const。
没有使用 final 或 const 修饰的变量的值是可以被更改的,即使这些变量之前引用过 const 的值。
foo = [1, 2, 3]; // Was const []
你可以在常量中使用 类型检查和强制类型转换 (is 和 as)、 集合中的 if 以及 展开操作符 (... 和 ...?):
const Object i = 3; // Where i is a const Object with an int value...
const list = [i as int]; // Use a typecast.
const map = {if (i is int) i: 'int'}; // Use is and collection if.
const set = {if (list is List<int>) ...list}; // ...and a spread.
可以查阅 Lists、Maps 和 Classes 获取更多关于使用 const 创建常量值的信息。
内置类型
Dart 语言支持下列内容:
Numbers
Dart 支持两种 Number 类型:
int 整数值;长度不超过 64 位,具体取值范围 依赖于不同的平台。在 DartVM 上其取值位于 -263 至 263 - 1 之间。在 Web 上,整型数值代表着 JavaScript 的数字(64 位无小数浮点型),其允许的取值范围在 -253 至 253 - 1 之间。
double 64 位的双精度浮点数字,且符合 IEEE 754 标准。
int 和 double 都是 num 的子类。 num 中定义了一些基本的运算符比如 +、-、*、/ 等,还定义了 abs()、ceil() 和 floor() 等方法(位运算符,比如 >> 定义在 int 中)。如果 num 及其子类不满足你的要求,可以查看 dart:math 库中的 API。
整数是不带小数点的数字,下面是一些定义整数字面量的例子:
var x = 1;
var hex = 0xDEADBEEF;
如果一个数字包含了小数点,那么它就是浮点型的。下面是一些定义浮点数字面量的例子:
var y = 1.1;
var exponents = 1.42e5;
You can also declare a variable as a num. If you do this, the variable can have both integer and double values.
num x = 1; // x can have both int and double values
x += 2.5;
整型字面量将会在必要的时候自动转换成浮点数字面量:
double z = 1; // Equivalent to double z = 1.0.
下面是字符串和数字之间转换的方式:
// String -> int
var one = int.parse('1');
assert(one == 1);
// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);
// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');
// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');
整型支持传统的位移操作,比如移位(<<、>> 和 >>>)、补码 (~)、按位与 (&)、按位或 (|) 以及按位异或 (^),例如:
assert((3 << 1) == 6); // 0011 << 1 == 0110
assert((3 | 4) == 7); // 0011 | 0100 == 0111
assert((3 & 4) == 0); // 0011 & 0100 == 0000
Strings
Dart 字符串(String 对象)包含了 UTF-16 编码的字符序列。可以使用单引号或者双引号来创建字符串:
var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";
// 代码中文解释
var s1 = '使用单引号创建字符串字面量。';
var s2 = "双引号也可以用于创建字符串字面量。";
var s3 = '使用单引号创建字符串时可以使用斜杠来转义那些与单引号冲突的字符串:\'。';
var s4 = "而在双引号中则不需要使用转义与单引号冲突的字符串:'";
在字符串中,请以 ${表达式} 的形式使用表达式,如果表达式是一个标识符,可以省略掉 {}。如果表达式的结果为一个对象,则 Dart 会调用该对象的 toString 方法来获取一个字符串。
var s = 'string interpolation';
assert('Dart has $s, which is very handy.' ==
'Dart has string interpolation, '
'which is very handy.');
assert('That deserves all caps. '
'${s.toUpperCase()} is very handy!' ==
'That deserves all caps. '
'STRING INTERPOLATION is very handy!');
// 代码中文解释
var s = '字符串插值';
assert('Dart 有$s,使用起来非常方便。' == 'Dart 有字符串插值,使用起来非常方便。');
assert('使用${s.substring(3,5)}表达式也非常方便' == '使用插值表达式也非常方便。');
== 运算符负责判断两个对象是否等同,比如,如果两个字符串包含一样的字符编码序列,则表示他们是等同的。
你可以使用 + 运算符或并列放置多个字符串来连接字符串:
var s1 = 'String '
'concatenation'
" works even over line breaks.";
assert(s1 ==
'String concatenation works even over '
'line breaks.');
var s2 = 'The + operator ' + 'works, as well.';
assert(s2 == 'The + operator works, as well.');
使用三个单引号或者三个双引号也能创建多行字符串:
var s1 = '''
你可以像这样创建多行字符串。
''';
var s2 = """这也是一个多行字符串。""";
在字符串前加上 r 作为前缀创建 “raw” 字符串(即不会被做任何处理(比如转义)的字符串):
var s = r'在 raw 字符串中,转义字符串 \n 会直接输出 “\n” 而不是转义为换行。';
字符串字面量是一个编译时常量,只要是编译时常量 (null、数字、字符串、布尔) 都可以作为字符串字面量的插值表达式:
// These work in a const string.
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';
// These do NOT work in a const string.
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = [1, 2, 3];
const validConstString = '$aConstNum $aConstBool $aConstString';
// const invalidConstString = '$aNum $aBool $aString $aConstList';
布尔类型 bool
Dart 使用 bool 关键字表示布尔类型,布尔类型只有两个对象 true 和 false,两者都是编译时常量。
Dart 的类型安全不允许你使用类似 if (nonbooleanValue) 或者 assert (nonbooleanValue) 这样的代码检查布尔值。相反,你应该总是显式地检查布尔值,比如像下面的代码这样:
// Check for an empty string.
var fullName = '';
assert(fullName.isEmpty);
// Check for zero.
var hitPoints = 0;
assert(hitPoints <= 0);
// Check for null.
var unicorn = null;
assert(unicorn == null);
// Check for NaN.
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);
Lists
数组 (Array) 是几乎所有编程语言中最常见的集合类型,在 Dart 中数组由 List 对象表示。通常称之为 List。
Dart 中的列表字面量是由逗号分隔的一串表达式或值并以方括号 ([]) 包裹而组成的。下面是一个 Dart List 的示例:
var list = [1, 2, 3];
这里 Dart 推断出 list 的类型为 List,如果往该数组中添加一个非 int 类型的对象则会报错。你可以阅读 类型推断 获取更多相关信息。
你可以在 Dart 的集合类型的最后一个项目后添加逗号。这个尾随逗号并不会影响集合,但它能有效避免「复制粘贴」的错误。
var list = [
'Car',
'Boat',
'Plane',
];
List 的下标索引从 0 开始,第一个元素的下标为 0,最后一个元素的下标为 list.length - 1。你可以像 JavaScript 中的用法那样获取 Dart 中 List 的长度以及元素:
var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);
list[1] = 1;
assert(list[1] == 1);
在 List 字面量前添加 const 关键字会创建一个编译时常量:
var constantList = const [1, 2, 3];
// constantList[1] = 1; // This line will cause an er
Dart 在 2.3 引入了 扩展操作符(...)和 空感知扩展操作符(...?),它们提供了一种将多个元素插入集合的简洁方法。
例如,你可以使用扩展操作符(...)将一个 List 中的所有元素插入到另一个 List 中:
// 明显参考的js
var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);
如果扩展操作符右边可能为 null ,你可以使用 null-aware 扩展操作符(...?)来避免产生异常:
var list2 = [0, ...?list];
assert(list2.length == 1);
Dart 还同时引入了 集合中的 if 和 集合中的 for 操作,在构建集合时,可以使用条件判断 (if) 和循环 (for)。
下面示例是使用 集合中的 if 来创建一个 List 的示例,它可能包含 3 个或 4 个元素:
var nav = ['Home', 'Furniture', 'Plants', if (promoActive) 'Outlet'];
下面是使用 集合中的 for 将列表中的元素修改后添加到另一个列表中的示例:
var listOfInts = [1, 2, 3];
var listOfStrings = ['#0', for (var i in listOfInts) '#$i'];
assert(listOfStrings[1] == '#1');
Sets
在 Dart 中,set 是一组特定元素的无序集合。 Dart 支持的集合由集合的字面量和 Set 类提供。
下面是使用 Set 字面量来创建一个 Set 集合的方法:
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
可以使用在 {} 前加上类型参数的方式创建一个空的 Set,或者将 {} 赋值给一个 Set 类型的变量:
var names = <String>{};
// Set<String> names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.
Set 还是 map? Map 字面量语法相似于 Set 字面量语法。因为先有的 Map 字面量语法,所以 {} 默认是 Map 类型。如果忘记在 {} 上注释类型或赋值到一个未声明类型的变量上,那么 Dart 会创建一个类型为 Map<dynamic, dynamic> 的对象。
使用 add() 方法或 addAll() 方法向已存在的 Set 中添加项目:
var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
使用 .length 可以获取 Set 中元素的数量:
var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
assert(elements.length == 5); // set 集合内容不重复
可以在 Set 变量前添加 const 关键字创建一个 Set 编译时常量:
final constantSet = const {
'fluorine',
'chlorine',
'bromine',
'iodine',
'astatine',
};
// constantSet.add('helium'); // This line will cause an error.
Maps
通常来说,Map 是用来关联 keys 和 values 的对象。其中键和值都可以是任何类型的对象。每个 键 只能出现一次但是 值 可以重复出现多次。 Dart 中 Map 提供了 Map 字面量以及 Map 类型两种形式的 Map。
下面是一对使用 Map 字面量创建 Map 的例子:
var gifts = {
"first":"partirge",
'second': 'turtledoves',
'fifth': 'golden rings'
}
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
Dart 将 gifts 变量的类型推断为 Map<String, String>,而将 nobleGases 的类型推断为 Map<int, String>。如果你向这两个 Map 对象中添加不正确的类型值,将导致运行时异常。你可以阅读 类型推断 获取更多相关信息。
你也可以使用 Map 的构造器创建 Map:
var gifts = Map<String, String>();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
var nobleGases = Map<int, String>();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';
如果你之前是使用的 C# 或 Java 这样的语言,也许你想使用 new Map() 构造 Map 对象。但是在 Dart 中,new 关键词是可选的。(译者注:且不被建议使用) 你可以查阅 构造函数的使用 获取更多相关信息。
向现有的 Map 中添加键值对与 JavaScript 的操作类似:
var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds'; // Add a key-value pair
从一个 Map 中获取一个值的操作也与 JavaScript 类似:
var gifts = {'first': 'partridge'};
assert(gifts['first'] == 'partridge');
如果检索的 Key 不存在于 Map 中则会返回一个 null:
var gifts = {'first': 'partridge'};
assert(gifts['fifth'] == null);
使用 .length 可以获取 Map 中键值对的数量:
var gifts = {'first': 'partridge'};
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);
在一个 Map 字面量前添加 const 关键字可以创建一个 Map 编译时常量:
final constantMap = const {
2: 'helium',
10: 'neon',
18: 'argon',
};
// constantMap[2] = 'Helium'; // This line will cause an error.
Map 可以像 List 一样支持使用扩展操作符(... 和 ...?)以及集合的 if 和 for 操作。你可以查阅 List 扩展操作符 和 List 集合操作符 获取更多相关信息。
函数
Dart 是一种真正面向对象的语言,所以即便函数也是对象并且类型为 Function,这意味着函数可以被赋值给变量或者作为其它函数的参数。你也可以像调用函数一样调用 Dart 类的实例。
下面是定义一个函数的例子:
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
虽然高效 Dart 指南建议在 公开的 API 上定义返回类型,不过即便不定义,该函数也依然有效:
isNoble(atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
语法 => 表达式 是 { return 表达式; } 的简写, => 有时也称之为 箭头 函数。
在 => 与 ; 之间的只能是 表达式 而非 语句。比如你不能将一个 if语句 放在其中,但是可以放置 条件表达式。
参数
函数可以有两种形式的参数:必要参数 和 可选参数。必要参数定义在参数列表前面,可选参数则定义在必要参数后面。可选参数可以是 命名的 或 位置的。 某些 API(特别是 Flutter 控件的构造器)只使用命名参数,即便参数是强制性的
向函数传入参数或者定义函数参数时,可以使用 尾逗号。
命名参数 命名参数默认为可选参数,除非他们被特别标记为 required。
定义函数时,使用 {参数1, 参数2, …} 来指定命名参数:如果你没有提供一个默认值,也没有使用 required 标记的话,那么它一定可空的类型,因为他们的默认值会是 null:
命名参数
命名参数默认为可选参数,除非他们被特别标记为 required。
定义函数时,使用 {参数1, 参数2, …} 来指定命名参数:如果你没有提供一个默认值,也没有使用 required 标记的话,那么它一定可空的类型,因为他们的默认值会是 null:
/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool? bold, bool? hidden}) {...}
当调用函数时,你可以使用 参数名: 参数值 指定一个命名参数的值。例如:
enableFlags(bold: true, hidden: false);
你可以使用 = 来为一个命名参数指定除了 null 以外的默认值。指定的默认值必须要为编译时的常量,例如:
/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold = false, bool hidden = false}) {...}
// bold will be true; hidden will be false.
enableFlags(bold: true);
如果你希望一个命名参数是强制需要使用的,调用者需要提供它的值,则你可以使用 required 进行声明:
const Scrollbar({super.key, required Widget child});
当你创建一个不带 child 参数的 Scrollbar 时,分析器就会报告这里出了问题。
一个标记了 required 的参数仍然是可空的类型:
const Scrollbar({super.key, required Widget? child});
尽管将位置参数放在最前面通常比较合理,但你也可以将命名参数放在参数列表的任意位置,让整个调用的方式看起来更适合你的 API:
repeat(times: 2, () {
...
});
可选的位置参数 使用 [] 将一系列参数包裹起来,即可将其标记为位置参数,因为它们的默认值是 null,所以如果你没有提供默认值的话,它们的类型必须得是允许为空 (nullable) 的类型。
String say(String from, String msg, [String? device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
下面是不使用可选参数调用上述函数的示例
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
下面是使用可选参数调用上述函数的示例:
assert(say('Bob', 'Howdy', 'smoke signal') ==
'Bob says Howdy with a smoke signal');
你可以使用 = 来为一个位置可选参数指定除了 null 以外的默认值。指定的默认值必须要为编译时的常量,例如:
String say(String from, String msg, [String device = 'carrier pigeon']) {
var result = '$from says $msg with a $device';
return result;
}
assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');
main() 函数
每个 Dart 程序都必须有一个 main() 顶级函数作为程序的入口, main() 函数返回值为 void 并且有一个 List 类型的可选参数。
下面是一个简单 main() 函数:
void main() {
print('Hello, World!');
}
函数是一级对象
可以将函数作为参数传递给另一个函数。例如:
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// Pass printElement as a parameter.
list.forEach(printElement);
你也可以将函数赋值给一个变量,比如:
该示例中使用了匿名函数。
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');
匿名函数
大多数方法都是有名字的,比如 main() 或 printElement()。你可以创建一个没有名字的方法,称之为 匿名函数、 Lambda 表达式 或 Closure 闭包。你可以将匿名方法赋值给一个变量然后使用它,比如将该变量添加到集合或从中删除。
匿名方法看起来与命名方法类似,在括号之间可以定义参数,参数之间用逗号分割。
后面大括号中的内容则为函数体:
([[类型] 参数[, …]]) {
函数体;
};
下面代码定义了只有一个参数 item 且没有参数类型的匿名方法。 List 中的每个元素都会调用这个函数,打印元素位置和值的字符串:
const list = ['apples', 'bananas', 'oranges'];
list.map((item) {
return item.toUpperCase();
}).forEach((item) {
print('$item: ${item.length}');
});
如果函数体内只有一行返回语句,你可以使用胖箭头缩写法。
list
.map((item) => item.toUpperCase())
.forEach((item) => print('$item: ${item.length}'));
词法作用域
Dart 是词法有作用域语言,变量的作用域在写代码的时候就确定了,大括号内定义的变量只能在大括号内访问,与 Java 类似。
下面是一个嵌套函数中变量在多个作用域中的示例:
bool topLevel = true;
void main() {
var insideMain = true;
void myFunction() {
var insideFunction = true;
void nestedFunction() {
var insideNestedFunction = true;
assert(topLevel);
assert(insideMain);
assert(insideFunction);
assert(insideNestedFunction);
}
}
}
词法闭包
闭包 即一个函数对象,即使函数对象的调用在它原始作用域之外,依然能够访问在它词法作用域内的变量。
函数可以封闭定义到它作用域内的变量。接下来的示例中,函数 makeAdder() 捕获了变量 addBy。无论函数在什么时候返回,它都可以使用捕获的 addBy 变量。
Function makeaddr(int addBy){
return (int i)=>addBy+1
}
void main() {
// Create a function that adds 2.
var add2 = makeAdder(2);
// Create a function that adds 4.
var add4 = makeAdder(4);
assert(add2(3) == 5);
assert(add4(3) == 7);
//
}
测试函数是否相等
下面是顶级函数、静态方法和实例方法相等性的测试示例:
void foo() {} // A top-level function
class A {
static void bar() {} // A static method
void baz() {} // An instance method
}
void main() {
Function x;
// Comparing top-level functions.
x = foo;
assert(foo == x);
// Comparing static methods.
x = A.bar;
assert(A.bar == x);
// Comparing instance methods.
var v = A(); // Instance #1 of A
var w = A(); // Instance #2 of A
var y = w;
x = w.baz;
// These closures refer to the same instance (#2),
// so they're equal.
assert(y.baz == x);
// These closures refer to different instances,
// so they're unequal.
assert(v.baz != w.baz);
}
返回值
所有的函数都有返回值。没有显式返回语句的函数最后一行默认为执行 return null;。
foo() {}
assert(foo() == null);
运算符
Dart 支持下表所示的操作符,它也体现了 Dart 运算符的关联性和 优先级 的从高到低的顺序。这也是 Dart 运算符关系的近似值。你可以将这些运算符实现为 一个类的成员。
Description | OperatorAssociativity | Associativity |
---|---|---|
unary postfix | expr++ expr-- () [] ?[] . ?. ! | None |
unary prefix | -expr !expr ~expr ++expr --expr await expr | None |
multiplicative | * / % ~/ | Left |
additive | + - | Left |
shift | << >> >>> | Left |
bitwise AND | & | Left |
bitwise XOR | Left | |
bitwise OR | "1" | Left |
relational and type test | >= > <= < as is is! | None |
equality | == != | None |
logical AND | && | Left |
logical OR | 两竖 | Left |
if null | ?? | Left |
conditional | expr1 ? expr2 : expr3 | Right |
cascade | .. ?.. | Left |
assignment | = *= /= += -= &= ^= etc. | Right |
一旦你使用了运算符,就创建了表达式。下面是一些运算符表达式的示例:
a++
a + b
a = b
a == b
c ? a : b
a is T
在 运算符表 中,运算符的优先级按先后排列,即第一行优先级最高,最后一行优先级最低,而同一行中,最左边的优先级最高,最右边的优先级最低。例如:% 运算符优先级高于 == ,而 == 高于 &&。根据优先级规则,那么意味着以下两行代码执行的效果相同:
// Parentheses improve readability.
if ((n % i == 0) && (d % i == 0)) ...
// Harder to read, but equivalent.
if (n % i == 0 && d % i == 0) ...
对于有两个操作数的运算符,左边的操作数决定了运算符的功能。比如对于一个 Vector 对象和一个 Point 对象,表达式 aVector + aPoint 中所使用的是 Vector 对象中定义的相加运算符 (+)。
算术运算符
Dart 支持常用的算术运算符:
运算符 | 描述 |
---|---|
+ | 加 |
- | 减 |
-表达式 | 一元负, 也可以作为反转(反转表达式的符号) |
* | 乘 |
/ | 除 |
~/ | 除并取整 |
% | 取模 |
是列
assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // Result is a double
assert(5 ~/ 2 == 2); // Result is an int
assert(5 % 2 == 1); // Remainder
assert('5/2 = ${5 ~/ 2} r ${5 % 2}' == '5/2 = 2 r 1');
Dart 还支持自增自减操作。
int a;
int b;
a = 0;
b = ++a; // Increment a before b gets its value.
assert(a == b); // 1 == 1
a = 0;
b = a++; // Increment a AFTER b gets its value.
assert(a != b); // 1 != 0
a = 0;
b = --a; // Decrement a before b gets its value.
assert(a == b); // -1 == -1
a = 0;
b = a--; // Decrement a AFTER b gets its value.
assert(a != b); // -1 != 0
关系运算符
Operator== | 相等 |
---|---|
!= | 不等 |
大于 | |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
类型判断运算符
as、is、is! 运算符是在运行时判断对象类型的运算符。
Operator | Meaning |
---|---|
as | 类型转换(也用作指定 库前缀)) |
is | 如果对象是指定类型则返回 true |
is! | 如果对象是指定类型则返回 false |
当且仅当 obj 实现了 T 的接口,obj is T 才是 true。例如 obj is Object 总为 true,因为所有类都是 Object 的子类。 |
仅当你确定这个对象是该类型的时候,你才可以使用 as 操作符可以把对象转换为特定的类型。例如:
(employee as Person).firstName = 'Bob';
如果你不确定这个对象类型是不是 T,请在转型前使用 is T 检查类型。
if (employee is Person) {
// Type check
employee.firstName = 'Bob';
}
备注:
上述两种方式是有区别的:如果 employee 为 null 或者不为 Person 类型,则第一种方式将会抛出异常,而第二种不会。
赋值运算符
可以使用 = 来赋值,同时也可以使用 ??= 来为值为 null 的变量赋值。
// Assign value to a
a = value;
// Assign value to b if b is null; otherwise, b stays the same
b ??= value;
像 += 这样的赋值运算符将算数运算符和赋值运算符组合在了一起。
= *= %= >>>= ^=
+= /= <<= &= |=
-= ~/= >>=
下面的例子展示了如何使用赋值以及复合赋值运算符:
var a = 2; // Assign using =
a *= 3; // Assign and multiply: a = a * 3
assert(a == 6);
逻辑运算符
使用逻辑运算符你可以反转或组合布尔表达式。
运算符 | 描述 |
---|---|
!表达式 | 对表达式结果取反(即将 true 变为 false,false 变为 true) |
| | | 逻辑或 |
&& | 逻辑与 |
下面是使用逻辑表达式的示例:
if (!done && (col == 0 || col == 3)) {
// ...Do something...
}
位运算符
在 Dart 中,二进制位运算符可以操作二进制的某一位,但仅适用于整数。
运算符 | 描述 |
---|---|
& | 按位与 |
| | 按位或 |
按位异或 | |
~表达式 | 按位取反(即将 “0” 变为 “1”,“1” 变为 “0”) |
<< | 位左移 |
|位右移
|无符号右移
下面是使用按位和移位运算符的示例:
final value = 0x22;
final bitmask = 0x0f;
assert((value & bitmask) == 0x02); // AND
assert((value & ~bitmask) == 0x20); // AND NOT
assert((value | bitmask) == 0x2f); // OR
assert((value ^ bitmask) == 0x2d); // XOR
assert((value << 4) == 0x220); // Shift left
assert((value >> 4) == 0x02); // Shift right
assert((value >>> 4) == 0x02); // Unsigned shift right
assert((-value >> 4) == -0x03); // Shift right
assert((-value >>> 4) > 0); // Unsigned shift right
条件表达式
Dart 有两个特殊的运算符可以用来替代 if-else 语句:
条件 ? 表达式 1 : 表达式 2 如果条件为 true,执行表达式 1并返回执行结果,否则执行表达式 2 并返回执行结果。
表达式 1 ?? 表达式 2 如果表达式 1 为非 null 则返回其值,否则执行表达式 2 并返回其值。
根据布尔表达式确定赋值时,请考虑使用 ? 和 :
var visibility = isPublic ? 'public' : 'private';
如果赋值是根据判定是否为 null 则考虑使用 ??。
String playerName(String? name) => name ?? 'Guest';
上述示例还可以写成至少下面两种不同的形式,只是不够简洁:
// Slightly longer version uses ?: operator.
String playerName(String? name) => name != null ? name : 'Guest';
// Very long version uses if-else statement.
String playerName(String? name) {
if (name != null) {
return name;
} else {
return 'Guest';
}
}
级联运算符
级联运算符 (.., ?..) 可以让你在同一个对象上连续调用多个对象的变量或方法。
比如下面的代码:
var paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
其他运算符
大多数其它的运算符,已经在其它的示例中使用过:
运算符 | 名字 | 描述 |
---|---|---|
() | 使用方法 | 代表调用一个方法 |
[] | 访问 List | 访问 List 中特定位置的元素 |
?[] | 判空访问 List | 左侧调用者不为空时,访问 List 中特定位置的元素 |
. | 访问成员 | 成员访问符 |
?. | 条件访问成员 | 与上述成员访问符类似,但是左边的操作对象不能为 null,例如 foo?.bar,如果 foo 为 null 则返回 null ,否则返回 bar |
! | 空断言操作符 | 将表达式的类型转换为其基础类型,如果转换失败会抛出运行时异常。例如 foo!.bar,如果 foo 为 null,则抛出运行时异常 |
流程控制语句
你可以使用下面的语句来控制 Dart 代码的执行流程:
-
if 和 else
-
for 循环
-
while 和 do-while 循环
-
break 和 continue
-
switch 和 case
-
assert
If 和 Else
Dart 支持 if - else 语句,其中 else 是可选的,比如下面的例子。你也可以参考条件表达式。
if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}
Dart 的 if 语句中的条件必须是布尔值而不能为其它类型。
For 循环
你可以使用标准的 for 循环进行迭代。例如:
var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
message.write('!');
}
在 Dart 语言中,for 循环中的闭包会自动捕获循环的 索引值 以避免 JavaScript 中一些常见的陷阱。假设有如下代码:
var callbacks = [];
for (var i = 0; i < 2; i++) {
callbacks.add(() => print(i));
}
for (final c in callbacks) {
c();
}
上述代码执行后会输出 0 和 1,但是如果在 JavaScript 中执行同样的代码则会输出两个 2。
如果要遍历的对象是一个可迭代对象(例如 List 或 Set),并且你不需要知道当前的遍历索引,则可以使用 for-in 方法进行 遍历:
for (final candidate in candidates) {
candidate.interview();
}
可迭代对象同时可以使用 forEach() 方法作为另一种选择:
var collection = [1, 2, 3];
collection.forEach(print); // 1 2 3
While 和 Do-While
while 循环会在执行循环体前先判断条件:
do-while 循环则会 先执行一遍循环体 再判断条件:
do {
printLine();
} while (!atEndOfPage());
Break 和 Continue
使用 break 可以中断循环:
while (true) {
if (shutDownRequested()) break;
processIncomingRequests();
}
使用 continue 可以跳过本次循环直接进入下一次循环:
for (int i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate.yearsExperience < 5) {
continue;
}
candidate.interview();
}
如果你正在使用诸如 List 或 Set 之类的 Iterable 对象,你可以用以下方式重写上述例子:
candidates
.where((c) => c.yearsExperience >= 5)
.forEach((c) => c.interview());
Switch 和 Case Switch 语句在 Dart 中使用 == 来比较整数、字符串或编译时常量,比较的两个对象必须是同一个类型且不能是子类并且没有重写 == 操作符。 枚举类型非常适合在 Switch 语句中使用。
每一个非空的 case 子句都必须有一个 break 语句,也可以通过 continue、throw 或者 return 来结束非空 case 语句。
不匹配任何 case 语句的情况下,会执行 default 子句中的代码:
var command = 'OPEN';
switch (command) {
case 'CLOSED':
executeClosed();
break;
case 'PENDING':
executePending();
break;
case 'APPROVED':
executeApproved();
break;
case 'DENIED':
executeDenied();
break;
case 'OPEN':
executeOpen();
break;
default:
executeUnknown();
}
下面的例子忽略了 case 子句的 break 语句,因此会产生错误:
var command = 'OPEN';
switch (command) {
case 'OPEN':
executeOpen();
// ERROR: Missing break
case 'CLOSED':
executeClosed();
break;
}
但是,Dart 支持空的 case 语句,允许其以 fall-through 的形式执行。
var command = 'CLOSED';
switch (command) {
case 'CLOSED': // Empty case falls through.
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();
break;
}
在非空 case 语句中想要实现 fall-through 的形式,可以使用 continue 语句配合 label 的方式实现:
var command = 'CLOSED';
switch (command) {
case 'CLOSED':
executeClosed();
continue nowClosed;
// Continues executing at the nowClosed label.
nowClosed:
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();
break;
}
每个 case 子句都可以有局部变量且仅在该 case 语句内可见。
断言
在开发过程中,可以在条件表达式为 false 时使用 — assert(条件, 可选信息); — 语句来打断代码的执行。你可以在本文中找到大量使用 assert 的例子。下面是相关示例:
// Make sure the variable has a non-null value.
assert(text != null);
// Make sure the value is less than 100.
assert(number < 100);
// Make sure this is an https URL.
assert(urlString.startsWith('https'));
异常
Dart 代码可以抛出和捕获异常。异常表示一些未知的错误情况,如果异常没有捕获则会被抛出从而导致抛出异常的代码终止执行。
与 Java 不同的是,Dart 的所有异常都是非必检异常,方法不必声明会抛出哪些异常,并且你也不必捕获任何异常。
Dart 提供了 Exception 和 Error 两种类型的异常以及它们一系列的子类,你也可以定义自己的异常类型。但是在 Dart 中可以将任何非 null 对象作为异常抛出而不局限于 Exception 或 Error 类型。
抛出异常
下面是关于抛出或者 引发 异常的示例:
throw FormatException('Expected at least 1 section');
你也可以抛出任意的对象:
throw 'Out of llamas!';
因为抛出异常是一个表达式,所以可以在 => 语句中使用,也可以在其他使用表达式的地方抛出异常:
void distanceTo(Point other) => throw UnimplementedError();
捕获异常
捕获异常可以避免异常继续传递(重新抛出异常除外)。捕获一个异常可以给你处理它的机会:
try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
}
对于可以抛出多种异常类型的代码,也可以指定多个 catch 语句,每个语句分别对应一个异常类型,如果 catch 语句没有指定异常类型则表示可以捕获任意异常类型:
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 来捕获异常,使用 on 来指定异常类型,使用 catch 来捕获异常对象,两者可同时使用。
你可以为 catch 方法指定两个参数,第一个参数为抛出的异常对象,第二个参数为栈信息 StackTrace 对象:
try {
// ···
} on Exception catch (e) {
print('Exception details:\n $e');
} catch (e, s) {
print('Exception details:\n $e');
print('Stack trace:\n $s');
}
关键字 rethrow 可以将捕获的异常再次抛出:
void misbehave() {
try {
dynamic foo = true;
print(foo++); // Runtime error
} catch (e) {
print('misbehave() partially handled ${e.runtimeType}.');
rethrow; // Allow callers to see the exception.
}
}
void main() {
try {
misbehave();
} catch (e) {
print('main() finished handling ${e.runtimeType}.');
}
}
Finally
无论是否抛出异常,finally 语句始终执行,如果没有指定 catch 语句来捕获异常,则异常会在执行完 finally 语句后抛出:
try {
breedMoreLlamas();
} finally {
// Always clean up, even if an exception is thrown.
cleanLlamaStalls();
}
类
Dart 是支持基于 mixin 继承机制的面向对象语言,所有对象都是一个类的实例,而除了 Null 以外的所有的类都继承自 Object 类。 基于 mixin 的继承 意味着尽管每个类(top class Object? 除外)都只有一个超类,一个类的代码可以在其它多个类继承中重复使用。 扩展方法 是一种在不更改类或创建子类的情况下向类添加功能的方式。
使用类的成员
对象的 成员 由函数和数据(即 方法 和 实例变量)组成。方法的 调用 要通过对象来完成,这种方式可以访问对象的函数和数据。
使用(.)来访问对象的实例变量或方法:
var p=Point(2,2)
assert(p.y==2)
使用 ?. 代替 . 可以避免因为左边表达式为 null 而导致的问题:
// If p is non-null, set a variable equal to its y value.
var a = p?.y;
使用构造函数
可以使用 构造函数 来创建一个对象。构造函数的命名方式可以为 类名 或 类名 . 标识符 的形式。例如下述代码分别使用 Point() 和 Point.fromJson() 两种构造器创建了 Point 对象:
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
以下代码具有相同的效果,但是构造函数名前面的的 new 关键字是可选的:
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
一些类提供了常量构造函数。使用常量构造函数,在构造函数名之前加 const 关键字,来创建编译时常量时:
var p = const ImmutablePoint(2, 2);
两个使用相同构造函数相同参数值构造的编译时常量是同一个对象:
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // They are the same instance!
在 常量上下文 场景中,你可以省略掉构造函数或字面量前的 const 关键字。例如下面的例子中我们创建了一个常量 Map:
// Lots of const keywords here.
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
根据上下文,你可以只保留第一个 const 关键字,其余的全部省略:
// Only one const, which establishes the constant context.
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
但是如果无法根据上下文判断是否可以省略 const,则不能省略掉 const 关键字,否则将会创建一个 非常量对象 例如:
var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant
assert(!identical(a, b)); // NOT the same instance!
获取对象的类型
可以使用 Object 对象的 runtimeType 属性在运行时获取一个对象的类型,该对象类型是 Type 的实例。
print('The type of a is ${a.runtimeType}');
实例变量
下面是声明实例变量的示例:
class Point {
double? x; // Declare instance variable x, initially null.
double? y; // Declare y, initially null.
double z = 0; // Declare z, initially 0.
}
所有未初始化的实例变量其值均为 null。
所有实例变量均会隐式地声明一个 Getter 方法。非终值的实例变量和 late final 声明但未声明初始化的实例变量还会隐式地声明一个 Setter 方法。你可以查阅 Getter 和 Setter 获取更多相关信息。
如果你在实例中声明了没有 late 修饰的变量,它会在实例初始化时早于构造方法进行赋值。因此,没有使用 late修饰的变量无法访问到 this。
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.
assert(point.x == 4); // Use the getter method for x.
assert(point.y == null); // Values default to null.
}
构造函数
声明一个与类名一样的函数即可声明一个构造函数(对于命名式构造函数 还可以添加额外的标识符)。大部分的构造函数形式是生成式构造函数,其用于创建一个类的实例:
class Point {
double x = 0;
double y = 0;
Point(double x, double y) {
// See initializing formal parameters for a better way
// to initialize instance variables.
this.x = x;
this.y = y;
}
}
使用 this 关键字引用当前实例。
备注:当且仅当命名冲突时使用 this 关键字才有意义,否则 Dart 会忽略 this 关键字。
终值初始化
对于大多数编程语言来说在构造函数中为实例变量赋值的过程都是类似的,而 Dart 则提供了一种特殊的语法糖来简化该步骤。
构造中初始化的参数可以用于初始化非空或 final 修饰的变量,它们都必须被初始化或提供一个默认值。
class Point {
final double x;
final double y;
// Sets the x and y instance variables
// before the constructor body runs.
Point(this.x, this.y);
}
在初始化时出现的变量默认是隐式终值,且只在初始化时可用。
默认构造函数 如果你没有声明构造函数,那么 Dart 会自动生成一个无参数的构造函数并且该构造函数会调用其父类的无参数构造方法。
构造函数不被继承 子类不会继承父类的构造函数,如果子类没有声明构造函数,那么只会有一个默认无参数的构造函数。
命名式构造函数 可以为一个类声明多个命名式构造函数来表达更明确的意图:
const double xOrigin = 0;
const double yOrigin = 0;
class Point {
final double x;
final double y;
Point(this.x, this.y);
// Named constructor
Point.origin()
: x = xOrigin,
y = yOrigin;
}
记住构造函数是不能被继承的,这将意味着子类不能继承父类的命名式构造函数,如果你想在子类中提供一个与父类命名构造函数名字一样的命名构造函数,则需要在子类中显式地声明。
调用父类非默认构造函数 默认情况下,子类的构造函数会调用父类的匿名无参数构造方法,并且该调用会在子类构造函数的函数体代码执行前,如果子类构造函数还有一个 初始化列表,那么该初始化列表会在调用父类的该构造函数之前被执行,总的来说,这三者的调用顺序如下:
-
初始化列表
-
父类的无参数构造函数
-
当前类的构造函数
如果父类没有匿名无参数构造函数,那么子类必须调用父类的其中一个构造函数,为子类的构造函数指定一个父类的构造函数只需在构造函数体前使用(:)指定。
下面的示例中,Employee 类的构造函数调用了父类 Person 的命名构造函数。点击运行按钮执行示例代码。
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().
Employee.fromJson(super.data) : super.fromJson() {
print('in Employee');
}
}
void main() {
var employee = Employee.fromJson({});
print(employee);
// Prints:
// in Person
// in Employee
// Instance of 'Employee'
}
因为参数会在子类构造函数被执行前传递给父类的构造函数,因此该参数也可以是一个表达式,比如一个函数:
class Employee extends Person {
Employee() : super.fromJson(fetchDefaultData());
// ···
}
传递给父类构造函数的参数不能使用 this 关键字,因为在参数传递的这一步骤,子类构造函数尚未执行,子类的实例对象也就还未初始化,因此所有的实例成员都不能被访问,但是类成员可以。
超类参数
为了不重复地将参数传递到超类构造的指定参数,你可以使用超类参数,直接在子类的构造中使用超类构造的某个参数。超类参数不能和重定向的参数一起使用。超类参数的表达式和写法与 终值初始化 类似:
class Vector2d {
final double x;
final double y;
Vector2d(this.x, this.y);
}
class Vector3d extends Vector2d {
final double z;
// Forward the x and y parameters to the default super constructor like:
// Vector3d(final double x, final double y, this.z) : super(x, y);
Vector3d(super.x, super.y, this.z);
}
如果超类构造的位置参数已被使用,那么超类构造参数就不能再继续使用被占用的位置。但是超类构造参数可以始终是命名参数:
class Vector2d {
// ...
Vector2d.named({required this.x, required this.y});
}
class Vector3d extends Vector2d {
// ...
// Forward the y parameter to the named super constructor like:
// Vector3d.yzPlane({required double y, required this.z})
// : super.named(x: 0, y: y);
Vector3d.yzPlane({required super.y, required this.z}) : super.named(x: 0);
}
初始化列表
除了调用父类构造函数之外,还可以在构造函数体执行之前初始化实例变量。每个实例变量之间使用逗号分隔。
// 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)');
}
使用初始化列表设置 final 字段非常方便,下面的示例中就使用初始化列表来设置了三个 final 变量的值
import 'dart:math';
class Point {
final double x;
final double y;
final double distanceFromOrigin;
Point(double x, double y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
void main() {
var p = Point(2, 3);
print(p.distanceFromOrigin);
}
重定向构造函数
有时候类中的构造函数仅用于调用类中其它的构造函数,此时该构造函数没有函数体,只需在函数签名后使用(:)指定需要重定向到的其它构造函数 (使用 this 而非类名):
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);
}
常量构造函数
如果类生成的对象都是不变的,可以在生成这些对象时就将其变为编译时常量。你可以在类的构造函数前加上 const 关键字并确保所有实例变量均为 final 来实现该功能。
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
工厂构造函数
使用 factory 关键字标识类的构造函数将会令该构造函数变为工厂构造函数,这将意味着使用该构造函数构造类的实例时并非总是会返回新的实例对象。例如,工厂构造函数可能会从缓存中返回一个实例,或者返回一个子类型的实例。
在如下的示例中, Logger 的工厂构造函数从缓存中返回对象,和 Logger.fromJson 工厂构造函数从 JSON 对象中初始化一个最终变量。
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);
}
}
工厂构造函数的调用方式与其他构造函数一样:
var logger = Logger('UI');
logger.log('Button clicked');
var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);
方法
方法是为对象提供行为的函数。
实例方法
对象的实例方法可以访问实例变量和 this。下面的 distanceTo() 方法就是一个实例方法的例子:
import 'dart:math';
calss Point{
final double x;
final double y;
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 允许您使用以下名称定义运算符:
运算符 | 运算符 | 运算符 | 运算符 |
---|---|---|---|
< | + | | | >>> |
/ | [] | ||
<= | ~/ | & | []= |
>= | * | << | ~ |
- | % | >> | == |
为了表示重写操作符,我们使用 operator 标识来进行标记。下面是重写 + 和 - 操作符的例子
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);
@override
bool operator ==(Object other) =>
other is Vector && x == other.x && y == other.y;
@override
int get hashCode => Object.hash(x, y);
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
Getter 和 Setter
Getter 和 Setter 是一对用来读写对象属性的特殊方法,上面说过实例对象的每一个属性都有一个隐式的 Getter 方法,如果为非 final 属性的话还会有一个 Setter 方法,你可以使用 get 和 set 关键字为额外的属性添加 Getter 和 Setter 方法:
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);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
使用 Getter 和 Setter 的好处是,你可以先使用你的实例变量,过一段时间过再将它们包裹成方法且不需要改动任何代码,即先定义后更改且不影响原有逻辑。
备注:
像自增(++)这样的操作符不管是否定义了 Getter 方法都会正确地执行。为了避免一些不必要的异常情况,运算符只会调用 Getter 一次,然后将其值存储在一个临时变量中。
抽象方法
实例方法、Getter 方法以及 Setter 方法都可以是抽象的,定义一个接口方法而不去做具体的实现让实现它的类去实现该方法,抽象方法只能存在于 抽象类中。
直接使用分号(;)替代方法体即可声明一个抽象方法:
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 标识类可以让该类成为 抽象类,抽象类将无法被实例化。抽象类常用于声明接口方法、有时也会有具体的方法实现。如果想让抽象类同时可被实例化,可以为其定义 工厂构造函数。
抽象类常常会包含 抽象方法。下面是一个声明具有抽象方法的抽象类示例:
abstract calss AbstractContainer {
void updateChildren();
}
隐式接口
每一个类都隐式地定义了一个接口并实现了该接口,这个接口包含所有这个类的实例成员以及这个类所实现的其它接口。如果想要创建一个 A 类支持调用 B 类的 API 且不想继承 B 类,则可以实现 B 类的接口。
一个类可以通过关键字 implements 来实现一个或多个接口并实现每个接口定义的 API:
class Person {
final string _name;
Person(this._name);
string greet(String who) => 'hellow .....'
}
如果需要实现多个类接口,可以使用逗号分隔每个接口类:
class point implements comparable,location {
}
扩展一个类(继承)
使用 extends 关键字来创建一个子类,并可使用 super 关键字引用一个父类:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
重写类成员
子类可以重写父类的实例方法(包括 操作符)、 Getter 以及 Setter 方法。你可以使用 @override 注解来表示你重写了一个成员:
class Television {
// ···
set contrast(int value) {...}
}
class SmartTelevision extends Television {
@override
set contrast(num value) {...}
// ···
}
noSuchMethod 方法 如果调用了对象上不存在的方法或实例变量将会触发 noSuchMethod 方法,你可以重写 noSuchMethod 方法来追踪和记录这一行为:
class A {
// Unless you override noSuchMethod, using a
// non-existent member results in a NoSuchMethodError.
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: '
'${invocation.memberName}');
}
}
只有下面其中一个条件成立时,你才能调用一个未实现的方法:
-
接收方是静态的 dynamic 类型。
-
接收方具有静态类型,定义了未实现的方法(抽象亦可),并且接收方的动态类型实现了 noSuchMethod 方法且具体的实现与 Object 中的不同。
扩展方法
扩展方法是向现有库添加功能的一种方式。你可能已经在不知道它是扩展方法的情况下使用了它。例如,当您在 IDE 中使用代码完成功能时,它建议将扩展方法与常规方法一起使用。
这里是一个在 String 中使用扩展方法的样例,我们取名为 parseInt(),它在 string_apis.dart 中定义:
import 'string_apis.dart'
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.
枚举类型
枚举类型是一种特殊的类型,也称为 enumerations 或 enums,用于定义一些固定数量的常量值。
备注:所有的枚举都继承于 Enum 类。枚举类是封闭的,即不能被继承、被实现、被 mixin 混入或显式被实例化。 抽象类和 mixin 可以显式的实现或继承 Enum,但只有枚举可以实现或混入这个类,其他类无法享有同样的操作。
声明简单的枚举 你可以使用关键字 enum 来定义简单的枚举类型和枚举值:
enum Color {
red,gren,blue
}
声明增强的枚举类型 Dart 中的枚举也支持定义字段、方法和常量构造,常量构造只能构造出已知数量的常量实例(已定义的枚举值)。
你可以使用与定义 类 类似的语句来定义增强的枚举,但是这样的定义有一些限制条件:
-
实例的字段必须是 final,包括由 mixin 混入的字段。
-
所有的 实例化构造 必须以 const 修饰。
-
工厂构造 只能返回已知的一个枚举实例。
-
由于 Enum 已经自动进行了继承,所以枚举类不能再继承其他类。
-
不能重载 index、hashCode 和比较操作符 ==。
-
不能声明 values 字段,否则它将与枚举本身的静态 values getter 冲突。
-
在进行枚举定义时,所有的实例都需要首先进行声明,且至少要声明一个枚举实例。
下方是一个增强枚举的例子,它包含多个枚举实例、成员变量、getter 并且实现了接口:
enum Vehicle implements Comparable<Vehicle> {
car(tires: 4, passengers: 5, carbonPerKilometer: 400),
bus(tires: 6, passengers: 50, carbonPerKilometer: 800),
bicycle(tires: 2, passengers: 1, carbonPerKilometer: 0);
const Vehicle({
required this.tires,
required this.passengers,
required this.carbonPerKilometer,
});
final int tires;
final int passengers;
final int carbonPerKilometer;
int get carbonFootprint => (carbonPerKilometer / passengers).round();
@override
int compareTo(Vehicle other) => carbonFootprint - other.carbonFootprint;
}
使用枚举 你可以像访问 静态变量 一样访问枚举值:
final favoriteColor = Color.blue;
if (favoriteColor == Color.blue) {
print('Your favorite color is blue!');
}
每一个枚举值都有一个名为 index 成员变量的 Getter 方法,该方法将会返回以 0 为基准索引的位置值。例如,第一个枚举值的索引是 0 ,第二个枚举值的索引是 1。以此类推。
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
想要获得全部的枚举值,使用枚举类的 values 方法获取包含它们的列表:
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
你可以在 Switch 语句中使用枚举,但是需要注意的是必须处理枚举值的每一种情况,即每一个枚举值都必须成为一个 case 子句,不然会出现警告:
var aColor = Color.blue;
switch(aColor){
case Color.red:
print('Red as roses!');
bread;
case Color.green:
print('Green as grass!');
break;
default: // Without this, you see a WARNING.
print(aColor); // 'Color.blue'
}
如果你想要获取一个枚举值的名称,例如 Color.blue 的 'blue',请使用 .name 属性:
print(Color.blue.name);
使用 Mixin 为类添加功能
Mixin 是一种在多重继承中复用某个类中代码的方法模式。
使用 with 关键字并在其后跟上 Mixin 类的名字来使用 Mixin 模式:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
想要实现一个 Mixin,请创建一个继承自 Object 且未声明构造函数的类。除非你想让该类与普通的类一样可以被正常地使用,否则请使用关键字 mixin 替代 class。例如:
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}
可以使用关键字 on 来指定哪些类可以使用该 Mixin 类,比如有 Mixin 类 A,但是 A 只能被 B 类使用,则可以这样定义 A:
class Musician {
// ...
}
mixin MusicalPerformer on Musician {
// ...
}
class SingerDancer extends Musician with MusicalPerformer {
// ...
}
类变量和方法
使用关键字 static 可以声明类变量或类方法。
静态变量
静态变量(即类变量)常用于声明类范围内所属的状态变量和常量:
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
静态变量在其首次被使用的时候才被初始化。
静态方法
静态方法(即类方法)不能对实例进行操作,因此不能使用 this。但是他们可以访问静态变量。如下面的例子所示,你可以在一个类上直接调用静态方法:
import 'dart:math';
class Point {
double x, y;
Point(this.x, this.y);
static double distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}
void main() {
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);
}
可以将静态方法作为编译时常量。例如,你可以将静态方法作为一个参数传递给一个常量构造函数。
泛型
如果你查看数组的 API 文档,你会发现数组 List 的实际类型为 List。 <…> 符号表示数组是一个 泛型(或 参数化类型) 通常 使用一个字母来代表类型参数,比如 E、T、S、K 和 V 等等。
为什么使用泛型?
泛型常用于需要要求类型安全的情况,但是它也会对代码运行有好处:
适当地指定泛型可以更好地帮助代码生成。
使用泛型可以减少代码重复。
比如你想声明一个只能包含 String 类型的数组,你可以将该数组声明为 List(读作“字符串类型的 list”),这样的话就可以很容易避免因为在该数组放入非 String 类变量而导致的诸多问题,同时编译器以及其他阅读代码的人都可以很容易地发现并定位问题:
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error
另一个使用泛型的原因是可以减少重复代码。泛型可以让你在多个不同类型实现之间共享同一个接口声明,比如下面的例子中声明了一个类用于缓存对象的接口:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}不久后你可能又会想专门为 String 类对象做一个缓存,于是又有了专门为 String 做缓存的类:
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
如果过段时间你又想为数字类型也创建一个类,那么就会有很多诸如此类的代码……
这时候可以考虑使用泛型来声明一个类,让不同类型的缓存实现该类做出不同的具体实现即可:
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
在上述代码中,T 是一个替代类型。其相当于类型占位符,在开发者调用该接口的时候会指定具体类型。
不久后你可能又会想专门为 String 类对象做一个缓存,于是又有了专门为 String 做缓存的类:
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
如果过段时间你又想为数字类型也创建一个类,那么就会有很多诸如此类的代码……
这时候可以考虑使用泛型来声明一个类,让不同类型的缓存实现该类做出不同的具体实现即可:
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
在上述代码中,T 是一个替代类型。其相当于类型占位符,在开发者调用该接口的时候会指定具体类型。
使用集合字面量
List、Set 以及 Map 字面量也可以是参数化的。定义参数化的 List 只需在中括号前添加 ;定义参数化的 Map 只需要在大括号前添加 <keyType, valueType>:
var names = <string>['seth','wxq'];
var uniqueNames = <string>{'seth','kathy','lars'}
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
使用类型参数化的构造函数
在调用构造方法时也可以使用泛型,只需在类名后用尖括号(<...>)将一个或多个类型包裹即可:
var nameSet = Set<String>.from(names);
下面代码创建了一个键为 Int 类型,值为 View 类型的 Map 对象:
var views = Map<int, View>();
泛型集合以及它们所包含的类型
Dart的泛型类型是 固化的,这意味着即便在运行时也会保持类型信息:
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
> 备注:
与 Java 不同的是,Java 中的泛型是类型 擦除 的,这意味着泛型类型会在运行时被移除。在 Java 中你可以判断对象是否为 List 但不可以判断对象是否为 List<String>。
限制参数化类型 有时使用泛型的时候,你可能会想限制可作为参数的泛型范围,也就是参数必须是指定类型的子类,这时候可以使用 extends 关键字。
一种常见的非空类型处理方式,是将子类限制继承 Object (而不是默认的 Object?)。
class Foo<T extends Object> {
// Any type provided to Foo for T must be non-nullable.
}
You can use extends with other types besides Object. Here’s an example of extending SomeBaseClass, so that members of SomeBaseClass can be called on objects of type T:
class Foo<T extends SomeBaseClass> {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
这时候就可以使用 SomeBaseClass 或者它的子类来作为泛型参数:
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
这时候也可以指定无参数的泛型,这时无参数泛型的类型则为Foo:
var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'
将非 SomeBaseClass 的类型作为泛型参数则会导致编译错误:
var foo = Foo<Object>();
使用泛型方法
方法和参数也可以使用类型参数了:
T first<T>(List<T> ts){
// Do some initial work or error checking, then...
T tmp = ts[0];
// Do some additional checking or processing...
return tmp;
}
方法 first 的泛型 T 可以在如下地方使用:
-
函数的返回值类型 (T)。
-
参数的类型 (List)。
-
局部变量的类型 (T tmp)。
库和可见性
import 和 library 关键字可以帮助你创建一个模块化和可共享的代码库。代码库不仅只是提供 API 而且还起到了封装的作用:以下划线(_)开头的成员仅在代码库中可见。 每个 Dart 程序都是一个库,即便没有使用关键字 library 指定。
Dart 的库可以使用 包工具 来发布和部署。
使用库
使用 import 来指定命名空间以便其它库可以访问。
比如你可以导入代码库 dart:html 来使用 Dart Web 中相关 API:
import 'dart:html';
import 的唯一参数是用于指定代码库的 URI,对于 Dart 内置的库,使用 dart:xxxxxx 的形式。而对于其它的库,你可以使用一个文件系统路径或者以 package:xxxxxx 的形式。 package:xxxxxx 指定的库通过包管理器(比如 pub 工具)来提供:
import 'package:test/test.dart';
URI 代表统一资源标识符。
URL(统一资源定位符)是一种常见的 URI。
指定库前缀 如果你导入的两个代码库有冲突的标识符,你可以为其中一个指定前缀。比如如果 library1 和 library2 都有 Element 类,那么可以这么处理:
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// Uses Element from lib1.
Element element1 = Element();
// Uses Element from lib2.
lib2.Element element2 = lib2.Element();
导入库的一部分 如果你只想使用代码库中的一部分,你可以有选择地导入代码库。例如:
// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
延迟加载库 延迟加载(也常称为 懒加载)允许应用在需要时再去加载代码库,下面是可能使用到延迟加载的场景:
-
为了减少应用的初始化时间。
-
处理 A/B 测试,比如测试各种算法的不同实现。
-
加载很少会使用到的功能,比如可选的屏幕和对话框。
使用 deferred as 关键字来标识需要延时加载的代码库:
import 'package:greetings/hello.dart' deferred as hello;
当实际需要使用到库中 API 时先调用 loadLibrary 函数加载库:
Future<void> greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
在前面的代码,使用 await 关键字暂停代码执行直到库加载完成。更多关于 async 和 await 的信息请参考异步支持。
loadLibrary 函数可以调用多次也没关系,代码库只会被加载一次。
当你使用延迟加载的时候需要牢记以下几点:
-
延迟加载的代码库中的常量需要在代码库被加载的时候才会导入,未加载时是不会导入的。
-
导入文件的时候无法使用延迟加载库中的类型。如果你需要使用类型,则考虑把接口类型转移到另一个库中然后让两个库都分别导入这个接口库。
-
Dart会隐式地将 loadLibrary() 导入到使用了 deferred as 命名空间 的类中。 loadLibrary() 函数返回的是一个 Future。
The library directive To specify library-level doc comments or metadata annotations, attach them to a library declaration at the start of the file.
/// A really great test library.
@TestOn('browser')
library;
实现库
查阅 创建依赖库包 可以获取有关如何实现库包的建议,包括:
-
如何组织库的源文件。
-
如何使用 export 命令。
-
何时使用 part 命令。
-
如何使用导入和导出命令实现多平台的库支持。
异步支持
Dart 代码库中有大量返回 Future 或 Stream 对象的函数,这些函数都是 异步 的,它们会在耗时操作(比如I/O)执行完毕前直接返回而不会等待耗时操作执行完毕。
async 和 await 关键字用于实现异步编程,并且让你的代码看起来就像是同步的一样。
处理 Future
可以通过下面两种方式,获得 Future 执行完成的结果:
-
使用 async 和 await,在 异步编程 codelab 中有更多描述;
-
使用 Future API,具体描述参考 库概览。
使用 async 和 await 的代码是异步的,但是看起来有点像同步代码。例如,下面的代码使用 await 等待异步函数的执行结果。
await lookUpVersion();
必须在带有 async 关键字的 异步函数 中使用 await:
Future<void> checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
尽管 async 函数可能会执行一些耗时操作,但是它并不会等待这些耗时操作完成,相反,异步函数执行时会在其遇到第一个 await 表达式时返回一个 Future 对象,然后等待 await 表达式执行完毕后继续执行。
使用 try、catch 以及 finally 来处理使用 await 导致的异常:
try {
version = await lookUpVersion();
} catch (e) {
// React to inability to look up the version
}
你可以在异步函数中多次使用 await 关键字。例如,下面代码中等待了三次函数结果:
var entrypoint = await findEntryPoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
await 表达式的返回值通常是一个 Future 对象;如果不是的话也会自动将其包裹在一个 Future 对象里。 Future 对象代表一个“承诺”, await 表达式会阻塞直到需要的对象返回。
如果在使用 await 时导致编译错误,请确保 await 在一个异步函数中使用。例如,如果想在 main() 函数中使用 await,那么 main() 函数就必须使用 async 关键字标识。
void main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
如上的例子使用了声明为 async 的函数 checkVersion(),但没有等待其结果。在实际的开发中,如果代码假设函数已经执行完成,则可能导致一些异步的问题。想要避免这些问题,请使用 unawaited_futures 提示规则。
声明异步函数
异步函数 是函数体由 async 关键字标记的函数。
将关键字 async 添加到函数并让其返回一个 Future 对象。假设有如下返回 String 对象的方法:
String lookUpVersion() => '1.0.0';
将其改为异步函数,返回值是 Future:
Future<String> lookUpVersion() async => '1.0.0';
注意,函数体不需要使用 Future API。如有必要,Dart 会创建 Future 对象。
如果函数没有返回有效值,需要设置其返回类型为 Future。
处理 Stream
如果想从 Stream 中获取值,可以有两种选择:
-
使用 async 关键字和一个 异步循环(使用 await for 关键字标识)。
-
使用 Stream API。详情参考 库概览。
使用 await for 定义异步循环看起来是这样的:
await for (varOrType identifier in expression) {
// Executes each time the stream emits a value.
}
表达式 的类型必须是 Stream。执行流程如下:
-
等待直到 Stream 返回一个数据。
-
使用 1 中 Stream 返回的数据执行循环体。
-
重复 1、2 过程直到 Stream 数据返回完毕。
使用 break 和 return 语句可以停止接收 Stream 数据,这样就跳出了循环并取消注册监听 Stream。
**如果在实现异步 for 循环时遇到编译时错误,请检查确保 await for 处于异步函数中。 ** 例如,要在应用程序的 main() 函数中使用异步 for 循环,main() 函数体必须标记为 async:
void main() async {
// ...
await for (final request in requestServer) {
handleRequest(request);
}
// ...
}