类
基本概念
- Object类:Dart是一个面向对象编程语言,每个对象都是一个类的实例,所有的类都继承于Object。
//定义一个类class Person {}复制代码
- 类的继承性:每个类(Object 除外) 都只有一个超类(父类),一个类的代码可以被其他多个类继承来使用。
- 创建新对象:使用new 关键字和构造函数来,构造函数名字可以类名;
- 对象的成员:包括方法和数据 (函数和示例变量),使用点(.)来引用对象的变量或者方法,使用 ?. 来替代 . 来访问对象可以避免当左边对象为 null 时候抛出异常.。
// If p is non-null, set its y value to 4.p?.y = 4;复制代码
- 获取实例对象的类型:可以使用 Object 的 runtimeType 属性来判断实例的类型,该属性返回一个 Type对象。
class Student {}main() { var a = new Student(); print(a.runtimeType); //Student}复制代码
类变量和函数
静态变量
- 静态变量:static关键字修饰的类级别的变量,直接使用"类名.x"来调用;
- 静态变量使用lowerCamelCase 来命名常量。
- 静态变量在定义的时候可以不初始化,此时默认为null。
class Person { static String name; static const sex = "男";}main() { print(Person.name); print(Person.sex);}复制代码
静态函数
static 修饰的函数即静态函数,静态函数不再类实例上执行, 所以无法访问 this。
注意: 对于通用的或者经常使用的静态函数,考虑 使用顶级方法而不是静态函数。
class Person { static void printInfo(){ print("hello world"); }}main() { Person.printInfo();}复制代码
实例的私有属性
和Java不同的是,Dart没有public、protected、和private关键字。如果一个标识符以 (_) 开头,则该标识符在库内是私有的。
实例变量
- 类的实例变量,所有没有初始化的变量值都是null。
- 如果你在实例变量定义的时候初始化该变量(不是 在构造函数或者其他方法中初始化),改值是在实例创建的时候 初始化的,也就是在构造函数和初始化参数列 表执行之前。
- 每个实例变量都会自动生成一个getter和setter方法(隐含的)。
class Person { //类的实例变量 String name; String sex; int age;}main() { var p = new Person(); p.age = 24; // Use the setter method for x. print(p.age.toString());}复制代码
this关键字
this 关键字指当前的实例,只有当名字冲突的时候才使用 this,Dart代码风格样式推荐忽略 this。
class Point { num x; num y; Point(num x, num y) { // There's a better way to do this, stay tuned. this.x = x; this.y = y; }}复制代码
构造函数赋值实例变量简化写法:
class Point { num x; num y; //由于把构造函数参数赋值给实例变量的场景太常见了, Dart 提供了一个语法糖来简化这个操作: // Syntactic sugar for setting x and y // before the constructor body runs. Point(this.x, this.y);}复制代码
函数
函数是类中定义的方法,是类对象的行为。
实例函数
实例函数是类中定义的普通方法,类实例对象可以调用的方法,对象的实例函数可以访问this;
class Person { String name; printName() { print(this.name); }}main() { new Person() ..name = 'ethan' ..printName();}复制代码
getter and setter
getter 和 setter 是用来设置和访问对象属性的特殊函数,定义时需要在变量名前加get或set标识。
特点:
- 每个实例变量默认隐含一个getter,如果变量不是final类型,则还会隐含一个 setter。
- 可以通过getter 和 setter 来创建新的属性,使用 get 和 set 关键字定义 getter和setter;
- 如果定义一个带get标识的变量,则实例对象可以直接通过“对象.x”来获取变量,但是这个变量没有setter,此时必须也实现setter,才可以通过“对象.x=n”来改变此变量;
class Person { String name; String prefix; Person(this.name); String get description => prefix + " " + name; set description(String prefix) => this.prefix = prefix;}main() { var p = new Person("ethan"); p.description = 'x'; print(p.description);}复制代码
抽象函数
抽象函数是没有函数体的函数。
实例函数、 getter、和 setter 函数可以为抽象函数, 抽象函数是只定义函数接口但是没有实现的函数,由子类来 实现该函数。
示例:
//抽象类abstract class Doer { //抽象方法 void doSomething();}//子类继承抽象类,必须实现抽象方法class EffectiveDoer extends Doer { void doSomething() { print("hello"); }}复制代码
构造函数
概述
定义一个和类名字一样的方法就定义了一个构造函数。 构造函数用来生成一个对象的新实例;
- 默认构造函数:与类同名,配合new关键字创建实例。如果没有定义构造函数,则会有个默认构造函数。 默认构造函数没有参数,并且会调用超类的 没有参数的构造函数。
- 构造函数不会继承:子类不会继承超类的构造函数。 子类如果没有定义构造函数,则只有一个默认构造函数 (没有名字没有参数)。
- 命名构造函数:定义格式为“类名.函数名(...)”,创建对象实例时也使用“类名.函数名(...)”.使用命名构造函数可以为一个类实现多个构造函数, 或者使用命名构造函数来更清晰的表明你的意图;
class Person { //类的实例变量 String name; int age; //默认构造函数 Person(); // Person(this.name, this.age); //命名构造函数 Person.fromJson(Map jsonMap) { name = jsonMap['name']; age = jsonMap['age']; }}main() { var p = Person.fromJson({ 'name': "ethan", "age": 11}); print(p.name); print(p.age);}复制代码
调用超类构造函数
默认调用方式
子类的构造函数会自动调用超类的 无名无参数的默认构造函数。 超类的构造函数在子类构造函数体开始执行的位置调用。 如果提供了一个 initializer list(初始化参数列表) ,则初始化参数列表在超类构造函数执行之前执行。
下面是构造函数执行顺序:
1. initializer list(初始化参数列表)2. superclass’s no-arg constructor(超类的无名构造函数)3. main class’s no-arg constructor(主类的无名构造函数)复制代码
超类是无参数构造函数
如果超类没有无名无参数构造函数, 则你需要手工的调用超类的其他构造函数。 在构造函数参数后使用冒号 (:) + () 可以调用 超类构造函数。
class Person { String firstName; Person.fromJson(Map data) { print('in Person'); }}class Employee extends Person { // Person does not have a default constructor; // you must call super.fromJson(data). Employee.fromJson(Map data) : super.fromJson(data) { print('in Employee'); }}main() { var emp = new Employee.fromJson({}); //in Person //in Employee}复制代码
注意:
- 如果在构造函数的初始化列表中使用 super(),需要把它放到最后。
- 调用超类构造函数的参数无法访问 this。 例如,参数可以为静态函数但是不能是实例函数。
构造函数初始化列表
在构造函数体执行之前除了可以调用超类构造函数之外,还可以 初始化实例参数。 使用逗号分隔初始化表达式。
警告: 初始化表达式等号右边的部分不能访问 this。
class Point { num x; num y; Point(this.x, this.y); // Initializer list sets instance variables before // the constructor body runs. Point.fromJson(Map jsonMap) : x = jsonMap['x'], y = jsonMap['y'] { print('In Point.fromJson(): ($x, $y)'); }}复制代码
重定向构造函数
有时候一个构造函数会调动类中的其他构造函数。 一个重定向构造函数是没有代码的,在构造函数声明后,使用 冒号调用其他构造函数。
class Point { num x; num y; // The main constructor for this class. Point(this.x, this.y); // Delegates to the main constructor. Point.alongXAxis(num x) : this(x, 0);}复制代码
常量构造函数
如果你的类提供一个状态不变的对象,你可以把这些对象 定义为编译时常量。要实现这个功能,需要定义一个 const 构造函数, 并且声明所有类的变量为 final。
- 使用常量构造函数 可以创建编译时常量,要使用常量构造函数只需要用const替代new 即可;
- 两个使用常量构造函数创建,并且创建时的内容一样常量对象,其实是同一个对象;
class ImmutablePoint { final num x; final num y; const ImmutablePoint(this.x, this.y); static final ImmutablePoint origin = const ImmutablePoint(0, 0);}var a = const ImmutablePoint(1, 1);var b = const ImmutablePoint(1, 1);assert(identical(a, b)); // They are the same instance!复制代码
工厂方法构造函数
如果一个构造函数并不总是返回一个新的对象,则使用 factory 来定义 这个构造函数。例如,一个工厂构造函数 可能从缓存中获取一个实例并返回,或者 返回一个子类型的实例。
工厂构造函数无法访问 this。
示例:定义工厂构造函数。
class Logger { final String name; bool flag = false; //_cache: 静态私有变量缓存实例对象 static final Map_cache = {}; factory Logger(String name) { if (_cache.containsKey(name)) { //获取缓存 return _cache[name]; } else { //缓存 final logger = new Logger._internal(name); _cache[name] = logger; return logger; } } //命名构造函数 Logger._internal(this.name); //改变flag的值 void changeFlag() { flag = !flag; }}main() { var logger = new Logger('LOG'); print("flag = " + logger.flag.toString()); logger.changeFlag(); //缓存的logger对象 print("flag = " + new Logger('LOG').flag.toString()); //新的logger对象 print("flag = " + new Logger('X').flag.toString());}复制代码
//执行结果:flag = falseflag = trueflag = false复制代码
可覆写的操作符
下表中的操作符可以被覆写:
< + | []> / ^ []=<= ~/ & ~>= * << ==– % >> 复制代码
覆写示例
对象加减
覆写+-法:
class Vector { final int x; final int y; const Vector(this.x, this.y); /// Overrides + (a + b). Vector operator +(Vector v) { return new Vector(x + v.x, y + v.y); } /// Overrides - (a - b). Vector operator -(Vector v) { return new Vector(x - v.x, y - v.y); }}main() { final v = new Vector(2, 3); final w = new Vector(2, 2); // v == (2, 3) assert(v.x == 2 && v.y == 3); // v + w == (4, 5) assert((v + w).x == 4 && (v + w).y == 5); // v - w == (0, 1) assert((v - w).x == 0 && (v - w).y == 1);}复制代码
覆写==
Dart 中的每个对象都有一个整数 hash 码,这样每个对象都 可以当做 map 的 key 来用。但是,你可以覆写 hashCode getter 来自定义 hash 码的实现,如果你这样做了,你也需要 同时覆写 == 操作符。相等的对象(使用 == 比较)的 hash 码应该一样。hash code 码并不要求是唯一的, 但是应该具有良好的分布形态。
如果你覆写了 == ,则还应该覆写对象的 hashCode getter 函数。
class Person { final String firstName, lastName; Person(this.firstName, this.lastName); // Override hashCode using strategy from Effective Java, // Chapter 11. int get hashCode { int result = 17; result = 37 * result + firstName.hashCode; result = 37 * result + lastName.hashCode; return result; } // You should generally implement operator == if you // override hashCode. bool operator ==(other) { if (other is! Person) return false; Person person = other; return (person.firstName == firstName && person.lastName == lastName); }}main() { var p1 = new Person('bob', 'smith'); var p2 = new Person('bob', 'smith'); var p3 = 'not a person'; assert(p1.hashCode == p2.hashCode); assert(p1 == p2); assert(p1 != p3);}复制代码
抽象类
使用“abstract”关键字修饰的类为抽象类,不能实例化,抽象类的方法可以实现也可以不实现;
- 如果你希望你的抽象类 是可示例化的,则定义一个 工厂构造函数。
- 抽象方法即没有方法体的方法,普通类不能有抽象方法;
//抽象类abstract class Person { void eat();}//子类实现抽象类的方法class Student extends Person { void eat(){ print("i can eat"); }}main() { new Student().eat();}复制代码
类的隐式接口
- 每个类都隐式的定义了一个包含所有实例成员的接口, 并且这个类实现了这个接口。
- 一个类可以通过 implements 关键字来实现一个或者多个接口, 并实现每个接口定义的 API。
如果你想 创建类 A 来支持 类 B 的 api,而不想继承 B 的实现, 则类 A 应该实现 B 的接口。例如:
class Person { final _name; Person(this._name); String greet(who) => 'hello, $who. I am $_name.';}class Animal { void eat() {}}//实现多个接口class StudentImpl implements Person, Animal { final _name = ""; String greet(who) => 'Hi $who. Do you know who I am?'; void eat() { print("i can eat"); }}greetBob(Person person) => person.greet('bob');main() { print(greetBob(new Person('kathy'))); print(greetBob(new StudentImpl()));}复制代码
类继承
- 子类通过extends来继承父类;
- 通过super来调用父类方法;
- 可以使用 @override注解一个函数,来表明你的函数是想覆写超类的一个函数;
继承示例如下:
class Animal { void eat() { print("i can eat"); } void greet() { print("hello everyone !"); }}class Person extends Animal { void printFeature() { super.eat(); } //重写父类函数 @override void greet() { print("hello i am person"); }}main() { new Person().greet();}复制代码
枚举类型
枚举类型通常称之为 enumerations 或者 enums, 是一种特殊的类,用来表现一个固定 数目的常量。
- 使用 enum 关键字来定义枚举类型;
- 枚举类型中的每个值都有一个 index getter 函数, 该函数返回该值在枚举类型定义中的位置(从 0 开始)。
- 枚举的 values 常量可以返回 所有的枚举值。
- 可以在 switch 语句 中使用枚举。 如果在 switch (e) 中的 e 的类型为枚举类, 如果你没有处理所有该枚举类型的值的话,则会抛出一个警告;
- 枚举类无法继承枚举类型、无法使用 mix in、无法实现一个枚举类型;
- 枚举类无法显示的初始化一个枚举类型;
enum Color { red, green, blue }judgeType(Color color) { switch (color) { case Color.blue: print("yes i am blue"); break; default: print("no"); break; }}main() { print(Color.green.index); // 1 print(Color.values); //枚举值列表[Color.red, Color.green, Color.blue] //枚举应用switch judgeType(Color.red); judgeType(Color.blue);}复制代码
mixins重用类
mixins可以实现类似继承多个类功能的效果,重用多个类的属性。
- mixins使用with关键字后跟需要的类;
- Dart 1.13之前,定义一个类继承Object,该类没有构造函数,不能调用 super ,则该类就是一个 mixin。
- Dart 1.13+,mixins可以继承其他类,不再限制为继承Object,可以调用 super()。
super mixins” 还 无法在 dart2js 中使用 并且需要在 dartanalyzer 中使用 --supermixin 参数。
class Animal { final name = "ethan";}class Musical { void printFeature() { print("i can song"); }}class Person { String iq;}class Student extends Person with Animal, Musical { void printInfo() { print("name=" + name); super.printFeature(); printFeature(); super.iq = "100"; print("IQ=" + iq); }}main() { new Student().printInfo();}复制代码