1.类
- 使用类成员
- 对象的成员包括函数和数据,分别称为方法和实例变量
- 方法在某个对象上被调用,可以访问该对象的实例变量
- 使用构造函数
- 构造函数可以是ClassName或ClassName.identifier(命名构造函数),关键字new可选
- 如果类提供了常量构造函数,可使用const来创建编译时常量,使用相同构造函数相同参数值构造的编译时常量是同一个实例
- 在一个常量上下文里(变量名前已经有了const关键字),可以省去常量构造函数前面的const
- 在一个非常量上下文里,如果省去常量构造函数前面的const,则会创建一个非常量对象
- 获取对象类型
- 运行时使用对象的runtimeType属性来获得对象类型,一个Type类型的对象
- 实例变量
- 未初始化的实例变量值为null
- 每个实例变量都会自动生成一个隐式的getter方法,非final实例变量和 late final 声明但未声明初始化的实例变量还会生成setter方法
- 在声明实例变量的同时进行的初始化,将先于构造函数和初始化器运行
- 构造函数
- 方法中引用实例变量一般不需要使用this,但如果出现变量名冲突,则可以使用this来区分
- 在构造函数参数里使用this来简化赋值
- 如果不声明构造函数,则构造时会使用默认构造函数,它会调用父类的无参数非命名构造函数
- 构造函数不会继承于父类
- 如果需要多个构造函数,可以使用命名构造函数
- 默认会调用父类的无参数非命名构造函数,如果没有则需显示调用某个构造函数
- 构造时语句执行顺序依次为初始化列表、父类构造函数、当前类的构造函数
- 初始化列表
Point.fromJson(Map<String, double> json) : x = json['x']!, y = json['y']! { print('In Point.fromJson(): ($x, $y)'); }
初始化列表表达式 = 右边的语句不能使用 this 关键字。
* 初始化列表中,在开发模式下使用assert验证参数合法性
Point.withAssert(this.x, this.y) : assert(x >= 0)
* 在初始化列表里可以赋值给final实例变量
* 如果父类没有匿名无参数构造函数,那么子类必须调用父类的其中一个构造函数,为子类的构造函数指定一个父类的构造函数只需在构造函数体前使用(:)指定Employee.fromJson(Map data) : super.fromJson(data){}
* 传递给父类构造函数的参数不能访问任何实例变量 - 可以将某个构造函数重定向到其它构造函数
class Point { double x, y; // The main constructor for this class. Point(this.x, this.y); // Delegates to the main constructor. //这里使用this而非类名 Point.alongXAxis(double x) : this(x, 0); }
- 如果类的实例一旦创建后就不再更改,可使用常量构造函数,要求类的所有实例变量都为final
- 使用工厂构造函数(factory)来返回并不总是全新构造的对象,比如来自缓存的或者子类对象
- 工厂构造函数里不能使用this
- 方法
- 实例方法可以访问实例变量
- 可以使用get和set关键字来创建新属性(计算属性)
- 如果不定义函数体,则方法为抽象方法,需要在子类里实现该方法
- 抽象类(abstract class)
- 抽象类不能被实例化,通常用来定义接口
- 抽象类通常包含抽象方法,也可包含部分实现
- 隐式接口
- 如果不想继承父类方法,可以使用implements来实现父类接口,此时父类可以有多个
- 每个类都隐式定义了一个包含其所有成员和实现接口的接口
- 扩展类(继承)
- 使用extends来扩展父类,子类中可使用super引用父类
- 使用@override来表示有意地覆盖父类方法
- 使用operator来覆盖算术运算、相等运算、比较运算、位运算、下标访问等操作符
- 如果覆盖==操作符,需要同时覆盖hashCode方法
- 可以覆盖noSuchMethod来拦截对未定义方法或实例变量的访问
- 枚举类型(enum)
- 枚举类型是特殊的类,用来表示固定数量的常值
- 每个枚举值都有一个index成员变量的getter方法
- 要获取全部枚举值,使用枚举类的values方法
- 枚举类型可用于switch语句,case语句需要处理所有可能情况
- 枚举类型不能被继承、混入或实现,也不能实例化
- 混入(Mixin)
- 混入用来给类增加功能,是多继承模式下的一种代码复用方式
- 使用with来混入一个或多个mixin
- 混入类需直接继承于Object,并且没有构造函数,如果混入类不需要用作普通类(不声明构造函数),使用mixin来定义,否则使用class
- 使用on来限制可以使用混入的类
- 类变量和方法
- 使用static来定义类级别的变量和方法
- 类变量直到类被使用时才进行初始化
- 类方法里不能引用this
- 类方法是编译时常量。比如可以用作const构造函数的参数
import 'dart:math';
class Point {
num x, y;
num distanceFromOrigin;
//构造函数,:后面是初始化列表
Point(this.x, this.y) : distanceFromOrigin = sqrt(pow(x, 2) + pow(y, 2));
//命名构造函数,重定向到类名的构造函数
Point.origin() : this(0, 0);
//命名构造函数,重定向到类名的构造函数
Point.fromJson(Map<String, num> json) : this(json['x'] ?? 0, json['y'] ?? 0);
//初始化列表中验证参数合法性
Point.withAssert(this.x, this.y)
: assert(x >= 0),
distanceFromOrigin = sqrt(pow(x, 2) + pow(y, 2));
//实例方法,求两点间距离
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(pow(dx, 2) + pow(dy, 2));
}
//重写父类(Object)中的方法
@override
String toString() => 'Point($x,$y)';
}
//实现不能更改的点的类
class ImmutablePoint {
//常量类变量
static const origin = const ImmutablePoint(0, 0);
// 声明final的成员变量
final num x, y;
// 常量构造函数
const ImmutablePoint(this.x, this.y);
}
class Person {
String? firstName;
Person.fromJson(Map data) {
print('in person');
}
}
class Employee extends Person {
//使用super引用父类的方法
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
//定义日志类
class Logger {
final String name;
//关闭的意思
bool mute = false;
//缓存已经构造的类
static final _cache = <String, Logger>{};
//工厂构造方法
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name]!;
} else {
final logger = Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
//命名构造方法
Logger._internal(this.name);
//未关闭的话 就打印
void log(String msg) {
if (!mute) print(msg);
}
}
//矩形类
class Rectangle {
num left, top, width, height;
//非命名构造方法
Rectangle(this.left, this.top, this.width, this.height);
//定义计算属性right和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;
}
// 抽象类
abstract class Doer {
//抽象方法,没有实现
void doSomeThing();
}
//实现
class EffectiveDoer extends Doer {
@override
void doSomeThing() => 'do someThing';
}
class Person1 {
//私有变量,定义为final,那么类型为dynamic
final _name;
Person1(this._name);
String greet(String who) => 'Hello, $who. I am $_name';
}
//不想继承的话,使用implements实现隐式接口
class Impostor implements Person1 {
get _name => '';
@override
String greet(String who) => 'Hi $who. Do you know who I am?';
}
// 这个方法也可以传递Impostor的实例进去
String greetBob(Person1 person) => person.greet('Bob');
class Television {
void turnOn() {
print('illuminate display');
print('activateIr sensor');
}
}
class SmartTelevision extends Television {
//重写父类方法
@override
void turnOn() {
super.turnOn();
print('illuminate display');
print('activateIr sensor');
print('upgrade apps');
}
}
// 向量类
class Vector {
final int x, y;
Vector(this.x, this.y);
//使用operator重写操作符
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
@override
int get hashCode {
int result = 17;
result = 37 * result + x.hashCode;
result = 37 * result + y.hashCode;
return result;
}
//重写==必须重写hashCode方法
@override
bool operator ==(dynamic other) {
if (other is! Vector) return false;
Vector vector = other;
return (vector.x == x && vector.y == y);
}
@override
String toString() => "Vector($x,$y)";
}
class A {
@override
noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ' +
"${invocation.memberName}");
}
}
void main() {
var p = Point(2, 2);
p.y = 3;
print(p.distanceTo(Point(4, 4))); //2.23606797749979
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, "y": 2});
print(p2); //Point(1,2)
var ip = const ImmutablePoint(2, 2);
//相同常量构造函数,相同参数值,返回同一个对象
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
print(
'1: a is equal to b is ${identical(a, b)}'); //1: a is equal to b is true
//常量上下文中,可以省略后面的const
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
a = const ImmutablePoint(1, 1);
b = ImmutablePoint(1, 1);
print(
'2: a is equal to b is ${identical(a, b)}'); //2: a is equal to b is false
print(
'The type of a is ${a.runtimeType}, the type of b is ${b.runtimeType}'); //The type of a is ImmutablePoint, the type of b is ImmutablePoint
var emp = Employee.fromJson({});
if (emp is Person) {
emp.firstName = 'Bob';
}
(emp as Person).firstName = 'Bob';
var logger = Logger('UI');
logger.log('Button clicked');
/*
in person
in Employee
Button clicked
*/
var rect = Rectangle(3, 4, 20, 15);
rect.right = 12;
print('rect.left = ${rect.left}'); //rect.left = -8
print(greetBob(Person1('Kathy'))); //Hello, Bob. I am Kathy
print(greetBob(Impostor())); //Hi Bob. Do you know who I am?
final v = Vector(2, 3);
final w = Vector(2, 2);
print(v + w); //Vector(4,5)
print(v - w); //Vector(0,1)
dynamic a1 = A();
a1.c(); //You tried to use a non-existent member: Symbol("c")
}
2.泛型
- 正确指定泛型类型有助于产生更好的代码
- 泛型可减少重复代码
- 列表和哈希表构造函数和字面量均支持类型参数
- 泛型集合的类型信息会保留到运行时
- 可使用extends来限制类型参数的可选范围
- 泛型不仅支持类,还支持方法和函数
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
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);
}
class Person {
String? FirstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
//使用extends限制T必须为Person的子类
class Foo<T extends Person> {
String toString() => 'Instance of "Foo<$T>"';
}
//方法或函数名后跟上类型参数
T first<T>(List<T> ts) {
T tmp = ts[0];
return tmp;
}
void main() {
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(
'names is List<String> is ${names is List<String>}'); //names is List<String> is true
names = <String>['Seth', 'Kathy', 'Lars'];
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'
};
pages = Map<String, String>();
var nameSet = Set<String>.from(names);
nameSet.addAll(['Seth', 'Kathy', 'Lars']);
print(nameSet); //{Seth, Kathy, Lars}
var personFoo = Foo<Person>();
var employeeFoo = Foo<Employee>();
//未指定泛型参数,看看是什么类型
var foo = Foo();
//如果Foo定义时未指定extends Person,那么是Foo<dynamic>
print(foo);//Instance of "Foo<Person>"
}
3.库
- 库是模块化的、可分享的代码包
- 库是一个私有单元,以下划线打头的标识符只能在库内部使用
- 每个Dart应用都自动成为一个库
- 使用import来导入一个库,内置库的URI前缀为dart:,如
import 'dart:math';
,import 'dart:html';
;由包管理工具提供的库前缀为package:,如import 'package:flutter/material.dart';
- 如果导入的多个库之间出现标识符冲突,可使用as指定引用前缀来避免
import 'package:lib2/lib2.dart' as lib2;
lib2.Element element2 = lib2.Element();
- 使用show和hide来导入或隐藏指定的标识符,这意味着我们可以仅导入库的一部分
// 仅导入foo
import 'package:lib1/lib1.dart' show foo;
// 除了foo外都导入
import 'package:lib2/lib2.dart' hide foo;
- 使用deferred as来延迟加载库
import 'package:greetings/hello.dart' deferred as hello;
//当实际需要使用到库中 API 时先调用 loadLibrary 函数加载库
Future<void> greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
loadLibrary 函数可以调用多次也没关系,代码库只会被加载一次。
import 'dart:math';
//如果有标识符冲突
import 'dart:math' as m;
//只导入sqrt
import 'dart:math' show sqrt;
//导入math中除了sqrt的所有标识符
import 'dart:math' hide sqrt;
//延时加载math库
import 'dart:math' deferred as math;
//使用时候需要先加载库
Future sqrt(num n) async {
await math.loadLibrary();
math.sqrt(2);
}
4.异步
- async/await是的可以像编写同步代码那样编写异步代码
- 只有在标记为async的函数里可以使用await,该函数在执行到await语句时将暂停,直到await后的表达式运行完成
- 使用try-catch来捕捉await语句的错误
- async函数里可以多次使用await
- async函数返回Future对象,普通返回值将自动包装为Future对象,如
Future<String>
,如果函数没有返回值,需要设置返回类型为Future<void>
- 使用await for来从Stream里读取序列值,如果想终止,可使用break或return
import 'dart:async';
//定义异步的函数
Future<String> lookUpVersion() async => '1.0.0';
Future checkVersion() async {
var version = await lookUpVersion();
print(version);
}
//main函数中如要使用await,也需要显示声明为async
void main() async {
var version;
try {
version = await lookUpVersion();
} catch (e) {
print(e);
return;
} finally {
print('finally');
}
print(version);
var s = Stream.fromIterable([1, 2, 3]);
await for (var v in s) {
print(v);
}
}
/*
打印结果:
finally
1.0.0
1
2
3
*/
5.生成器
- 生成器惰性地产生序列值
- 生成器分为同步和异步两种,分别返回Iterable和Stream对象
- 使用sync */yield 来定义同步生成器
- 使用async */yield来定义异步生成器
- 使用yield*来优化从嵌套的生成器里读取序列值(递归可用的生成器)
- 生成器函数体中,执行到yield的地方,会返回一个值,同时会暂停函数的执行,当下一次再次调用函数时,再恢复执行,恢复的时候从yield后面继续执行
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
//遇到yield就返回值,并暂停执行,直到下一次调用生成器
while (k < n) yield k++;
}
Stream<int> asyncNatualTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
Iterable<int> natualsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* natualsDownFrom(n - 1);
}
}
void main() async {
//同步的
for (var v in naturalsTo(3)) {
print(v);
}
//读取Stream需要用await for
await for (var v in asyncNatualTo(3)) {
print(v);
}
for (var v in natualsDownFrom(3)) {
print(v);
}
}
6.可调用类
- 如果想让类可以像函数一样执行,可以给类添加一个call方法
class WannabeFunction {
call(String a, String b, String c) => '$a $b $c';
}
void main() async {
var wf = WannabeFunction();
var out = wf('Hi', 'there', 'gang');
print('$out');
}
7.类型别名(typedef)
- 类型别名用来命名已有类型
- 大多数情况下,推荐使用内联函数类型替代typedef,当然,函数的typedef依然是有效的
//定义一个整形list的别名
typedef IntList = List<int>;
//类型别名可以有类型参数
typedef ListMapper<X> = Map<X, List<X>>;
//函数的别名 更推荐使用内联函数
typedef compare<T> = bool Function(T a, T b);
void main() {
IntList il = [1, 2, 3];
print(il); //[1, 2, 3]
ListMapper<String> m = {
'1': ['1', '2', '3']
};
print(m); //{1: [1, 2, 3]}
bool sort(int a, int b) => a > b;
print(sort is compare<int>); //true
}
//内联函数类型
class FilteredObservable {
final bool Function(Event) _predicate;
final List<void Function(Event)> _observers;
FilteredObservable(this._predicate, this._observers);
void Function(Event)? notify(Event event) {
if (!_predicate(event)) return null;
void Function(Event)? last;
for (final observer in _observers) {
observer(event);
last = observer;
}
return last;
}
}
8. 元数据
- 元数据用来给代码添加额外信息,这些信息在运行时可以通过反射获取到
- 元数据注解以@打头,其后跟运行时常量或对常量构造函数的调用
- 内置的元数据注解有@deprecated、@Deprecated和@override。
- 元数据可出现在 library、class、typedef、type parameter、 constructor、factory、function、field、parameter 或者 variable 声明之前使用,也可以在 import 或 export 之前使用。可使用反射在运行时获取元数据信息。
- 可以自定义元数据注解
class Television {
@deprecated
void activate() {
tunOn();
}
//@Deprecated可以添加注释
@Deprecated('使用turnOn替代')
void activate2() {
tunOn();
}
void tunOn() {}
}
//自定义@Todo注解
class Todo {
final String who;
final String what;
const Todo(this.who, this.what);
}
@Todo('seth', 'make this do sth')
void doSth() {
print('do sth');
}
void main() {
final tel = Television();
tel.activate();
tel.activate2();
doSth();
}
9. 注释
- 单行代码注释使用//
- 多行代码注释使用/* */
- 单行文档注释使用///
- 多行文档注释使用/** */,也可以使用多行连续的///,推荐后者
- 代码注释在编译后会被丢弃,而文档注释会保留
- 文档注释里可使用[]来引用当前作用域内可见的函数、类、方法等,一旦注释被转换为文档,引用文字会被替换为文档链接