本文转自https://www.jianshu.com/p/9e5f4c81cc7d
Dart所有的东西都是对象, 即使是数字numbers、函数function、null也都是对象,所有的对象都继承自Object类。
Dart动态类型语言, 尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下会类型会是 dynamic(动态的)。
Dart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度。
Dart中的类和接口是统一的,类即接口,你可以继承一个类,也可以实现一个类(接口),自然也包含了良好的面向对象和并发编程的支持。
Dart 提供了顶级函数(如:main())。
Dart 没有 public、private、protected 这些关键字,变量名以"_"开头意味着对它的 lib 是私有的。
没有初始化的变量都会被赋予默认值 null。
final的值只能被设定一次。const 是一个编译时的常量,可以通过 const 来创建常量值,var c=const[];,这里 c 还是一个变量,只是被赋值了一个常量值,它还是可以赋其它值。实例变量可以是 final,但不能是 const。
编程语言并不是孤立存在的,Dart也是这样,他由语言规范、虚拟机、类库和工具等组成:
关键字 | - | - | - |
---|---|---|---|
abstract | do | import | super |
as | dynamic | in | switch |
assert | else | interface | sync* |
enum | implements | is | this |
async* | export | library | throw |
await | external | mixin | true |
break | extends | new | try |
case | factory | null | typedef |
catch | false | operator | var |
class | final | part | void |
const | finally | rethrow | while |
continue | for | return | with |
covariant | get | set | yield* |
default | if | static | deferred |
// 没有明确类型,编译的时候根据值明确类型
var name = ‘Bob’;
Object name = '张三';
dynamic name = '李四';
// 显示声明将被推断类型, 可以使用String显示声明字符串类型
String name = 'Bob' ;
//测试 数字类型的初始值是什么?
int lineCount;
// 为false的时候抛出异常
assert(lineCount == null);
print(lineCount); //打印结果为null,证明数字类型初始化值是null
final and const
如果您从未打算更改一个变量,那么使用 final 或 const,不是var,也不是一个类型。
一个 final 变量只能被初始化一次; const变量是一个编译时常量,(Const变量是隐式的final)
final的顶级或类变量在第一次使用时被初始化。
被final修饰的顶级变量或类变量在第一次声明的时候就需要初始化。
// The final variable 'outSideFinalName' must be initialized.
final String outSideFinalName
//可以省略String这个类型声明
final name = "Bob";
final String name1 = "张三";
const name2 = "alex";
const String name3 = "李四";
final String outSideFinalName = "Alex";
// outSideFinalName', a final variable, can only be set once
// 一个final变量,只能被设置一次。
outSideFinalName = "Bill";
const String outSideName = 'Bill';
// 这样写,编译器提示:Constant variables can't be assigned a value
// const常量不能赋值
// outSideName = "小白";
// Members can't be declared to be both 'const' and 'var'
const var String outSideName = 'Bill';
// Members can't be declared to be both 'final' and 'var'
final var String name = 'Lili';
// 常量如果是类级别的,请使用 static const
static const String name3 = 'Tom';
// 这样写保存
// Only static fields can be declared as const
// 只有静态字段可以声明为const
//const String name3 = 'Tom';
const speed = 100; //速度(km/h)
const double distance = 2.5 * speed; // 距离 = 时间 * 速度
final speed2 = 100; //速度(km/h)
final double distance2 = 2.5 * speed2; // 距离 = 时间 * 速度
// 注意: [] 创建的是一个空的list集合
// const []创建一个空的、不可变的列表(EIL)。
var varList = const []; // varList 当前是一个EIL
final finalList = const []; // finalList一直是EIL
const constList = const []; // constList 是一个编译时常量的EIL
// 可以更改非final,非const变量的值
// 即使它曾经具有const值
varList = ["haha"];
// 不能更改final变量或const变量的值
// 这样写,编译器提示:a final variable, can only be set once
// finalList = ["haha"];
// 这样写,编译器提示:Constant variables can't be assigned a value
// constList = ["haha"];
const String outSideName = 'Bill';
final String outSideFinalName = 'Alex';
const String outSideName2 = 'Tom';
const aConstList = const ['1', '2', '3'];
// In constant expressions, operands of this operator must be of type 'bool', 'num', 'String' or 'null'
// 在常量表达式中,该运算符的操作数必须为'bool'、'num'、'String'或'null'。
const validConstString = '$outSideName $outSideName2 $aConstList';
// Const variables must be initialized with a constant value
// const常量必须用conat类型的值初始化
const validConstString = '$outSideName $outSideName2 $outSideFinalName';
var outSideVarName='Cathy';
// Const variables must be initialized with a constant value.
// const常量必须用conat类型的值初始化
const validConstString = '$outSideName $outSideName2 $outSideVarName';
// 正确写法
const String outSideConstName = 'Joy';
const validConstString = '$outSideName $outSideName2 $outSideConstName';
num
num 是数字类型的父类,有两个子类 int 和 double。
int 根据平台的不同,整数值不大于64位。在Dart VM上,值可以从-263到263 - 1,编译成JavaScript的Dart使用JavaScript代码,允许值从-253到253 - 1。
double 64位(双精度)浮点数,如IEEE 754标准所规定。
int a = 1;
print(a);
double b = 1.12;
print(b);
// String -> int
int one = int.parse('1');
// 输出3
print(one + 2);
// String -> double
var onePointOne = double.parse('1.1');
// 输出3.1
print(onePointOne + 2);
// int -> String
String oneAsString = 1.toString();
// The argument type 'int' can't be assigned to the parameter type 'String'
//print(oneAsString + 2);
// 输出 1 + 2
print('$oneAsString + 2');
// 输出 1 2
print('$oneAsString 2');
// double -> String 注意括号中要有小数点位数,否则报错
String piAsString = 3.14159.toStringAsFixed(2);
// 截取两位小数, 输出3.14
print(piAsString);
String aString = 1.12618.toStringAsFixed(2);
// 检查是否四舍五入,输出1.13,发现会做四舍五入
print(aString);
String
String singleString = 'abcdddd';
String doubleString = "abcsdfafd";
String sdString = '$singleString a "bcsd" ${singleString}';
String dsString = "abc 'aaa' $sdString";
print(sdString);
print(dsString);
String singleString = 'aaa';
String doubleString = "bbb";
// 单引号嵌套双引号
String sdString = '$singleString a "bbb" ${doubleString}';
// 输出 aaa a "bbb" bbb
print(sdString);
// 双引号嵌套单引号
String dsString = "${singleString.toUpperCase()} abc 'aaa' $doubleString.toUpperCase()";
// 输出 AAA abc 'aaa' bbb.toUpperCase(),
可以看出 ”$doubleString.toUpperCase()“ 没有加“{}“,导致输出结果是”bbb.toUpperCase()“
print(dsString);
bool
// 检查是否为空字符串
var fullName = '';
assert(fullName.isEmpty);
// 检查0
var hitPoints = 0;
assert(hitPoints <= 0);
// 检查是否为null
var unicorn;
assert(unicorn == null);
// 检查是否为NaN
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);
//创建一个int类型的list
List list = [10, 7, 23];
// 输出[10, 7, 23]
print(list);
// 使用List的构造函数,也可以添加int参数,表示List固定长度,不能进行添加 删除操作
var fruits = new List();
// 添加元素
fruits.add('apples');
// 添加多个元素
fruits.addAll(['oranges', 'bananas']);
List subFruits = ['apples', 'oranges', 'banans'];
// 添加多个元素
fruits.addAll(subFruits);
// 输出: [apples, oranges, bananas, apples, oranges, banans]
print(fruits);
// 获取List的长度
print(fruits.length);
// 获取第一个元素
print(fruits.first);
// 获取元素最后一个元素
print(fruits.last);
// 利用索引获取元素
print(fruits[0]);
// 查找某个元素的索引号
print(fruits.indexOf('apples'));
// 删除指定位置的元素,返回删除的元素
print(fruits.removeAt(0));
// 删除指定元素,成功返回true,失败返回false
// 如果集合里面有多个“apples”, 只会删除集合中第一个改元素
fruits.remove('apples');
// 删除最后一个元素,返回删除的元素
fruits.removeLast();
// 删除指定范围(索引)元素,含头不含尾
fruits.removeRange(start,end);
// 删除指定条件的元素(这里是元素长度大于6)
fruits.removeWhere((item) => item.length >6);
// 删除所有的元素
fruits.clear();
注意事项:
可以直接打印list包括list的元素,list也是一个对象。但是java必须遍历才能打印list,直接打印是地址值。
和java一样list里面的元素必须保持类型一致,不一致就会报错。
和java一样list的角标从0开始。
如果集合里面有多个相同的元素“X”, 只会删除集合中第一个改元素
一般来说,map是将键和值相关联的对象。键和值都可以是任何类型的对象。
每个键只出现一次,但您可以多次使用相同的值。Dart支持map由map文字和map类型提供。
初始化Map方式一: 直接声明,用{}表示,里面写key和value,每组键值对中间用逗号隔开。
// Two keys in a map literal can't be equal.
// Map companys = {'Alibaba': '阿里巴巴', 'Tencent': '腾讯', 'baidu': '百度', 'Alibaba': '钉钉', 'Tenect': 'qq-music'};
Map companys = {'Alibaba': '阿里巴巴', 'Tencent': '腾讯', 'baidu': '百度'};
// 输出:{Alibaba: 阿里巴巴, Tencent: 腾讯, baidu: 百度}
print(companys);
Map schoolsMap = new Map();
schoolsMap['first'] = '清华';
schoolsMap['second'] = '北大';
schoolsMap['third'] = '复旦';
// 打印结果 {first: 清华, second: 北大, third: 复旦}
print(schoolsMap);
var fruits = new Map();
fruits["first"] = "apple";
fruits["second"] = "banana";
fruits["fifth"] = "orange";
//换成双引号,换成var 打印结果 {first: apple, second: banana, fifth: orange}
print(fruits);
// 指定键值对的参数类型
var aMap = new Map<int, String>();
// Map的赋值,中括号中是Key,这里可不是数组
aMap[1] = '小米';
//Map中的键值对是唯一的
//同Set不同,第二次输入的Key如果存在,Value会覆盖之前的数据
aMap[1] = 'alibaba';
// map里面的value可以相同
aMap[2] = 'alibaba';
// map里面value可以为空字符串
aMap[3] = '';
// map里面的value可以为null
aMap[4] = null;
print(aMap);
// 检索Map是否含有某Key
assert(aMap.containsKey(1));
//删除某个键值对
aMap.remove(1);
print(aMap);
注意事项
map的key类型不一致也不会报错。
添加元素的时候,会按照你添加元素的顺序逐个加入到map里面,哪怕你的key,比如分别是 1,2,4,看起来有间隔,事实上添加到map的时候是{1:value,2:value,4:value} 这种形式。
map里面的key不能相同。但是value可以相同,value可以为空字符串或者为null。
描述 | 操作符 | ||
---|---|---|---|
一元后置操作符 | expr++ expr-- () [] . ?. | ||
一元前置操作符 | expr !expr ~expr ++expr --expr | ||
乘除 | * / % ~/ | ||
加减 | + - | ||
位移 | << >> | ||
按位与 | & | ||
按位或 | |||
按位异或 | ^ | ||
逻辑与 | && | ||
逻辑或 | |||
关系和类型判断 | >= > <= < as is is! | ||
等 | == != | ||
如果为空 | ?? | ||
条件表达式 | expr1 ? expr2 : expr3 | ||
赋值 | = *= /= ~/= %= += -= <<= >>= &= ^= = ??= | ||
级联 | .. |
throw new FormatException('Expected at least 1 section');
抛出任意类型的异常
throw 'Out of llamas!';
因为抛出异常属于表达式,可以将throw语句放在=>语句中,或者其它可以出现表达式的地方
distanceTo(Point other) =>
throw new UnimplementedError();
try {
breedMoreLlamas();
} on OutOfLlamasException {
// A specific exception
buyMoreLlamas();
} on Exception catch (e) {
// Anything else that is an exception
print('Unknown exception: $e');
} catch (e, s) {
print('Exception details:\n $e');
print('Stack trace:\n $s');
}
final foo = '';
void misbehave() {
try {
foo = "1";
} catch (e) {
print('2');
rethrow;// 如果不重新抛出异常,main函数中的catch语句执行不到
}
}
void main() {
try {
misbehave();
} catch (e) {
print('3');
}
}
finally
final foo = '';
void misbehave() {
try {
foo = "1";
} catch (e) {
print('2');
}
}
void main() {
try {
misbehave();
} catch (e) {
print('3');
} finally {
print('4'); // 即使没有rethrow最终都会执行到
}
}
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
main()函数
void main() {
querySelector('#sample_text_id')
..text = 'Click me!'
..onClick.listen(reverseText);
}
querySelector('#confirm') // Get an object.
..text = 'Confirm' // Use its members.
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
final addressBook = (AddressBookBuilder()
..name = 'jenny'
..email = 'jenny@example.com'
..phone = (PhoneNumberBuilder()
..number = '415-555-0100'
..label = 'home')
.build())
.build();
var sb = StringBuffer();
sb.write('foo') // 返回void
..write('bar'); // 这里会报错
可选参数
//设置[bold]和[hidden]标志
void enableFlags({bool bold, bool hidden}) {
// ...
}
enableFlags(bold: true, hidden: false);
String say(String from, String msg, [String device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
say('Bob', 'Howdy'); //结果是: Bob says Howdy
say('Bob', 'Howdy', 'smoke signal'); //结果是:Bob says Howdy with a smoke signal
默认参数
函数可以使用=为命名参数和位置参数定义默认值。默认值必须是编译时常量。如果没有提供默认值,则默认值为null。
下面是为命名参数设置默认值的示例:
// 设置 bold 和 hidden 标记的默认值都为false
void enableFlags2({bool bold = false, bool hidden = false}) {
// ...
}
// 调用的时候:bold will be true; hidden will be false.
enableFlags2(bold: true);
String say(String from, String msg,
[String device = 'carrier pigeon', String mood]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
if (mood != null) {
result = '$result (in a $mood mood)';
}
return result;
}
//调用方式:
say('Bob', 'Howdy'); //结果为:Bob says Howdy with a carrier pigeon;
// 使用list 或者map设置默认值
void doStuff(
{List<int> list = const [1, 2, 3],
Map<String, String> gifts = const {'first': 'paper',
'second': 'cotton', 'third': 'leather'
}}) {
print('list: $list');
print('gifts: $gifts');
}
作为一个类对象的功能
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// 把 printElement函数作为一个参数传递进来
list.forEach(printElement);
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');
匿名函数
大多数函数都能被命名为匿名函数,如 main() 或 printElement()。您还可以创建一个名为匿名函数的无名函数,有时也可以创建lambda或闭包。您可以为变量分配一个匿名函数,例如,您可以从集合中添加或删除它。
一个匿名函数看起来类似于一个命名函数 - 0或更多的参数,在括号之间用逗号和可选类型标注分隔。
下面的代码块包含函数的主体:
([[Type] param1[, …]]) {
codeBlock;
};
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
list.forEach((item) => print('${list.indexOf(item)}: $item'));
返回值
foo() {}
assert(foo() == null);
var jsonData = JSON.decode('{"x":1, "y":2}');
// Create a Point using Point().
var p1 = new Point(2, 2);
// Create a Point using Point.fromJson().
var p2 = new Point.fromJson(jsonData);
var p = new Point(2, 2);
// Set the value of the instance variable y.
p.y = 3;
// Get the value of y.
assert(p.y == 3);
// Invoke distanceTo() on p.
num distance = p.distanceTo(new Point(4, 4));
?.
来确认前操作数不为空, 常用来替代.
, 避免左边操作数为null引发异常。```
// If p is non-null, set its y value to 4.
p?.y = 4;
```
```
var p = const ImmutablePoint(2, 2);
```
```
print('The type of a is ${a.runtimeType}');
```
class Point {
num x; // Declare instance variable x, initially null.
num y; // Declare y, initially null.
num z = 0; // Declare z, initially 0.
}
class Point {
num x;
num y;
}
main() {
var point = new 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 {
num x;
num y;
Point(num x, num y) {
// There's a better way to do this, stay tuned.
this.x = x;
this.y = y;
}
}
class Point {
num x;
num y;
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}
class Point {
num x;
num y;
Point(this.x, this.y);
// 命名构造函数Named constructor
Point.fromJson(Map json) {
x = json['x'];
y = json['y'];
}
}
默认情况下,子类只能调用父类的无名,无参数的构造函数; 父类的无名构造函数会在子类的构造函数前调用; 如果initializer list 也同时定义了,则会先执行initializer list 中的内容,然后在执行父类的无名无参数构造函数,最后调用子类自己的无名无参数构造函数。即下面的顺序:
如果父类不显示提供无名无参数构造函数的构造函数,在子类中必须手打调用父类的一个构造函数。这种情况下,调用父类的构造函数的代码放在子类构造函数名后,子类构造函数体前,中间使用:(colon)
分割。
class Person {
String firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
// 父类没有无参数的非命名构造函数,必须手动调用一个构造函数
super.fromJson(data)
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
main() {
var emp = new Employee.fromJson({});
// Prints:
// in Person
// in Employee
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}
(emp as Person).firstName = 'Bob';
}
初始化列表
class Point {
num x;
num y;
Point(this.x, this.y);
// 初始化列表在构造函数运行前设置实例变量。
Point.fromJson(Map jsonMap)
: x = jsonMap['x'],
y = jsonMap['y'] {
print('In Point.fromJson(): ($x, $y)');
}
}
注意:上述代码,初始化程序无法访问 this 关键字。
静态构造函数
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin = const ImmutablePoint(0, 0);
}
重定向构造函数
class Point {
num x;
num y;
// 主构造函数
Point(this.x, this.y) {
print("Point($x, $y)");
}
// 重定向构造函数,指向主构造函数,函数体为空
Point.alongXAxis(num x) : this(x, 0);
}
void main() {
var p1 = new Point(1, 2);
var p2 = new Point.alongXAxis(4);
}
常量构造函数
如果类的对象不会发生变化,可以构造一个编译时的常量构造函数。定义格式如下:
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin = const ImmutablePoint(0, 0);
}
class Logger {
final String name;
bool mute = false;
// _cache 是一个私有库,幸好名字前有个 _ 。
static final Map<String, Logger> _cache = <String, Logger>{};
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = new Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) {
print(msg);
}
}
}
注意:工厂构造函数不能用 this。
实例方法
import 'dart:math';
class Point {
num x;
num y;
Point(this.x, this.y);
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
setters 和 Getters
class Rectangle {
num left;
num top;
num width;
num height;
Rectangle(this.left, this.top, this.width, this.height);
// 定义两个计算属性: right and bottom.
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}
main() {
var rect = new Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
借助于 getter 和 setter ,你可以直接使用实例变量,并且在不改变客户代码的情况下把他们包装成方法。
注: 不论是否显式地定义了一个 getter,类似增量(++)的操作符,都能以预期的方式工作。为了避免产生任何向着不期望的方向的影响,操作符一旦调用 getter ,就会把他的值存在临时变量里。
抽象方法
abstract class Doer {
// ...定义实例变量和方法...
void doSomething(); // 定义一个抽象方法。
}
class EffectiveDoer extends Doer {
void doSomething() {
// ...提供一个实现,所以这里的方法不是抽象的...
}
}
枚举类型
枚举类型,通常被称为 enumerations 或 enums ,是一种用来代表一个固定数量的常量的特殊类。
声明一个枚举类型需要使用关键字 enum :
enum Color {
red,
green,
blue
}
在枚举中每个值都有一个 index getter 方法,它返回一个在枚举声明中从 0 开始的位置。例如,第一个值索引值为 0 ,第二个值索引值为 1 。
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
```
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
```
* 你可以在 switch 语句 中使用枚举。如果 e 在 switch (e) 是显式类型的枚举,那么如果你不处理所有的枚举值将会弹出警告:
```
enum Color {
red,
green,
blue
}
// ...
Color aColor = Color.blue;
switch (aColor) {
case Color.red:
print('Red as roses!');
break;
case Color.green:
print('Green as grass!');
break;
default: // Without this, you see a WARNING.
print(aColor); // 'Color.blue'
}
```
***枚举类型有以下限制***
* 你不能在子类中混合或实现一个枚举。
* 你不能显式实例化一个枚举。
为类添加特征:mixins
mixins 是一种多类层次结构的类的代码重用。
要使用 mixins ,在 with 关键字后面跟一个或多个 mixin 的名字。下面的例子显示了两个使用mixins的类:
class Musician extends Performer with Musical {
// ...
}
class Maestro extends Person with Musical,
Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
要实现 mixin ,就创建一个继承 Object 类的子类,不声明任何构造函数,不调用 super 。例如:
abstract class 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');
}
}
}
类的变量和方法
使用 static 关键字来实现类变量和类方法。
只有当静态变量被使用时才被初始化。
静态变量, 静态变量(类变量)对于类状态和常数是有用的:
class Color {
static const red = const Color('red'); // 一个恒定的静态变量
final String name; // 一个实例变量。
const Color(this.name); // 一个恒定的构造函数。
}
main() {
assert(Color.red.name == 'red');
}
静态方法, 静态方法(类方法)不在一个实例上进行操作,因而不必访问 this 。例如:
import 'dart:math';
class Point {
num x;
num y;
Point(this.x, this.y);
static num distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}
main() {
var a = new Point(2, 2);
var b = new Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(distance < 2.9 && distance > 2.8);
}
注:考虑到使用高阶层的方法而不是静态方法,是为了常用或者广泛使用的工具和功能。
你可以将静态方法作为编译时常量。例如,你可以把静态方法作为一个参数传递给静态构造函数。
使用 abstract 修饰符来定义一个抽象类,该类不能被实例化。抽象类在定义接口的时候非常有用,实际上抽象中也包含一些实现。如果你想让你的抽象类被实例化,请定义一个 工厂构造函数 。
抽象类通常包含 抽象方法。下面是声明一个含有抽象方法的抽象类的例子:
// 这个类是抽象类,因此不能被实例化。
abstract class AbstractContainer {
// ...定义构造函数,域,方法...
void updateChildren(); // 抽象方法。
}
下面的类不是抽象类,因此它可以被实例化,即使定义了一个抽象方法:
class SpecializedContainer extends AbstractContainer {
// ...定义更多构造函数,域,方法...
void updateChildren() {
// ...实现 updateChildren()...
}
// 抽象方法造成一个警告,但是不会阻止实例化。
void doSomething();
}
每个类隐式的定义了一个接口,含有类的所有实例和它实现的所有接口。如果你想创建一个支持类 B 的 API 的类 A,但又不想继承类 B ,那么,类 A 应该实现类 B 的接口。
一个类实现一个或更多接口通过用 implements 子句声明,然后提供 API 接口要求。例如:
// 一个 person ,包含 greet() 的隐式接口。
class Person {
// 在这个接口中,只有库中可见。
final _name;
// 不在接口中,因为这是个构造函数。
Person(this._name);
// 在这个接口中。
String greet(who) => 'Hello, $who. I am $_name.';
}
// Person 接口的一个实现。
class Imposter implements Person {
// 我们不得不定义它,但不用它。
final _name = "";
String greet(who) => 'Hi $who. Do you know who I am?';
}
greetBob(Person person) => person.greet('bob');
main() {
print(greetBob(new Person('kathy')));
print(greetBob(new Imposter()));
}
这里是具体说明一个类实现多个接口的例子:
class Point implements Comparable, Location {
// ...
}
使用 extends 创建一个子类,同时 supper 将指向父类:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ...
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ...
}
子类可以重载实例方法, getters 方法, setters 方法。下面是个关于重写 Object 类的方法 noSuchMethod() 的例子,当代码企图用不存在的方法或实例变量时,这个方法会被调用。
class A {
// 如果你不重写 noSuchMethod 方法, 就用一个不存在的成员,会导致NoSuchMethodError 错误。
void noSuchMethod(Invocation mirror) {
print('You tried to use a non-existent member:' +
'${mirror.memberName}');
}
}
你可以使用 @override 注释来表明你重写了一个成员。
class A {
@override
void noSuchMethod(Invocation mirror) {
// ...
}
}
@proxy
class A {
void noSuchMethod(Invocation mirror) {
// ...
}
}
import,part,library指令可以帮助创建一个模块化的,可共享的代码库。库不仅提供了API,还提供隐私单元:以下划线(_)开头的标识符只对内部库可见。每个Dartapp就是一个库,即使它不使用库指令。
库可以分布式使用包。见 Pub Package and Asset Manager 中有关pub(SDK中的一个包管理器)。
使用库
使用 import 来指定如何从一个库命名空间用于其他库的范围。
例如,Dart Web应用一般采用这个库 dart:html,可以这样导入:
import 'dart:html';
import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';
```
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// ...
var element1 = new Element(); // 使用lib1里的元素
var element2 =
new lib2.Element(); // 使用lib2里的元素
```
导入部分库
// 只导入foo库
import 'package:lib1/lib1.dart' show foo;
//导入所有除了foo
import 'package:lib2/lib2.dart' hide foo;
延迟加载库
延迟(deferred)加载(也称为延迟(lazy)加载)允许应用程序按需加载库。下面是当你可能会使用延迟加载某些情况:
为了延迟加载一个库,你必须使用 deferred as 先导入它。
import 'package:deferred/hello.dart' deferred as hello;
当需要库时,使用该库的调用标识符调用 LoadLibrary()。
greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
在前面的代码,在库加载好之前,await关键字都是暂停执行的。有关 async 和 await 见 asynchrony support 的更多信息。
您可以在一个库调用 LoadLibrary() 多次都没有问题。该库也只被加载一次。
当您使用延迟加载,请记住以下内容:
库的实现
声明库
利用library identifier(库标识符)指定当前库的名称:
// 声明库,名ballgame
library ballgame;
// 导入html库
import 'dart:html';
// ...代码从这里开始...
关联文件与库
添加实现文件,把part fileUri放在有库的文件,其中fileURI是实现文件的路径。然后在实现文件中,添加部分标识符(part of identifier),其中标识符是库的名称。下面的示例使用的一部分,在三个文件来实现部分库。
第一个文件,ballgame.dart,声明球赛库,导入其他需要的库,并指定ball.dart和util.dart是此库的部分:
library ballgame;
import 'dart:html';
// ...其他导入在这里...
part 'ball.dart';
part 'util.dart';
// ...代码从这里开始...
第二个文件ball.dart,实现了球赛库的一部分:
part of ballgame;
// ...代码从这里开始...
第三个文件,util.dart,实现了球赛库的其余部分:
part of ballgame;
// ...Code goes here...
* 可以通过重新导出部分库或者全部库来组合或重新打包库。例如,你可能有实现为一组较小的库集成为一个较大库。或者你可以创建一个库,提供了从另一个库方法的子集。
```
// In french.dart:
library french;
hello() => print('Bonjour!');
goodbye() => print('Au Revoir!');
// In togo.dart:
library togo;
import 'french.dart';
export 'french.dart' show hello;
// In another .dart file:
import 'togo.dart';
void main() {
hello(); //print bonjour
goodbye(); //FAIL
}
```
Dart 添加了一些新的语言特性用于支持异步编程。最通常使用的特性是 async 方法和 await 表达式。Dart 库大多方法返回 Future 和 Stream 对象。这些方法是异步的:它们在设置一个可能的耗时操作(比如 I/O 操作)之后返回,而无需等待操作完成
当你需要使用 Future 来表示一个值时,你有两个选择。
同样的,当你需要从 Stream 获取值的时候,你有两个选择。
使用 async 和 await 的代码是异步的,不过它看起来很像同步的代码。比如这里有一段使用 await 等待一个异步函数结果的代码:
await lookUpVersion()
要使用 await,代码必须用 await 标记
checkVersion() async {
var version = await lookUpVersion();
if (version == expectedVersion) {
// Do something.
} else {
// Do something else.
}
}
你可以使用 try, catch, 和 finally 来处理错误并精简使用了 await 的代码。
try {
server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044);
} catch (e) {
// React to inability to bind to the port...
}
声明异步函数
一个异步函数是一个由 async 修饰符标记的函数。虽然一个异步函数可能在操作上比较耗时,但是它可以立即返回-在任何方法体执行之前。
checkVersion() async {
// ...
}
lookUpVersion() async => /* ... */;
在函数中添加关键字 async 使得它返回一个 Future,比如,考虑一下这个同步函数,它将返回一个字符串。
String lookUpVersionSync() => '1.0.0';
如果你想更改它成为异步方法-因为在以后的实现中将会非常耗时-它的返回值是一个 Future 。
Future<String> lookUpVersion() async => '1.0.0';
请注意函数体不需要使用 Future API,如果必要的话 Dart 将会自己创建 Future 对象
一个 await表达式具有以下形式
await expression
在异步方法中你可以使用 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 标记:
```
main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
```
结合 streams 使用异步循环
await for (variable declaration in expression) {
// Executes each time the stream emits a value.
}
表达式
的值必须有Stream 类型(流类型)。执行过程如下:
如果要停止监听 stream ,你可以使用 break 或者 return 语句,跳出循环并取消来自 stream 的订阅 。
如果一个异步 for 循环没有正常运行,请确认它是一个异步方法。 比如,在应用的 main() 方法中使用异步的 for 循环时,main() 的方法体必须被 async 标记。
main() async {
...
await for (var request in requestServer) {
handleRequest(request);
}
...
}