作者 | 弗拉德
起源 | 弗拉德(公众号:fulade_me)
类
Dart是一种面向对象的语言,所有对象都是一个类的实例,而所有的类都继承自Object
类。每个除了Object
类之外的类都只有一个超类,一个类的代码能够在其它多个类继承中重复使用。
类的实例变量
上面是申明实例变量的示例:
class Point { double x; // 申明 double 变量 x 并初始化为 null。 double y; // 申明 double 变量 y 并初始化为 null double z = 0; // 申明 double 变量 z 并初始化为 0。}
所有未初始化的实例变量其值均为null
。
所有实例变量均会隐式地申明一个Getter
办法,非final
类型的实例变量还会隐式地申明一个Setter
办法
class Point { double x; double y;}void main() { var point = Point(); point.x = 4; // 应用 x 的 Setter 办法。 assert(point.x == 4); // 应用 x 的 Getter 办法。 assert(point.y == null); // 默认值为 null。}
如果你在申明一个实例变量的时候就将其初始化(而不是在构造函数或其它办法中),那么该实例变量的值就会在对象实例创立的时候被设置,该过程会在构造函数以及它的初始化器列表执行前。
拜访类的成员
对象的成员由函数和数据(即办法和实例变量)组成。办法的调用要通过对象来实现,这种形式能够拜访对象的函数和数据。
应用.
来拜访对象的实例变量或办法:
var p = Point(2, 2);// 获取 y 值assert(p.y == 2);// 调用变量 p 的 distanceTo() 办法。double distance = p.distanceTo(Point(4, 4));
应用 ?.
代替.
能够防止因为右边表达式为null
而导致的问题:
// If p is non-null, set a variable equal to its y value.var a = p?.y;
办法
办法是对象提供行为的函数。
对象的实例办法能够拜访实例变量和this
。上面的distanceTo()
办法就是一个实例办法的例子:
import 'dart:math';class Point { double x, y; Point(this.x, this.y); double distanceTo(Point other) { var dx = x - other.x; var dy = y - other.y; return sqrt(dx * dx + dy * dy); }}
重写类成员
子类能够重写父类的实例办法(包含操作符)、 Getter
以及 Setter
办法。你能够应用 @override
注解来示意你重写了一个成员:
class SmartTelevision extends Television { @override void turnOn() {...} // ···}
noSuchMethod 办法
如果调用了对象中不存在的办法或实例变量将会触发noSuchMethod
办法,你能够重写noSuchMethod
办法来追踪和记录这一行为:
class A { // 除非你重写 noSuchMethod,否则调用一个不存在的成员会导致 NoSuchMethodError。 @override void noSuchMethod(Invocation invocation) { print('你尝试应用一个不存在的成员:' + '${invocation.memberName}'); }}
你不能调用一个未实现的办法除非满足上面其中的一个条件:
- 接管方是动态的
dynamic
类型。 - 接管方具备动态类型,定义了未实现的办法,并且接管方的动静类型实现了
noSuchMethod
办法且具体的实现与Object
中的不同。
类的动态变量和静态方法
应用关键字static
能够申明类变量或类办法。
动态变量
动态变量(即类变量)罕用于申明类范畴内所属的状态变量和常量:
class Queue { static const initialCapacity = 16; // ···}void main() { assert(Queue.initialCapacity == 16);}
动态变量在其首次被应用的时候才被初始化。
静态方法
静态方法(即类办法)不能被一个类的实例拜访,同样地,静态方法内也不能够应用关键字this
:
import 'dart:math';class Point { double x, y; Point(this.x, this.y); static double distanceBetween(Point a, Point b) { var dx = a.x - b.x; var dy = a.y - b.y; return sqrt(dx * dx + dy * dy); }}void main() { var a = Point(2, 2); var b = Point(4, 4); var distance = Point.distanceBetween(a, b); assert(2.8 < distance && distance < 2.9); print(distance);}
应用构造函数构建对象
能够应用构造函数来创立一个对象。构造函数的命名形式能够为类名或类名.
标识符的模式。例如下述代码别离应用Point()
和Point.fromJson()
两种结构器来创立Point
对象:
var p1 = Point(2, 2);var p2 = Point.fromJson({'x': 1, 'y': 2});
以下代码具备雷同的成果,构造函数名后面的的new
关键字是可选的:
var p1 = new Point(2, 2);var p2 = new Point.fromJson({'x': 1, 'y': 2});
一些类提供了常量构造函数。应用常量构造函数,在构造函数名之前加 const
关键字,来创立编译时常量时:
var p = const ImmutablePoint(2, 2);
两个应用雷同构造函数雷同参数值结构的编译时常量是同一个对象:
var a = const ImmutablePoint(1, 1);var b = const ImmutablePoint(1, 1);assert(identical(a, b)); // 它们是同一个实例 (They are the same instance!)
依据应用常量上下文的场景,你能够省略掉构造函数或字面量前的const
关键字。例如上面的例子中咱们创立了一个常量Map
:
// 这里有很多 const 关键字const pointAndLine = const { 'point': const [const ImmutablePoint(0, 0)], 'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],};
依据上下文,你能够只保留第一个const
关键字,其余的全副省略:
// 只须要一个 const 关键字,其它的则会隐式地依据上下文进行关联。const pointAndLine = { 'point': [ImmutablePoint(0, 0)], 'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],};
然而如果无奈依据上下文判断是否能够省略const
,则不能省略掉 const
关键字,否则将会创立一个非常量对象 例如:
var a = const ImmutablePoint(1, 1); // 创立一个常量 (Creates a constant)var b = ImmutablePoint(1, 1); // 不会创立一个常量assert(!identical(a, b)); // 这两变量并不相同
获取对象的类型
能够应用Object
对象的runtimeType
属性在运行时获取一个对象的类型,该对象类型是Type
的实例。
var = Point(2, 2);print('The type of a is ${a.runtimeType}');
构造函数
申明一个与类名一样的函数即可申明一个构造函数(对于命名式构造函数 还能够增加额定的标识符)。大部分的构造函数模式是生成式构造函数,其用于创立一个类的实例:
class Point { double x, y; Point(double x, double y) { // 还会有更好的形式来实现此逻辑,敬请期待。 this.x = x; this.y = y; }}
应用 this
关键字援用以后实例。
对于大多数编程语言来说在构造函数中为实例变量赋值的过程都是相似的,而Dart
则提供了一种非凡的语法糖来简化该步骤:
class Point { double x, y; // 在构造函数体执行前用于设置 x 和 y 的语法糖。 Point(this.x, this.y);}
默认构造函数
如果你没有申明构造函数,那么Dart
会主动生成一个无参数的构造函数并且该构造函数会调用其父类的无参数构造方法。
构造函数不被继承
子类不会继承父类的构造函数,如果子类没有申明构造函数,那么只会有一个默认无参数的构造函数。
命名式构造函数
能够为一个类申明多个命名式构造函数来表白更明确的用意:
class Point { double x, y; Point(this.x, this.y); // 命名式构造函数 Point.origin() { x = 0; y = 0; }}
构造函数是不能被继承的,这将意味着子类不能继承父类的命名式构造函数,如果你想在子类中提供一个与父类命名构造函数名字一样的命名构造函数,则须要在子类中显式地申明。
调用父类非默认构造函数
默认状况下,子类的构造函数会调用父类的匿名无参数构造方法,并且该调用会在子类构造函数的函数体代码执行前,如果子类构造函数还有一个初始化列表,那么该初始化列表会在调用父类的该构造函数之前被执行,总的来说,这三者的调用程序如下:
- 初始化列表
- 父类的无参数构造函数
- 以后类的构造函数
如果父类没有匿名无参数构造函数,那么子类必须调用父类的其中一个构造函数,为子类的构造函数指定一个父类的构造函数只需在构造函数体前应用:
指定。
因为参数会在子类构造函数被执行前传递给父类的构造函数,因而该参数也能够是一个表达式,比方一个函数:
class Employee extends Person { Employee() : super.fromJson(defaultData); // ···}
初始化列表
除了调用父类构造函数之外,还能够在构造函数体执行之前初始化实例变量。每个实例变量之间应用逗号分隔。
// 应用初始化列表在构造函数体执行前设置实例变量。Point.fromJson(Map<String, double> json) : x = json['x'], y = json['y'] { print('In Point.fromJson(): ($x, $y)');}
在开发模式下,你能够在初始化列表中应用assert
来验证输出数据:
Point.withAssert(this.x, this.y) : assert(x >= 0) { print('In Point.withAssert(): ($x, $y)');}
重定向构造函数
有时候类中的构造函数会调用类中其它的构造函数,该重定向构造函数没有函数体,只需在函数签名后应用:
指定须要重定向到的其它构造函数即可:
class Point { double x, y; // 该类的主构造函数。 Point(this.x, this.y); // 委托实现给主构造函数。 Point.alongXAxis(double x) : this(x, 0);}
常量构造函数
如果类生成的对象都是不会变的,那么能够在生成这些对象时就将其变为编译时常量。你能够在类的构造函数前加上const
关键字并确保所有实例变量均为final
来实现该性能。
class ImmutablePoint { static final ImmutablePoint origin = const ImmutablePoint(0, 0); final double x, y; const ImmutablePoint(this.x, this.y);}
常量构造函数创立的实例并不总是常量。
工厂构造函数
应用factory
关键字标识类的构造函数将会令该构造函数变为工厂构造函数,这将意味着应用该构造函数结构类的实例时并非总是会返回新的实例对象。例如,工厂构造函数可能会从缓存中返回一个实例,或者返回一个子类型的实例。
在如下的示例中,Logger
的工厂构造函数从缓存中返回对象,和 Logger.fromJson
工厂构造函数从JSON
对象中初始化一个最终变量。
class Logger { final String name; bool mute = false; // _cache 变量是库公有的,因为在其名字后面有下划线。 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); }}
工厂结构函的调用形式与其余构造函数一样:
var logger = Logger('UI');logger.log('Button clicked');var logMap = {'name': 'UI'};var loggerJson = Logger.fromJson(logMap);
抽象类
应用关键字abstract
标识类能够让该类成为抽象类,抽象类将无奈被实例化。抽象类罕用于申明接口办法、有时也会有具体的办法实现。如果想让抽象类同时可被实例化,能够为其定义工厂构造函数。
抽象类经常会蕴含形象办法。上面是一个申明具备形象办法的抽象类示例:
// 该类被申明为形象的,因而它不能被实例化。abstract class AbstractContainer { // 定义构造函数、字段、办法等…… void updateChildren(); // 形象办法。}
隐式接口
每一个类都隐式地定义了一个接口并实现了该接口,这个接口蕴含所有这个类的实例成员以及这个类所实现的其它接口。如果想要创立一个A
类反对调用B
类的API且不想继承B
类,则能够实现B
类的接口。
一个类能够通过关键字implements
来实现一个或多个接口并实现每个接口定义的 API:
// Person 类的隐式接口中蕴含 greet() 办法。class Person { // _name 变量同样蕴含在接口中,但它只是库内可见的。 final _name; // 构造函数不在接口中。 Person(this._name); // greet() 办法在接口中。 String greet(String who) => '你好,$who。我是$_name。';}// Person 接口的一个实现。class Impostor implements Person { get _name => ''; String greet(String who) => '你好$who。你晓得我是谁吗?';}String greetBob(Person person) => person.greet('小芳');void main() { print(greetBob(Person('小芸'))); print(greetBob(Impostor()));}
如果须要实现多个类接口,能够应用逗号宰割每个接口类:
class Point implements Comparable, Location {}
扩大一个类
应用extends
关键字来创立一个子类,并可应用super
关键字援用一个父类:
class Television { void turnOn() { _illuminateDisplay(); _activateIrSensor(); }}class SmartTelevision extends Television { void turnOn() { super.turnOn(); _bootNetworkInterface(); _initializeMemory(); _upgradeApps(); }}
应用 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; }}
定义一个类继承自Object
并且不为该类定义构造函数,这个类就是 Mixin
类,除非你想让该类与一般的类一样能够被失常地应用,否则能够应用关键字mixin
代替class
让其成为一个单纯的Mixin
类:
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
类 A
,然而A
只能被B
类应用,则能够这样定义 `A:
class Musician { // ...}mixin MusicalPerformer on Musician { // ...}class SingerDancer extends Musician with MusicalPerformer { // ...}
Extension 办法
Dart 2.7 中引入的Extension
办法是向现有库增加性能的一种形式。
这里是一个在 String 中应用 extension
办法的样例,咱们取名为 parseInt()
,它在 string_apis.dart
中定义:
extension NumberParsing on String { int parseInt() { return int.parse(this); } double parseDouble() { return double.parse(this); }}
而后应用string_apis.dart
外面的parseInt()
办法
import 'string_apis.dart';print('42'.padLeft(5)); // Use a String method.print('42'.parseInt()); // Use an extension method.