文章目录
1. const 和 final
-
Dart中的const定义一个编译时常量。编译时常量是指,在编译时该const值必须确定。而final是指变量的值只能被设置一次。可以是在运行时确定。
示例1:
// 正确 final time = new DateTime.now(); // 错误。因为 DateTime.now()在运行时才能获知时间,在编译时无法获知。 const time = new DateTime.now();
-
const创建的常量是深度递归的,例如常量容器也内元素也是不能变的,但是final修饰的容器内元素可以改变。
示例:
const foo1 = [1,2]; foo1[0]=3; // 错误 foo1.add(3); // 错误 const foo1 = [1,2]; foo1[0]=3; // 正确 foo1.add(3); // 正确
-
相同的const对象只会被创建一次,而相同的final对象被创建多个。
-
const 可以用来申明常量变量,也可以创建常量值。
示例:
var foo = const[1,2,3]; // const修饰的是[1,2,3],所以[1,2,3] 不能更改,但是foo指向的对象可以更改。 foo=[1,2]; //正确 const foo = [1,2,3]; // 这表示修饰的foo是一个常量变量,不可更改foo指代的对象。 // 根据语法可推测此时[1,2,3] 中的内容是可以更改,但是实际上 foo[1] = 4; // 错误 // const foo = [1,2,3]; 等于 const foo = const [1,2,3]; // 可以认为const foo =xxx 时 const foo = const xxx 的简写形式 // final 修饰的变量所指代的对象是可以更改对象的内容,但是不能更改变量指代的对象。 所以可以认为以下两句是等价的: final foo = const [1,2,3]; const foo = [1,2,3];
2. Mixin
mixin是一个类,其他类可以访问mixin类中的方法、成员变量而不必继承该类。
mixin是一个普通的类(在Dart语言中该类没有构造方法),其他类可以继承该类中的方法和变量而不用继承整个类。
-
在java语言中,类的继承关系是单继承的,可以extends一个父类并可以implements多个接口。在Dart中也同样采用这种继承关系。只不过Dart中没有interface 关键字,只有普通类和抽象类。当一个类被作为接口使用,那么在子类中必须重写这个接口类中的所有方法,而不管该方法是否是抽象方法。
所以当有如下继承关系时:
我们在不用Mixin的情况下,java代码的写法和Dart写法基本一致。// 使用接口的语法 // 动物 abstract class Animal { void live(); } // 水生动物 class Aquatic extends Animal { @override void live() { print("我生活在水中!"); } } // 陆生动物 class Terrestrial extends Animal { @override void live() { print("我生活在陆地!"); } } // 定义接口----------------- abstract class Flyable { void fly() { print("我可以飞行!"); } } abstract class Swimmable { void swim() { print("我可以游泳!"); } } abstract class Singable { void sing(); } // ------------------------ //鲨鱼 class Shark extends Aquatic implements Swimmable { @override void swim() { print('我可以游泳'); } } // 章鱼 class Octopus extends Aquatic implements Swimmable { @override void swim() { print('我可以游泳'); } } // 猫 class Cat extends Terrestrial implements Singable { @override void sing() { print('喵喵叫!'); } } // 鸟 class Bird extends Terrestrial implements Singable, Flyable { @override void sing() { print("啾啾叫!"); } @override void fly() { print("我可以飞行!"); } } main(List<String> args) { Shark().swim(); Octopus().swim(); Cat().sing(); Bird().sing(); Bird().fly(); } /* 我可以游泳 我可以游泳 喵喵叫! 啾啾叫! */
上述代码中,虽然abstract类中已经对方法提供了实现,但是当该抽象类作为接口使用,子类必须重写该接口中的所有方法。观察上述代码
Swimmable
接在,在实现该接口的Shark
和Octopus
类中, 重新的swim
方法完全一样,造成了代码的冗余, 此时可以使用Mixin来来解决该问题 , 将以上代码修改:// 使用mixin // 动物 abstract class Animal { void live(); } // 水生动物 class Aquatic extends Animal { @override void live() { print("我生活在水中!"); } } // 陆生动物 class Terrestrial extends Animal { @override void live() { print("我生活在陆地!"); } } // 定义接口----------------- mixin Flyable { void fly() { print("我可以飞行!"); } } mixin Swimmable { void swim() { print("我可以游泳!"); } } abstract class Singable { void sing(); } // ------------------------ //鲨鱼 class Shark extends Aquatic with Swimmable { } // 章鱼 class Octopus extends Aquatic with Swimmable { } // 猫 class Cat extends Terrestrial implements Singable { @override void sing() { print('喵喵叫!'); } } // 鸟 // with 关键字必须在implements前面 class Bird extends Terrestrial with Flyable implements Singable { @override void sing() { print("啾啾叫!"); } } main(List<String> args) { Shark().swim(); Octopus().swim(); Cat().sing(); Bird().sing(); Bird().fly(); }
上述代码中我将
Flyable
, 和Swimmable
改为了mixin
. 此时(使用with
关键字来继承mixin类) 子类中不必重写mixin中已经实现的方法.为啥不用extends方式继承? 因为extends是单继承的, 使用with可以加入多个混合类.
上述示例中可以看出mixin的优点:
- 解决了extends不能多继承问题
- 解决了接口继承方式子类必须重写接口中所有方法(不管是否是抽象方法)的缺点.
-
mixin
的继承顺序, 当with关键字后继承多个mixin类且这些类有相同的方法时:- 子类中的重写版
- with关键字后的多个mixin类的最后一个.
其实mixin其继承原理,并不是只继了最后一个版本,而是按照顺序继承, 例如:
class C with A, B{}
其继承关系是 C->B->A, 即当A,B,C中有同名方法时, B中方法覆盖A中的,C中方法覆盖B中的.例如:
mixin A { void test() { print("Im class A"); } } mixin B { void test() { print("Im class B"); } } class C with A, B {} main(List<String> args) { C().test(); } // 输出: // Im class B
示例:
abstract class Base { void test() { print("Im class Base"); } } mixin A on Base { void test() { print("Im class A"); super.test(); } } mixin B on Base { void test() { print("Im class B"); super.test(); } } class C extends Base with A, B { @override void test() { print("Im class C"); super.test(); } } main(List<String> args) { C().test(); } // 输出: //Im class C //Im class B //Im class A //Im class Base
-
on
关键字在 mixin中的使用, 声明mixin类时, 可以使用on
关键字来申明该mixin类可以在哪个类的子类中使用.abstract class Base { void say() { print("Im base class"); } } mixin MixFun on Base { void test() { print('Im Mixin funcion'); } } // 正确, 因为BaseExt是Base类的子类, MixFun可以混入 class BaseExt extends Base with MixFun { } // 报错!!, 因为 ObjTest不是Base以及Base的子类. class ObjTest with MixFun { }
3. Future和Stream
Future
表示不能立即完成的计算。普通函数返回结果,异步函数返回Future,后者最终将包含结果。当结果准备好时,未来会告诉你。
Stream
是异步事件的序列。它就像一个异步迭代,当你请求它时,流不是得到下一个事件,而是告诉你当它准备好时有一个事件。
4. dynamic/var/object
- dynamic是所有dart 对象的基础类型,在大多数情况下,不直接使用它。
通过它定义的变量会关闭类型检查,这意味着dynamix x= 'hi'; x.foo();
这段代码静态类型检查不会报错,但是运行时会crash,因为String并没有foo()
方法,所以建议大家在编程时不要直接使用dynamic; - var是一个关键字,系统会自动判断类型
runtimeType
; - object:是Dart 对象的基类,定义
object o =xxx;
时系统会认为o
是个对象,你可以调用o的toString()
和hashCode()
方法因为Object 提供了这些方法,但是如果你尝试调用o.foo()
时,静态类型检查会运行报错。
总结:dynamic 与object 的最大的区别是在静态类型检查上。
5. Runes 和 Symbols
1.Runes
Runes是TUF-32字符集。在Dart语言中的String是使用UTF-16表示的。所以UTF-32需要使用专门的类来表达。比如\u{1f600}
代表笑脸emoji。
main(List<String> args) {
var s = '\u{1f600}';
print(s.runtimeType); // String
print(s.runes.runtimeType); // Runes
print(s); //输出:😀。 说明String在输出时自动转码
print(s.runes); //输出:(128512) .输出的是编码值
// 创建一个Runes对象
Runes runes = Runes('\u{2665} , \u{1f605}, \u{1f60e}');
print(runes); // 输出:(9829, 32, 44, 32, 128517, 44, 32, 128526)
// 输出字符
print(String.fromCharCodes(runes)); // 输出:♥ , 😅, 😎
}
此外, Runes继承自Iterable
, 所以Runes是课迭代对象。
2.Symbols
官网解释很模糊。只说用到的机会很少。然后就没了。。。
此教程中有一个在反射机制上的应用。
反射机制加载类,需要使用Symbol类作为参数。以下为示例:
首先定义一个library:
library fool_lib;
class Foo {
f1() {
print("function 1");
}
f2() {
print("function 2");
}
f3() {
print("Function 3");
}
}
使用反射机制加载该library
import 'dart:mirrors';
import 'fool_lib.dart';
main(List<String> args) {
Symbol lib = Symbol("fool_lib");
Symbol className = Symbol("Foo");
if (checkIfClassAvailableInLibary(lib, className)) print("class fount");
}
bool checkIfClassAvailableInLibary(Symbol lib, Symbol className) {
MirrorSystem mirrorSystem = currentMirrorSystem();
LibraryMirror libMirror = mirrorSystem.findLibrary(lib);
if (libMirror != null) {
print("找到模块");
print("${libMirror.declarations.length}");
libMirror.declarations.forEach((key, value) {
print(key);
});
}
if (libMirror.declarations.containsKey(className)) {
return true;
}
return false;
}
6. getter和setter
get
和set
方法为类的成员属性提供了读和写的方法. 每个类的实例其实都包含了隐式的get和set. 同时我们也可以显示定义get和set方法. 使用get和set方法定义的成员变量无法在类的构造函数中赋值.
例如:
class A{
set a(int v) => a=v;
int get a => a;
A(this.a); // 报错
}
get的使用示例:
class Vehicle {
String make;
String model;
int manufactureYear;
int vehicleAge;
String color;
Map<String,dynamic> get map {
return {
"make": make,
"model": model,
"manufactureYear":manufactureYear,
"color": color,
};
}
int get age {
return DateTime.now().year - manufactureYear;
}
void set age(int currentYear) {
vehicleAge = currentYear - manufactureYear;
}
Vehicle({this.make,this.model,this.manufactureYear,this.color,});
}
main(){
Vehicle car = Vehicle(
make:"Honda",
model: "Civic",
manufactureYear:2010,
color: "red",
);
print(car.age);// 输出车龄
print(car.map);// 将car的属性组装成map输出
}
7. Dart构造函数
Dart语言的构造函数有4种:
- 普通构造函数
- 命名构造函数
- 常量构造函数
- 工厂构造函数
- 默认构造函数
如果定义了类,而没有提供构造函数,那么它有一个默认无参数的构造函数.
2.普通构造函数
class A{
num x,y;
A(num x, num y){
this.x=x;
this.y=y;
}
A(this.x, this.y); // 上述构造函数可以简写为此形式, 此为dart语言的语法糖
}
3.命名构造函数
Dart语言的普通构造函数不能重载,当需要有多个构造函数时,需要使用命名构造函数.
此外,命名构造函数不能被继承.
class A{
num x,y;
A(this,x,this,y);
A.origin(){
x=0;
y=0;
} // 匿名构造函数.
}
4.之类构造调用父类构造
之类的构造函数中必须调用父类的构造函数. 当我们没有显示调用父类的构造函数时, 系统默认调用父类的父类构造函数.
构造函数的执行顺序(和c++的顺序相同):
初始化列表->父类构造->子类构造;
当父类没有无参数的构造函数时,此时我们必须显示的调用父类的构造函数.
class A{
num x,y;
A(this.x,this.y);
}
class B extends A{
num z;
B(num x, num y, this.z):super(x,y); // 调用父类构造
B(num x, num y, num z): z=z, super(x,y) ; // 此构造方法等同与上一个,
//需要注意的是: 1. 初始化列表中属性初始化使用=,而父类构造使用(). 2. 父类的构函数必须放在初始化列表的最后
B(num x, num y, num z):super(x,y) , z=z,; // 这样写将会报错
}
main(){
var b = B(1,2,3);
print(b.x);
print(b.y);
print(b.z);
}
5.构造函数传递
类中可以有一个普通构造函数和多个命名构造函数. 构造函数之间可以传递.
class Point {
num x, y;
// 普通构造函数
Point(this.x, this.y);
// 初始化列表中调用普通构造函数
Point.alongXAxis(num x) : this(x, 0);
}
6.常量构造函数
- 常量构造函数中所有的成员变量都是final.
- 创建常量对象时需要加const修饰,否则创建的是非const对象.
7.工厂构造方法
工厂构造函数是一种构造函数,与普通构造函数不同,工厂函数不会自动生成实例,而是通过代码来决定返回的实例对象.
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);
}
}