Dart-Tour2-类
类
Dart语法样式: https://www.dartlang.org/guides/language/effective-dart/style
语法:https://www.dartlang.org/guides/language/language-tour
概览
- 重要概念
- 关键词
- 注释
- 变量
- 内置类型
- 函数
- 操作符
- 流程
- 异常
- 类
- 泛型
- 库
- 异步
- 发电机
- 可调用的类
- 分离
- 类型定义
- 元数据
Dart是一种具有类和基于mixin的继承的面向对象语言。每个对象
都是一个类的实例
,所有类都来自Object
。
基于Mixin的继承意味着虽然每个类(除了Object)只有一个超类,但是类体可以在多个类层次结构中重用。
特点:当最左边的操作数为null
时,使用?.
而不是.
避免异常:
构造函数
12345678910111213141516 | class { num x,y; // 原始写法 Point(num x, num y){ // 该this关键字是指当前实例。 this.x = x; this.y = y; }*/ // 使用Dart语法糖 // 在构造体运行之前,为x,y设置语法糖。 Point(this.x, this.y); } |
默认构造函数
如果未声明构造函数,则会为您提供默认构造函数。默认构造函数没有参数,并在超类中调用无参数构造函数。
构造函数不是继承的
子类不从其超类继承构造函数。没有声明构造函数的子类只有默认(无参数,无名称)构造函数。
命名构造函数(多构造函数)
12345678 | // 命名构造函数:originPoint.origin(){x = 8;y = 9;}// 命名构造函数:withValPoint.withVal(this.x, this.y); |
注意:构造函数不是继承的,这意味着超类的命名构造函数不会被子类继承。如果希望使用超类中定义的命名构造函数创建子类,则必须在子类中实现该构造函数。
一个完整的多构造函数写法:
123456789101112131415161718192021222324252627 | class { num x,y; // 原始写法 Point(num x, num y){ // 该this关键字是指当前实例。 this.x = x; this.y = y; }*/ // 使用Dart语法糖 // 在构造体运行之前,为x,y设置语法糖。 Point(this.x, this.y); // 命名构造函数:origin Point.origin(){ x = 8; y = 9; } // 命名构造函数:withVal Point.withVal(this.x, this.y);} |
调用
1234567891011121314 | void classAboutMethod(){ var pointOrigin = Point(34,67); print(pointOrigin.x); print(pointOrigin.y); var pointAbout = Point.origin(); print(pointAbout.x); print(pointAbout.y); var pointAboutVal = Point.withVal(11, 21); print(pointAboutVal.x); print(pointAboutVal.y);} |
结果为:
123456 | 3467891121 |
调用非默认的超类构造函数
执行顺序如下:
- 初始化列表
- 超类的无参数构造函数
- 主类的无参数构造函数
初始化列表
重定向构造函数
有时构造函数的唯一目的是重定向
到同一个类中的另一个构造函数
。重定向构造函数
的主体是空的
,构造函数调用出现在冒号(:)之后。
123456789 | class { num x,y; Point(this.x, this.y); Point.alongX(num x) : this(x,0);} |
调用:
123 | var pointAlongX = Point.alongX(11);print(pointAlongX.x);print(pointAlongX.y); |
结果为:
12 | 110 |
常量构造函数
如果你想使你的类生成永远不会更改的对象
,则可以使这些对象成为编译时常量
。为此,请 定义 const构造函数
并 确保 所有实例变量
都是final
。
1234567 | class ImmutablePoint { static final ImmutablePoint origin = const ImmutablePoint(0, 0); final num x, y; const ImmutablePoint(this.x, this.y);} |
工厂建设者
在不需要总是创建新的实例的情况下可以使用 factory
关键字,例如下面这个例子表示:从缓存中返回对象的构造函数:
注意:工厂构造函数无权访问this。
123456789101112131415161718 | class { num x,y; Point.alongX(num x) : this(x,0); static final Map<String, Point> _cache = Map<String, Point>{}; factory Point.name(String name){ if (_cache.containsKey(name)) { return _cache[name]; }else { final point = Point.alongX(9); _cache[name] = point; return point; } }} |
调用:
1234567 | var pointName = Point.name('Alice');var pointName2 = Point.name('Alice');var pointName3 = Point.name('Bob');print(pointName.hashCode);print(pointName2.hashCode);print(pointName3.hashCode);assert(pointName == pointName2); |
结果:
123 | 477440163477440163461006651 |
使用class成员
- 使用
.
来引用实例变量或方法
123 | var pointOrigin = Point(34,67);print(pointOrigin.x);print(pointOrigin.y); |
- 当
最左边
的操作数为null
时,使用?.
而不是.
避免异常:
123456789 | // 如果pointAbout不为空,那么设置x = 4;pointAbout?.x = 4;print('pointAbout.x is ${pointAbout.x}');// 结果:pointAbout.x is 4pointAbout = null;pointAbout?.x = 4;//print('pointAbout.x is ${pointAbout.x}');// 结果:抛出异常 |
使用构造函数
一般情况
1234567 | var pointOrigin = Point(34,67);print(pointOrigin.x);print(pointOrigin.y);var pointAbout = Point.origin();print(pointAbout.x);print(pointAbout.y); |
注意:在Dart2中,new
是可选关键字。
常量构造函数
有些类提供常量构造函数
。要使用常量构造函数创建编译时常量,请将const
关键字放在构造函数
名称之前
:
1 | var p = const ImmutablePoint(2, 2); |
如果构造2个相同
的编译时常量
,那么结果是:产生同一个
规范的实例:
1234 | var a = const ImmutablePoint(1, 1);var b = const ImmutablePoint(1, 1);assert(identical(a, b)); // They are the same instance! |
在常量上下文中,您可以省略
const构造函数或文字之前的内容。例如,查看此代码,该代码创建一个const映射:
1234 | const pointAndLine = const { 'point': const [const ImmutablePoint(0, 0)], 'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],}; |
那么,除了第一次出现的const
外其他所有的const
关键字都可以省略。简写为:
12345 | // Only one const, which establishes the constant context.const pointAndLine = { 'point': [ImmutablePoint(0, 0)], 'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],}; |
如果常量构造函数在常量上下文之外
并且在没有const
的情况下调用,则会创建一个非
常量对象:
1234 | var a = const ImmutablePoint(1, 1); // Creates a constantvar b = ImmutablePoint(1, 1); // Does NOT create a constantassert(!identical(a, b)); // NOT the same instance! |
获取对象的类型
要在运行时获取对象的类型,可以使用Object
的runtimeType
属性,该属性返回
一个Type对象
。
12 | var pointAboutVal = Point.withVal(11, 21);print('the type of pointAboutVal is ${pointAboutVal.runtimeType}'); |
结果为:
1 | the type of pointAboutVal is Point |
实例变量
以下是 声明 实例变量的方法:
12345 | class { num x; // Declare instance variable x, initially null. num y; // Declare y, initially null. num z = 0; // Declare z, initially 0.} |
所有未初始化
的实例变量都具有该值null
。
所有实例变量都生成一个隐式getter方法
。非final实例变量也会生成隐式setter方法
。有关详细信息,请参阅Getters和setter。
1234567891011 | class Point { num x; num y;}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.} |
如果初始化声明它的实例变量(而不是构造函数或方法),则在创建实例时设置该值,该实例在构造函数及其初始化列表执行之前。
方法
方法是为对象提供行为的函数。
实例方法
对象的实例方法可以访问实例变量和this。
getters and setters
抽象方法
实例、getter和setter方法可以是抽象的,定义一个接口,但将其实现留给其他类。抽象方法只能存在于抽象类中。
1234567891011 | abstract class Doer {大专栏 Dart-Tour2-类"line"> // 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
修饰符定义抽象类 - 无法实例化的类
。抽象类对于定义接口非常有用,通常还有一些实现。如果希望抽象类看起来是可实例化
的,请定义工厂构造函数
。
12345 | abstract class Doer { // Define instance variables and methods... void doSomething(); // Define an abstract method.} |
Dart
与 Java
关于 抽象类
的区别:
Dart
:由abstract
关键字修饰的类就是抽象类,由关键字
决定。Java
:只要一个类中有未实现的方法,这个方法就是抽象类,由方法
决定。
隐式接口(interfaces)
每个类隐式的定义了一个接口,含有类的所有实例和它实现的所有接口。如果你想创建一个支持类 B 的 API 的类 A,但又不想继承类 B ,那么,类 A 应该实现类 B 的接口。
一个类实现一个或更多接口通过用 implements 子句声明,然后提供 API 接口要求。例如
12345678910111213141516171819202122232425 | // A person. The implicit interface contains greet().class Person { // In the interface, but visible only in this library. final _name; // Not in the interface, since this is a constructor. Person(this._name); // In the interface. String greet(String who) => 'Hello, $who. I am $_name.';}// An implementation of the Person interface.class Impostor implements Person { get _name => ''; String greet(String who) => 'Hi $who. Do you know who I am?';}String greetBob(Person person) => person.greet('Bob');void main() { print(greetBob(Person('Kathy'))); print(greetBob(Impostor()));} |
这里是具体说明一个类实现多个接口的例子:
123 | class Point implements Comparable, Location { // ... } |
特点:
这里的interface
接口和java不同
在于:不需要
定义interface
,任何class
都可以被implements
继承类(extends)
同Java
重写成员(@override)
同Java
注意:要在类型安全
的代码中缩小
方法参数 或 实例变量 的 类型
,可以使用covariant
关键字。
例子:
123456789 | class Animal { void chase(Animal x) { ... }}class Mouse extends Animal { ... }class Cat extends Animal { void chase(covariant Mouse x) { ... }} |
虽然此示例显示covariant
在子类型
中使用,但covariant
关键字可以放在超类
或子类方法
中。通常,超类方法
是放置它的最佳位置
。该covariant
关键字适用于单个参数
,并且在setter和字段上也受支持。
重写操作符
您可以覆盖下表中显示的运算符。
说明:表达e1 != e2
是!(e1 == e2)
的语法糖。
一个覆盖+
和-
运算符的类的示例:
123456789101112131415161718 | class Vector{ final 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); }final v = Vector(2,2);print('x is ${v.x}, y is ${v.y}');final w = Vector(4,5);print('x is ${w.x}, y is ${w.y}');var vw = v + w;print('x is ${vw.x}, y is ${vw.y}'); |
结果为:
123 | x is 2, y is 2x is 4, y is 5x is 6, y is 7 |
noSuchMethod()
可以通过重写noSuchMethod()可以发现或者对在代码中尝试使用不存在的方法
或者实例变量
作出反应。
123456789 | class A { // Unless you override noSuchMethod, using a // non-existent member results in a NoSuchMethodError. void noSuchMethod(Invocation invocation) { print('You tried to use a non-existent member: ' + '${invocation.memberName}'); }} |
你不能调用未实现的方法,除非以下一个是真实的:
接收器具有静态类型dynamic。
接收器有一个静态类型,它定义了未实现的方法(抽象是OK),接收器的动态类型的实现与类noSuchMethod() 中的实现不同Object。
枚举类型
枚举类型(通常称为枚举或枚举)是一种特殊类,用于表示固定数量
的常量值
。
使用enum
关键字声明枚举类型:
1 | enum Color { red, green, black } |
枚举中的每个值都有一个index
,从0
开始。如:
1 | assert(Color.black.index == 2); |
要获取枚举中所有值的列表
,请使用枚举values
常量。如:
1234 | List<Color> colors = Color.values;for (var x in colors) { print('当前的索引为: ${x.index} ,对应着的值为:${x}');} |
结果为:
123 | 当前的索引为: 0 ,对应着的值为:Color.red当前的索引为: 1 ,对应着的值为:Color.blue当前的索引为: 2 ,对应着的值为:Color.black |
switch
用法
12345678910111213 | void doSwitch() { var colorSingel = Color.black; switch (colorSingel) { case Color.black: break; case Color.blue: break; case Color.red: break; default: print(''); }} |
向类添加功能:mixin
mixins
是一种在多个类层次结构
中重用类代码
的方法。可以简单的理解为: mixin修饰的类
中定义了多个类
中公用
的变量
和方法
。
mix
是混合的意思
;in
是在……里面
。那么mixin
可以理解为:可以混合在指定类里面
的公用代码块
。用mixin
关键字代替class
关键字来实现一个可扩展的Object类
,且无需声明构造函数 :
mixin
本身可以
是抽象
的,可以定义各种方法属性,也可以是抽象的,等后续类去实现。
mixin使用
要使用 mixin
,请使用with
关键字后跟一个或多个
mixin关键字修饰的类。以下示例显示了使用有mixins
修饰的类:
1234567891011121314151617181920 | mixin TestMixin { void test() { print('test'); } int testInt = 1; void test2();}class Test with TestMixin { test2() { print('test2'); }}void main() { Test().test(); // test print(Test().testInt); // 1 Test().test2(); // test2} |
注意:mixin
是 Dart 2.1
中引入的对关键字的支持。
mixin与on关键字:
要指定只有
某些类型可以使用mixin
——使用on
来指定所需的超类
,这样你的mixin
可以调用一个它自身没有定义
的方法:
1234567891011121314151617181920212223 | class BaseObject { void method() { print('call method'); }}mixin TestMixin on BaseObject{ void test() { print('test'); } int testInt = 1; void test2() { method(); }}class Test extends BaseObject with TestMixin {}void main() { Test().test(); // test print(Test().testInt); // 1 Test().test2(); // call method} |
当使用on
关键字后,则表示该mixin修饰的类
只能在那个类的子类
使用了。
多个mixin
123456789101112131415161718192021222324252627282930313233343536373839404142 | mixin TestMixin1 on BaseContext{ var intValue = 1; void mix1() => print('this is from textMixin1'); void printAnother() => sayHello();}mixin TestMixin2 on BaseContext{ var intValue = 2; void mix2() => print('this is from testMixin2'); void printBye() => sayGoodbye();}class BaseContext{ void sayHello() => print("BaseContext say hello"); void sayGoodbye() => print('BaseContext say byebye');}class TestClass extends BaseContext with TestMixin1,TestMixin2{ @override void mix2() { // TODO: implement mix2 super.mix2(); print('this msg is from TestClass!'); }}void main(){ TestClass().mix2(); // this is from testMixin2 this msg is from TestClass! TestClass().mix1(); // this is from textMixin1 TestClass().sayGoodbye(); // BaseContext say byebye TestClass().printAnother(); // BaseContext say hello TestClass().printBye(); // BaseContext say byebye print(TestClass().intValue); // 2} |
在TestMixin1
与TestMixin2
中都声明了一个intValue
变量,但是值不同。在class TestClass extends BaseContext with TestMixin1,TestMixin2
的顺序中,得到的结果是:
1 | 2 |
即:TestMixin2
中初始化的值。
那么改变一下with
的顺序呢?class TestClass extends BaseContext with TestMixin2,TestMixin1
,结果是:
1 | 1 |
由此可见:取值的是最后一个with
的类中的值。
类变量和方法
使用static
关键字实现类范围
的变量和方法。
静态变量
静态变量(类变量)
对于类范围
的状态
和常量
很有用:
12345678 | class Queue { static const initialCapacity = 16; // ···}void main() { assert(Queue.initialCapacity == 16);} |
注意:
- 静态变量在
使用之前
不会初始化。 - 遵循
样式指南
建议,优先使用lowerCamelCase
作为常量名称
。
静态方法
12345678910 | class Point { num x, 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); }} |
注意:
- 对于常用或广泛使用的实用程序和功能,请考虑使用
顶级函数
而不是静态方法。
您可以使用静态方法作为编译时常量。例如,您可以将静态方法作为参数传递给常量构造函数。