类
Dart是一种基于Mixin继承机制的面向对象的语言。除了NULL
之外所有的类都是Object
类的子类。Dart是一个单继承的语言,每个类只有一个超类。
可以通过Mixin
来实现一个类使用多个mixin的方法实现,通过方法扩展在不更改类不创建子类的情况下追加方法功能。
Dart遵循《风格推荐指南》,所有的类名都是以驼峰形式命名。
类成员的使用
对象的成员由函数和数据(即方法和实例变量)组成。方法的调用要通过对象来完成。
构造函数
构造函数可以通过 类名 或者 类名.标识符 的形势来调用,比如:
var point = Point(1,1);
var point2 = Point.formatJson({'x':1,'y':2})
Dart 中
new
关键字可以省略
如果在定义构造函数时加上了const
则表示声明了一个常量构造函数,那么使用时,使用const则表示声明了一个编译时常量,两个相同的常量构造函数和相同的参数声明的对象是相同的。
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
print(identical(a, b)); //true
方法和变量的调用
- 通过(
.
)来调用对象的变量和方法 - 通过(
?.
)来避免对象实例为空的情况下调用变量和方法时发生异常 - 静态的变量和方法,直接通过
类.变量名
/类.方法名
来调用
获取对象类型
通过Object
类的属性runtimeType
来获取对象的类型
类成员的定义
实例变量的定义
有以下几种方式定义类的实例变量
class TestClass {
// 定义一个可空的变量,变量初始值为null
double? canBeNullVariable;
// 定义一个可空的变量,显示声明变量初始值为null
double? canBeNullVariable2 = null;
// 定义一个不可空变量,必须显示的声明变量初始值为非null的字面量
double cantBeNullVariable = 1.0;
// 使用late修饰一个不可空的变量,可以不赋初始值,在使用前赋值
late double cantBeNullVariable2;
// 使用final修饰的不可变量,必须赋初始值
final double cantBeNullVariable3 = 2.0;
// 使用late修饰的不可变量,可以不赋初始值,在使用前赋值,只能赋值一次
late final double cantBeNullVariable4;
}
所有的实例变量都会隐式的声明一个Getter方法,所有非不可变量的实例变量或者时late final的不可变量都会隐式的声明Setter方法。late final的set方法只能调用一次,再次调用的时候抛出异常。
区别于Java,在不添加late修饰时,全局变量定义引用其他变量是不正确的,在编译时会报错。
class TestClass {
double a = 1;
double b = 2;
// 这样定义是不正确的,编译器会报错
//double c = a + b;
// 必须加上 late
late double c = a + b;
// 只有在late修饰的情况下, 才可以使用this
late double d = this.a + this.c;
}
构造函数的定义
通常构造函数是使用与类名同样名称的函数来定义。例如:
class Point {
double x = 0;
double y = 0;
Point(double x, double y) {
// 设置实例的初始值
this.x = x;
this.y = y;
}
}
Dart提供了特殊的语法糖来在构造函数时给非空变量和不可变量赋初始值或默认值;也就是说可以不在定义的时候赋值。
class Point {
double x;
final double y;
late double z;
Point(this.x, this.y);
}
在构造函数的参数中通过 this 关键字指定非空变量或不可变量来赋值
- 每个类都默认有一个无参的构造函数
- 子类不会继承父类的构造函数,没有定义子类的构造函数时,子类只有默认的无参构造函数
- Dart支持命名式构造函数
class Point{ double x; double y; Point.origin(): x = 10, y = 20; } // 调用命名构造函数 var p = Point.origin();
命名构造函数也不可以继承,子类如果要声明一个跟父类同名的命名构造函数,必须显示的声明。
class Person{ String? name; Person.initName(String name): this.name = name; } class Employee extends Person{ Employee(super.name):super.initName(); Employee.initName(super.name):super.initName(); }
- 上面例子中的
super.name
属于超类参数,可以避免重复参数赋值。使用super
关键字。在超类的构造函数中就不需要再次使用相应的参数了 - 初始化参数,在构造函数体执行之前还可以初始化参数,使用
:
进行初始化,每个参数之间使用,
隔开。
Point.fromJson(MapM<String, double> json): x = json['x']!, y = json['y']!{ print(`x is ${this.x} & y is ${this.y}`); }
- 重定向构造函数,使用
:
和this
来指向重定向的构造函数
class Point { double x = 0; double y = 0; Point(this.x, this.y); Point.onlyXAxios(double x): this(x,0); }
- 常量构造函数,在构造函数前加上 const,并确保数据都是final的,可以定义一个编译时的常量对象。
- 使用
factory
关键字标识类的构造函数将会令该构造函数变为工厂构造函数,这将意味着使用该构造函数构造类的实例时并非总是会返回新的实例对象。
工厂构造函数中,不能使用
this
关键字 - 上面例子中的
方法
操作符方法
除了定义普通的方法外,Dart中的运算符也是特殊名称的实例方法。以下运算符是可以被你当成方法使用:
< + | >>> > / ^ [] <= ~/ & []= >= * << ~% >> ==
使用 operator
标识来进行标记来重写操作符。下面是重写 + 和 - 操作符的例子
class Vector {
final int 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);
@override
bool operator ==(Object other) =>
other is Vector && x == other.x && y == other.y;
@override
int get hashCode => Object.hash(x, y);
@override
String toString() => 'the x is $x , the y is $y';
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
print(v + w ); //the x is 4 , the y is 5
print(v - w ); //the x is 0 , the y is 1
}
Getter 和 Setter 方法
默认情况下,Dart会为每个变量隐式的创建Getter和Setter方法。但如果显示的声明了get 或set方法后,Dart将不会自动创建Getter或Setter方法。
class TestGetAndSet {
double a = 1;
double b = 2;
// 可以使用this关键字
double get c => this.a + this.b;
set c(double x)=>this.a = this.b + this.x;
// 不能调用其set方法
double get d => this.a + this.c;
// 不能调用其 get方法
set e(double x)=> this.a = this.d + x;
}
void main(){
var test = TestGetAndSet();
print(test.a); // 1
print(test.b); // 2
print(test.c); // 3
print(test.d); // 4
// e 没有get方法,因此不能调用test.e
// print(test.e);
test.c = 10;
print(test.a); // 12
print(test.c); // 14
test.e = 10;
print(test.a); // 36
print(test.c); // 38
print(test.d); // 74
// d 没有set方法,不能赋值
//test.d = 10;
}
像自增(
++
)这样的操作符不管是否定义了Getter
方法都会正确地执行。为了避免一些不必要的异常情况,运算符只会调用Getter
一次,然后将其值存储在一个临时变量中。
抽象方法
没有方法体的函数就是抽象方法,抽象方法必须在抽象类中。
抽象类
使用关键字 abstract 标识类可以让该类成为 抽象类,抽象类将无法被实例化。抽象类常用于声明接口方法、有时也会有具体的方法实现。
抽象类常常会包含抽象方法。
隐式接口
一个类可以通过关键字 implements
来实现一个或多个接口并实现每个接口定义的除了构造函数之外的所有函数(包括Getter & Setter)。
class Person{
String name;
Person(this.name);
String sayHello(){
return 'hello guys, I am $name';
}
}
class Employee {
//必须定义,get set方法也要重新
String name;
Employee.named(String name):this.name=name;
// 必须重写Person类中定义的所有方法,且不可以调用super来实现
String sayHello(){
return 'hello emplpyee, I am $name';
}
}
扩展类
使用 extends
关键字来创建一个子类,并可使用 super
关键字引用一个父类:
通过 @override
来标记重写一个类成员。
- 重写的方法返回类型必须要和超类方法的返回类型一致;
- 重写的方法的参数类型,参数个数必须和超类的方法的类型,个数一致;子类参数类型可以上父类方法类型的超类。
- 位置参数个数一定要一致
- 重写泛型方法必须也是泛型,重写非泛型方法必须也是非泛型
扩展方法
扩展方法可通过extension-on
的关键字来实现。
extension NumberParsing on String {
int parseInt(){
return int.parse(this);
}
}
使用时,需要先导入定义的文件。如果上述代码定义在test.dart
文件中。
import 'test.dart';
void main(){
print('42'.parseInt().runtimeType); //int
}
如果是一个动态类型,那么扩展方法将不可用
dynamic a = '123'
变量a将不能使用a.parseInt()
这一扩展方法,但是var a = '123'
通过类型推断可以推断出是String,则可以使用扩展方法。
扩展方法冲突
如果有多个扩展文件中定义了相同名称的扩展方法时,扩展方法则会不生效。
- 可使用
hide
进行屏蔽 - 可以指定使用扩展命名
- 如果文件有相同的扩展名,则可以使用别称。
/// test、test1、test2 都包含parseInt的扩展方法
/// test、test3 的扩展都为NumberParse
/// test2的扩展名为你NumberParse1
import 'test.dart';
import 'test2.dart';
import 'test3.dart' as alisName;
print(NumberParse('10').parseInt());
print(NumberParse1('10').parseInt());
print(alisName.NumberParse('10').parseInt());
Mixin添加功能
Mixin是一种在多重继承中复用某个类中代码的方法模式。可以理解中是一个将公共方法提取出来的工具类。
使用 with 关键字并在其后跟上 Mixin 类的名字来使用 Mixin 模式
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
实现一个Mixin类;请创建一个继承自 Object 且未声明构造函数的类。除非你想让该类与普通的类一样可以被正常地使用,否则请使用关键字 mixin 替代 class。
mixin 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');
}
}
}
可以使用关键字
on
来指定哪些类可以使用该Mixin
类,指定后,只有指定类和其子类可以使用这一Mixin类。
枚举
枚举类型是一种特殊的类型,用于定义一些固定数量的常量值。
所有的枚举都继承于
Enum
类。枚举类是封闭的,即不能被继承、被实现、被mixin 混入或显式被实例化。
声明枚举
通过关键字 enum
来声明枚举。
enum Color {red, yellow, green};
增强型枚举
Dart 中的枚举也支持定义字段、方法和常量构造,常量构造只能构造出已知数量的常量实例
可以使用与定义 类 类似的语句来定义增强的枚举,但是这样的定义有一些限制条件:
- 实例的字段必须是
final
,包括由mixin
混入的字段。 - 所有的实例化构造 必须以
const
修饰。 - 工厂构造 只能返回已知的一个枚举实例。
- 由于
Enum
已经自动进行了继承,所以枚举类不能再继承其他类。 - 不能重载
index
、hashCode
和比较操作符==
。 - 不能声明
values
字段,否则它将与枚举本身的静态values
getter
冲突。 - 在进行枚举定义时,所有的实例都需要首先进行声明,且至少要声明一个枚举实例。
使用枚举
- 枚举使用与使用静态变量一致
- 每个枚举都包含一个名为
index
成员变量的Getter
方法;索引从0开始。 - 想要获取全部的枚举值,可通过
values
来获取
异常
异常的定义
Dart 可以抛出和捕获异常,如果代码抛出的异常没有被捕获,程序就会被终止。与Java不一样的是,Dart的不需要在方法上声明抛出哪些异常,也就是说所有异常都是运行时异常。Dart提供了Exception
和Error
两种异常,你可以抛出/捕获他们以及他们的子类。
throw FormatException('Expected at least 1 section');
Dart也不限定一定要抛出Exception
和Error
的类型,事实上,可以抛出任意非null的类型。但是强烈不建议这么做。
throw 'Out of llamas!';
我们在定义抛出异常时,应该使用
Exception
及其子类,而避免使用Error
,Error
通常表示无法预料和捕获的错误,一旦出错终止程序将是最安全的操作。
异常的捕获
- 可以通过
on catch
和catch
来捕获异常,on catch
指定异常类型捕获,而catch
是捕获所有的异常信息
try {
// ···
} on Exception catch (e) {
print('Exception details:\n $e');
} catch (e, s) {
print('Exception details:\n $e');
print('Stack trace:\n $s');
}
catch的第一个参数表示异常的对象, 而第二个参数表示异常的堆栈信息
- 可以通过
rethrow
在catch中重新抛出异常。
void misbehave() {
try {
dynamic foo = true;
print(foo++); // Runtime error
} catch (e) {
print('misbehave() partially handled ${e.runtimeType}.');
rethrow; // Allow callers to see the exception.
}
}
调用misbehave的时候,仍然需要
catch
异常,否则程序会终止。
- 无论是否捕获异常,
finally
都会执行,如果捕获异常,则finally
在catch
之后执行,如果没有捕获异常,那么finally
执行完成后,抛出异常。
泛型
泛型是表示一个参数化的类型,使用指定的泛型类型可以更好的帮助代码生成,减少重复代码。
泛型的使用
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
使用泛型,就不需要为每个类型的值都定义一个Cache类,同时也避免定义Object类型再做强制类型转换。
- 集合字面量定义;List、Set 以及 Map 字面量也可以是参数化的。定义参数化的 List 只需在中括号前添加 <type>;定义参数化的 Map 只需要在大括号前添加 <keyType, valueType>
- 类型参数化构造函数 如:
var map = Map<String,String>()
;
泛型类型
Dart中的泛型是固化的,在运行时也能保持类型信息。这一点与java不同,java的泛型类型在运行时时擦除的,在运行时是不会保持类型信息的。
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
限制参数类型
有时使用泛型的时候,有时需要限制可作为参数的泛型范围,也就是参数必须是指定类型的子类,这时候可以使用 extends
关键字。
限制非空类型时,使用
T extends Object
,而非Object?
泛型方法
在方法中使用泛型function<T>
,可以有
- 返回值使用泛型
- 参数使用泛型
- 局部变量使用泛型
T func<T>(List<T> params){
T temp = params[0];
return temp;
}