变量
定义
// 使用var定义一个变量,可以自动推断类型
var name = 'Bob';
// 使用指定类型定义一个变量
String text = '123';
// 使用dynamic和Object定义一个不局限于单一类型的变量
dynamic a = 1;
Object obj = true;
dynamic
和Object
的区别
dynamic
:变量在运行时确定实际类型
Object
:变量在编译时确定实际类型,效率优于dynamic
默认值
Dart 中一切皆为对象,包括 int
和 double
// Dart中,未初始化的变量均有一个默认值null
// 但对于未声明成可空的变量,使用前必须先赋值,否则无法使用
int i; // defalut null
final & const
// final修饰的变量是不可变的
final String s = '1';
// const修饰的变量表示编译时常亮
// 如果const修饰的变量在class中,则必须加上`static`关键字
const c = 'c';
class A {
static const a = '1';
}
late
Since Dart 2.12
- 如果你确定一个变量在使用时是非空的,那就可以添加
late
关键字,让编译器在该变量定义时不校验是否可空。 - 当使用
late
关键字修饰变量的同时对该变量进行赋值,则只有当该变量第一次使用时,才会进行真正的赋值。
内置类型
Numbers
Dart 中包含两种 Numbers
类型,int
和 double
Strings
使用单引号 ' '
或双引号 " "
来创建字符串
使用 ${表达式}
进行字符串插值
使用三引号""" """
创建多行字符串
在字符串前添加r
,如r'n\n'
,生成原始raw字符串 n\n
// 使用+进行字符串拼接
var s1 = 'aaa' + 'bbb' + 'ccc';
print(s1); // aaabbbccc
// 将多个字符串放在一起进行字符串拼接
var s2 = 'aaa' 'bbb' 'ccc';
print(s2); // aaabbbccc
// 使用三个单引号或双引号创建多行字符串
var s3 = '''
aaa
bbb
ccc''';
print(s3);
// aaa
// bbb
// ccc
// 在字符串前加上r作为前缀创建raw字符串,不会处理任何的转义字符
var s4 = r'123 \n';
print(s4); // 123 \n
Booleans
bool b = true;
true
/ false
Lists
// 定义
var list1 = [1, 2, 3];
// 数组内可添加尾随逗号,方便格式化
var list2 = [
'apple',
'banana',
'orange',
];
// 可以使用add/addAll添加元素
list1.add(4);
list1.addAll([5, 6, 7]);
// 循环list
for (var i = 0; i < list1.length; i++) {
...
}
for (var value in list1) {
...
}
list1.forEach((element) {
...
});
// 创建一个常量list,在list前添加const,或者用const声明
var constList = const ['a', 'b', 'c'];
const constList2 = [1, 2, 3];
// 可以使用...扩展操作符,将一个list中所有元素插入到另一个list中
var list1_1 = [0, ...list1]; //[0, 1, 2, 3, 4, 5, 6, 7]
// 可以使用...?空感知扩展操作符来避免发生异常
var listNull;
var list1Null = [0, ...?listNull]; //[0]
// 创建list时,可使用if或者for
var flag = false;
var listIf = [1, 2, if (flag) 3]; //[1, 2]
var listFor = [1, 2, for (var i in listIf) i]; //[1, 2, 1, 2]
const list = []
和var list = const []
的区别
const list = []
:list是一个常量数组,无法修改数组内的元素,也无法修改引用
var list = const []
:list引用一个常量数组,无法修改数组内的元素,但可以修改list的引用
Sets
// 定义
var set1 = {1, 2};
// 定义一个空的set,需要在{}前指定类型,或赋给一个set类型的变量
var set2 = <String>{};
Set<String> set3 = {};
// 若向下面这样创建,则会默认生成一个Map<dynamic, dynamic>
var map = {};
// 可以使用add/addAll添加元素
set1.add(3);
set1.addAll({4, 5, 6});
// 循环set
for (var value in set1) {
...
}
set1.forEach((element) {
...
});
// 创建一个常量set,在set前添加const,或者用const声明
var constSet = const {1};
const constSet2 = {2};
//set可支持list一样的.../...?/if/for
Maps
// 定义一个map
var map = {'a':1,'b':2};
// 也可使用构造器创建map
// 若构造器未指定key value类型,则默认创建dynamic类型
var map2 = Map<int, int>();
map2[1] = 11;
map2[2] = 22;
// 循环map
map.forEach((key, value) {
...
});
// const,...,...?,if,for与list和set类似
Runes 与 grapheme clusters
不常用,Dart中文网
Symbols
不常用,Dart中文网
运算符
类型判断运算符 as & is & is!
as
:类型转换
is
:类型判断,若类型匹配,返回true
is!
:类型判断,若类型匹配,返回false
// 如果emp是null或者不是Person类型,会抛异常
(emp as Person).firstName = 'Bob';
// 如果emp是null或者不是Person类型,不会抛异常,会走else
if (emp is Person) {
emp.firstName = 'Bob';
}
赋值运算符
使用??=
来为null
值赋值,若不是null
,则不会赋值
var i;
i ??= 1;
print(i); // 1
i ??= 2;
print(i); // 1
条件表达式
condition ? expr1 : expr2
:与Java一样,三目运算符
expr1 ?? expr2
:若expr1 non-null
,则返回expr1
,否则返回expr2
var flag = true;
print(flag ? "true" : "false"); // true
String? str;
print(str ?? "str is null"); // str is null
str = "abc";
print(str ?? "str is null"); // abc
级联运算符 .. / ?..
使用级联运算符..
/?..
可以让你在同一个对象上执行一系列的操作,函数调用或访问字段
void main() {
var t = Test()
..a() // aaa
..b() // bbb
..flag = false;
print(t.flag); // false
}
class Test {
var flag = true;
void a() {
print('aaa');
}
void b() {
print('bbb');
}
}
?..
需要dart >= 2.12
void main() {
var t = createTest(true)
?..a() // aaa
..b() // bbb
..flag = false;
print(t?.flag); // false
}
class Test {
var flag = true;
void a() {
print('aaa');
}
void b() {
print('bbb');
}
}
Test? createTest(bool flag) {
if (flag)
return Test();
else
return null;
}
Functions 函数
函数的定义与Java类似;
除了使用void
修饰返回值的函数没有返回值,其他所有函数均有返回,若没有显式的返回值,默认最后一行执行return null
;
bool isNull(obj) {
return obj == null;
}
如果不定义返回类型,该函数依然有效,但不推荐
isNull(obj) {
return obj == null;
}
如果函数体内只有一个表达式,可以使用=>
语法
isNull(obj) => obj == null;
参数 Parameters
函数有两种形式的参数:必要参数 和 可选参数,必要参数定义在列表前面,可选参数定义在必要参数后面;
可选参数可以是 命名的(使用{}括起来的参数
) 或者 位置的(使用[]括起来的参数
);
必要参数不能设置默认值,可选参数可以设置默认值;
void main() {
fun(1, "Hello"); //a: 1, b: Hello, c: null, d: 123
fun(1, "Hello", c: 1, d: "Dart"); //a: 1, b: Hello, c: 1.0, d: Dart
fun(1, "Hello", d: "Dart", c: 2.5); //a: 1, b: Hello, c: 2.5, d: Dart
fun(1, "Hello", d: "Dart"); //a: 1, b: Hello, c: null, d: Dart
}
/// [a] 必要参数
/// [b] 必要参数
/// [c] 可选命名参数
/// [d] 可选命名参数,默认值为 123
void fun(int a, String b, {double? c, String d = "123"}) {
print("a: $a, b: $b, c: $c, d: $d");
}
函数作为一级对象
Dart中,一切皆对象,所以函数也有类型Function
,所以函数可以赋值给对象或者作为参数传递。
void main() {
// 将fun函数作为参数传递
[1, 2, 3].forEach(fun);
// 将函数赋值给变量
var f = (String b) => "@@@@ $b";
print(f("123")); //@@@@ 123
}
void fun(int a) => print(a);
匿名函数
一个没有名字的函数被称为 匿名函数,又称 Lambda表达式,Closure闭包
void main() {
// 最常见的匿名函数就在forEach循环中
[1, 2, 3].forEach((element) {
print(element);
});
// 若函数体只有一行,则也可以使用=>语法
[1, 2, 3].forEach((element) => print(element));
}
流程控制语句
-
if
和else
-
for
循环 -
while
和do-while
-
break
和continue
-
switch
和case
以上这些均与Java类似 -
assert
:断言,若表达式为true
,执行接下来的代码,若为false
,抛出异常。仅在调试模式生效,生产环境会被忽略。
void main() {
int num = 14;
// 第一个参数传入一个bool类型的表达式,第二个参数为可选参数,用作异常提示
assert(num < 10, "num 必须小于 10");
print('num < 10');
}
异常 Exception & Error
Dart 中的异常都是非必检异常,可以不捕获。
抛出异常
Dart 默认提供了Exception
和Error
及它们一系列子类,Dart还可以将任何非null
对象作为异常抛出。
throw Exception("123");
throw "abc";
捕获异常
使用try catch
来捕获。
catch
支持两个参数,第一个参数(e):异常对象;第二个参数(s):栈信息。
void main() {
try {
test();
} catch (e, s) {
print(e);
print(s);
}
}
void test() {
throw Exception("123");
}
// Exception: 123
// #0 test (package:flutter_demo/test.dart:11:3)
// #1 main (package:flutter_demo/test.dart:3:5)
// #2 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:283:19)
// #3 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
可以使用on
来指定异常。
void main() {
try {
test('1.1');
} on int catch (e) {
// throw的是int类型
print('int error');
} on String catch (e) {
// throw的是String类型
print('String error');
} catch (e) {
// throw的其他类型
print("other type error");
}
}
void test(i) {
if (i is int) {
throw 1;
} else if (i is String) {
throw "123";
}
throw true;
}
rethrow
可以使用rethrow
将捕获的异常再次抛出。
void main() {
try {
test();
} catch (e) {
print("other type error");
}
}
void test() {
try {
dynamic a = true;
print(a++);
} catch(e) {
print('123');
rethrow;
}
}
// test中使用rethrow输出:
// 123
// other type error
// test中不使用rethrow输出:
// 123
finally
与Java类似,无论如何,都会执行到finally
代码块。
类 class
定义,属性声明
与java类似
class Test {
int a;
String? b;
bool c = true;
// 使用下划线来声明一个私有属性
int _d = 1;
}
使用下划线_
来声明变量,表示这是一个私有变量。
构造函数
构造函数语法糖
class Test {
int a;
int b = 1;
// 构造函数,语法糖,未赋值且非空字段必须添加到里面
Test(this.a);
// Test(this.a, this.b); // 错误,注意:构造函数不能重载,只能实现一个
}
命名构造函数
class Test {
int a;
// 命名构造函数
Test.a(this.a);
}
参数初始化列表
class Test {
int a;
String? b;
bool c = true;
// 参数初始化列表
Test.abc(this.a)
: b = "str",
c = false;
Test.fromMap(Map map)
: a = map['a'],
b = map['b'],
c = map['c'];
}
重定向构造函数
class Test {
int a;
Test(this.a);
// 重定向构造函数,只能使用命名构造函数重定向到默认构造函数
Test.no() : this(0);
}
私有构造方法
class Test {
int a;
// 私有构造方法,在命名构造方法命名前添加下划线_
Test._p(this.a);
}
常量构造函数
// 常量构造函数
// 在类的构造函数前加上 const 关键字并确保所有实例变量均为 final 来实现常量构造函数
class ConstTest {
final int x;
final int y;
const ConstTest(this.x, this.y);
}
void main() {
var c1 = ConstTest(1, 1);
var c2 = ConstTest(1, 1);
var c3 = const ConstTest(1, 1);
var c4 = const ConstTest(1, 1);
var c5 = const ConstTest(1, 2);
// 使用new关键字(可省略)创建的对象,始终为false
print(c1 == c2);
// 使用const关键字创建的对象,当参数一致时,为true
print(c3 == c4);
// 使用const关键字创建的对象,当参数不一致时,为false
print(c3 == c5);
}
工厂构造函数
// 工厂构造函数
class FactoryTest {
// 使用factory修饰的构造函数,就是工厂构造函数,必须返回一个实例对象
factory FactoryTest.get() {
return FactoryTest();
}
FactoryTest();
}
使用工厂构造函数构造类的实例时并非总是会返回新的实例对象。例如,工厂构造函数可能会从缓存中返回一个实例,或者返回一个子类型的实例。
Getter & Setter
每一个属性都有一个隐式的Getter
方法,非final
属性的话,还有一个Setter
方法。
void main() {
var p = Point(1, 2);
print('x: ${p.x}, y: ${p.y}'); //x: 2, y: 2
p.x = 2;
p.y = 3;
print('x: ${p.x}, y: ${p.y}'); //x: 4, y: 3
}
class Point {
int _x;
// 每一个属性都有一个隐式的Getter方法,非final属性的话,还有一个Setter方法
int y;
Point(this._x, this.y);
// 为私有属性_x提供自定义get方法 [returnType] get [functionName] {}
int get x => _x * 2;
// 为私有属性_x提供自定义set方法 set [functionName](param) {}
set x(int x) => _x = x;
}
运算符重载
使用operator
修饰符来修饰
void main() {
var p1 = Point(1, 2);
var p2 = Point(3, 4);
var p3 = p1 + p2;
print("p3.x: ${p3.x}, p3.y: ${p3.y}"); //p3.x: 4, p3.y: 6
}
class Point {
int x;
int y;
Point(this.x, this.y);
// 运算符重载,+运算符
Point operator +(Point other) => Point(x + other.x, y + other.y);
}
抽象类,抽象方法
使用abstract
关键字修饰的类,就是抽象类;
在抽象类中,没有方法体的方法就是抽象方法。
void main() {
var animal = Dog();
print(animal.name()); //狗
print(animal.species()); //动物
}
// 定义一个抽象类
abstract class Animal {
// 定义一个抽象方法,不写方法体即可,子类必须重写,不像java,不需要也不可添加abstract关键字
String name();
String species() => "动物";
}
class Dog extends Animal {
@override
String name() => "狗";
}
隐式接口
与Java不同,Dart中没有interface
关键字,Dart中每个类都隐式的定义了一个包含所有实例成员的接口,并且这个类实现了这个接口,所以你可以implement
某个class
,重写其内部所有属性及方法即可。
class Person {
var name = "姓名";
String greet() => "你好啊!";
}
class P implements Person {
@override
String name = "小P";
@override
String greet() => "我是小P,你好啊!";
}
接口与继承的区别:
- 单继承,多实现;
- 继承可以有选择的重写父类方法并且可以使用super,实现强制重新定义接口所有成员;
枚举类 enum
void main() {
var greenIndex = colorIndex(Color.green);
print(greenIndex); //1
}
enum Color { red, green, blue }
int colorIndex(Color color) {
switch (color) {
case Color.red:
return Color.red.index;
case Color.green:
return Color.green.index;
case Color.blue:
return Color.blue.index;
}
}
枚举类有两个限制:
- 枚举不能成为子类,也不可以Mixin,也不可以实现一个枚举;
- 不能显示得实例化一个枚举类。
Mixin
Mixin
是一种在多重继承中复用某各类中代码的方法模式。
使用with
关键字并在其后跟上Mixin类的名字来使用Mixin模式。
一个没有构造函数的类,就是Mixin类。
void main() {
var c = C();
print(c.a()); //B a
print(c.b()); //B b
}
class A {
String a() => "A a";
}
class B {
String a() => "B a";
String b() => "B b";
}
class C with A, B {}
以上述代码为例:若类A,B,C中均有重名方法,那C类的对象在执行重名方法时的逻辑为:
- 优先执行C类中的方法;
- 如果C类中没有该方法,会从
with
后面,从后往前
寻找拥有该方法的类,并执行其类中对应的方法;
若你想让一个类只能被用做Mixin类使用,无法像其他正常类使用,将class
改为mixin
关键字即可。
使用mixin关键字声明的类无法被实例化。
mixin D {
String d() => "D d";
}
你也可以使用on
关键字来指定哪些类可以使用该Mixin类。
class E {}
// 确保mixin F只能被E的子类mixin
mixin F on E {}
// 正确
class G extends E with F {}
// 错误,因为想要使用mixin F,H必须继承自E才可以
// class H with F {}
类变量和方法(静态变量和静态方法) static
使用关键字static
修饰的变量或方法。
void main() {
print(StaticTest.str);
print(StaticTest.greet());
}
class StaticTest {
int i = 0;
static String str = "你好啊";
static String greet() => "Hello!";
}
静态变量只有在首次使用时初始化。
对于一些通用或常用的静态方法,应该将其定义成顶级函数而非静态方法。
生成器 Generators
当你需要延迟地生成一连串的值时,可以考虑使用 生成器函数。Dart 内置支持两种形式的生成器方法:
同步生成器
:返回一个Iterable
对象。
异步生成器
:返回一个Stream
对象。
在函数后添加
sync*
关键字,并将返回值设置为Iterable
来实现一个同步生成器函数,并使用yield
来传递值。
在函数后添加async*
关键字,并将返回值设置为Stream
来实现一个异步生成器函数,并使用yield
来传递值。
如果生成器是递归调用的,可以使用yield*
来提升性能
// 在函数后添加 sync* 关键字,并将返回值设置为 Iterable 来实现一个 同步 生成器函数
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) {
print(k);
yield k++;
}
}
// 在函数后添加 async* 关键字,并将返回值设置为 Stream 来实现一个 异步 生成器函数
Stream<int> asyncNaturalsTo(int n) async* {
int k = 0;
while (k < n) {
print(k);
yield k++;
}
}
// 如果生成器是递归调用的,可以使用 yield* 来提升性能
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
可调用类Callable classes
通过实现类的call
方法,允许使用类似函数调用的方式来使用该类的实例
void main() {
var test = Test();
print(test.call(3)); //6
print(test(2)); //4
}
class Test {
String call(int a) => "${a * 2}";
}
类型定义 typedef
Dart 2.13
之前,typedef
只能用于函数类型;
typedef f = int Function(int a, int b);
Dart 2.13
之后,typedef
可以用作类型别名;
typedef IntList = List<int>;
IntList il = [1, 2, 3];
元数据 @
使用元数据可以为代码增加一些额外的信息。
元数据注解以 @
开头,其后紧跟一个编译时常量
(比如 deprecated)或者调用一个常量构造函数
。
class Todo {
final String who;
final String what;
const Todo(this.who, this.what);
}
@Todo("seth", "playing football")
void doSomething() {
print("do something");
}
元数据可以在 library、class、typedef、type parameter、constructor、factory、function、field、parameter、variable
声明之前使用,也可以在 import 或 export
之前使用。
可使用反射在运行时获取元数据信息。
extension 扩展
void main() {
'42'.parseInt();
}
extension NumberParsing on String {
int parseInt() => int.parse(this);
}