Dart学习-类

Dart

dart是一种面向对象的语言,具有类和基于mixin的继承。每个对象都是一个类的实例,所有的类都是Object的子类。基于mixin的继承意味着,尽管每个类(Object除外)只有一个超类,但一个类主体可以在多个类层次中重用。


使用类成员

对象的成员包括函数和数据(分别是方法和实例变量)。当你调用一个方法时,你在一个对象上调用它:这个方法可以访问那个对象的函数和数据。

使用点(.)引用实例变量或方法:

var p = Point(2, 2);

// 设置实例变量要的值
p.y = 3;

// 获取y的值
assert(p.y == 3);

// 在p上调用 distanceTo()
num distance = p.distanceTo(Point(4, 4));

使用?.而不是.为了避免最左边的操作对象为空时出现异常:

// 如果p不为空,设置它的变量y的值为4
p?.y = 4;

使用构造函数

你可以使用一个创建函数来创建对象。构造函数的名字可以是ClassName或者ClassName.indentifier。例如:以下的代码创建Point对象使用Point()Point.formJson()构造函数:

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});

版本说明:key关键词在版本2中为可选使用

一些类提供常量构造函数constant constructors。使用一个常量构造函数去创建一个编译时常量,将 const关键词放在构造函数名称之前:

var p = const ImmutablePoint(2, 2);

构造两个相同的编译时常量会产生一个规范的实例:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); //他们是同一个对象

在常量上下文中,可以省略构造函数或常量前面的const。例如,看看这个代码,它创建了一个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的情况下被调用,那么它会创建一个非常量对象:

var a = const ImmutablePoint(1, 1); // 创建一个常量
var b = ImmutablePoint(1, 1); //没有常见一个常量

assert(!identical(a, b)); //两者不是相同对象!

版本说明: const关键字在Dart 2中的恒定上下文中变得可选。


获取对象的类型

在运行时获取对象的类型,你可以使用对象的runtimeType属性,该属性返回一个Type对象

print('The type of a is ${a.runtimeType}');

到目前为止,你已经知道如何使用类。接下去剩余的章节展示如何去实现类。


对象变量

这里展示你如何声明对象的变量:

class Point {
  num x; // 声明变量x,初始化为null
  num y; // 声明变量y,初始化为null
  num z = 0; // 声明变量z,初始化为0
}

所有未初始化的对象变量拥有值null

所有实例变量都生成隐式getter方法。Non-final实例变量也生成隐式setter方法。

class Point {
  num x;
  num y;
}

void main() {
  var point = Point();
  point.x = 4; // 对x使用setter方法
  assert(point.x == 4); // 对x使用getter法
  assert(point.y == null); // 值默认为空。
}

如果初始化声明实例变量量(该变量不是在构造函数或方法中),则在创建实例时设置该值,该值在构造函数及其初始值列表之前执行。


构造函数

通过创建一个与其类同名的函数来声明一个构造函数(另外,还可以添加一个附加标识符,如命名构造函数中所述)。最常见的构造函数形式——生成构造函数——创建了一个类的新实例:

class Point {
  num x, y;

  Point(num x, num y) {
    //有更好的方法来做到这一点,请继续往下看
    this.x = x;
    this.y = y;
  }
}

this关键词引用到当前的对象

注:只有在名称冲突时才使用此选项。否则,Dart样式会忽略这个。

将构造函数参数分配给实例变量的模式非常常见,Dart有语法糖以方便创建对象构造器:

class Point {
  num x, y;

  // 在构造函数运行前,语法糖会帮助设置x和y
  Point(this.x, this.y);
}
默认的构造函数

如果不声明构造函数,则会为你提供默认构造函数。默认构造函数没有参数,并调用超类中的无参数构造函数。

构造函不能被继承

子类不从它们的超类继承构造函数。声明没有构造函数的子类只有默认(没有参数,没有名称)构造函数。

命名构造函数

使用命名构造函数为一个类实现多个构造函数,或者提供额外的声明:

class Point {
  num x, y;

  Point(this.x, this.y);

  //命名构造函数
  Point.origin() {
    x = 0;
    y = 0;
  }
}

请记住,构造函数不是继承的,这意味着超类的命名构造函数不是由子类继承的。如果你希望使用超类中定义的命名构造函数创建子类,则必须在子类中实现该构造函数。

调用 非默认超类 构造函数

默认情况下,子类中的构造函数调用超类的未命名无参数构造函数。超类的构造函数在构造函数主体的开头被调用。如果初始值列表也被使用,它会在调用超类之前执行。总之,执行顺序如下:

  1. 初始值列表
  2. 超类的无参构造器
  3. 主类的无参构造器

如果超类没有未命名的无参数构造函数,则必须手动调用超类中的一个构造函数。在冒号(:)后面指定超类构造函数,就在构造函数主体(如果有的话)前面。

在以下示例中,Employee类的构造函数调用其超类Person的命名构造函数。

class Person {
  String firstName;
  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});
  if (emp is Person) {
    // 类型检查
    emp.firstName = '永动机';
  }
  (emp as Person).firstName = '永动机';
}
-------------------------------------------------------------------
in Person
in Employee

因为超类构造函数的参数是在调用构造函数之前评估的,所以参数可以是表达式,如函数调用:

class Employee extends Person {
  Employee() : super.fromJson(getDefaultData());
  // ···
}

警告:超类构造函数的参数没有访问this的权限。例如,参数可以调用静态方法,但不能调用实例方法。

初始值列表

除了调用超类构造函数,还可以在构造函数主体运行之前初始化实例变量。用逗号分隔初始值设定项。

// 初始值列表在构造函数运行前初始化对象变量
Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

警告:初始值设定项的右侧没有访问this的权限。

在开发过程中,你可以使用初始值列表中的assert来验证输入。

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

初始值列表在设置final字段时非常方便。下面的示例初始化初始值列表中的三个final字段。

import 'dart:math';

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(2, 3);
  print(p.distanceFromOrigin);
}
----------------------------------------------------------------------
3.605551275463989
重定向构造函数

有时构造函数的唯一目的是重定向到同一类中的另一个构造函数。重定向构造函数的主体为空,构造函数调用出现在冒号( :)之后。

class Point {
  num x, y;

  // 这个类的主构造函式。
  Point(this.x, this.y);

  // 委托给主构造函数。
  Point.alongXAxis(num x) : this(x, 0);
}

常量构造函数
如果你的类创建的对象从不改变,你可以创建一些编译时的常量对象。因此,定义一个const构造函数,且保证所有的对象变量都是final。

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final num x, y;

  const ImmutablePoint(this.x, this.y);
}
工厂构造函数

在实现一个构造函数时使用factory关键字,该构造函数并不总是创建其类的新实例。例如,工厂构造函数可能会从缓存中返回实例,也可能会返回子类型的实例。

下面的示例演示从缓存返回对象的工厂构造函数:

class Logger {
  final String name;
  bool mute = false;

 // _cache是私有变量
//_在名称前,表示该变量为私有
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

注意:工厂构造者对this没有访问权限。

像调用任何其他构造函数一样调用工厂构造函数:

var logger = Logger('UI');
logger.log('Button clicked');

方法

方法是为对象提供行为的函数。

实例方法

在对象的实例方法有权限获取对象变量和this,在接下来的例子里distanceTo就是一个对象方法:

import 'dart:math';

class Point {
  num x, y;

  Point(this.x, this.y);

  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}
Getters 和 setters

getters 和 setters 是提供对对象属性的读写访问的特殊方法。回想一下,每个实例变量都有一个隐式getter,如果合适的话,还有一个setter。你可以通过使用getset关键字实现getters和setters来创建其他属性:

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // 定义两个计算属性:右侧和底部。
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

使用getters和setters,您可以从实例变量开始,然后用方法包装它们,所有这些都不需要更改客户端代码。

注意:无论是否明确定义了getter,increment ( ++ )等运算符都以预期的方式工作。为了避免任何意外的副作用,操作只调用getter一次,将其值保存在一个临时变量中。

抽象方法

对象、getter和setter方法可以是抽象的,定义一个接口,但将其实现留给其他类。抽象方法只存在于抽象类中。

要使方法抽象,请使用分号(;)而不是方法体:

abstract class Doer {
  //定义对象变量和方法...

  void doSomething(); //定义抽象方法
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // 提供一个实现,因此该方法在这里不是抽象的...
  }
}
抽象的类

使用abstract修饰符来定义一个抽象类(一个类不能被实例化)抽象类对于定义接口是非常有用的,经常有一些实现。如果你的抽象类想要能被实例化,请定义工厂构造函数

抽象类里的抽象方法

// 此对象爱你过是抽象类,因此不能被实例化
abstract class AbstractContainer {
  // 定义构造函数、字段、方法...

  void updateChildren(); // 抽象方法
}

隐式接口

每个类隐式定义一个接口,该接口包含该类及其实现的任何接口的所有实例成员。如果你想创建一个不继承B实现而支持B类API的A类,A类应该实现B接口。

一个类通过在implements子句中声明一个或多个接口,然后提供接口所需的API来实现这些接口。例如:

// 隐式接口包含 greet()
class Person {
  // 在接口中,但仅在此类中可见。
  final _name;

  //不在接口中,因为这是构造函数。
  Person(this._name);

  // 在接口中
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// 实现了Person接口
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()));
}

下面是一个指定一个类实现多个接口的示例:

class Point implements Comparable, Location {...}

扩展类

使用extends去创建子类,super去引用超类:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}
重写成员

子类可以重写实例方法、getters和setters。您可以使用@override注解来指示你想要重写的成员:

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}

要缩小代码中类型安全的方法参数或实例变量的类型,可以使用covariant关键字。

重写运算符

你可以重写以下表中显示的运算符。例如,如果定义Vecor类,则可以定义+方法来添加两个vectors。

< + | []
> / ^ []=
<= ~/ & ~
>= * << ==
% >>

注意:你可能已经注意到了!=不是可重写的运算符。表达式E1!= E2只是语法上的糖!( E1 = = E2 )

下面是一个重写+-运算符的类的示例:

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);

 //运算符= =和hashCode未显示。
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

如果重写==,则还应该重写对象的hashCode getter。

noSuchMethod()

要在代码试图使用不存在的方法或实例变量时检测或做出反应,可以重写noSuchMethod ( ) :

class A {
  // 除非重写noSuchMethod,否则使用不存在的成员会导致noSuchMethod错误。
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

除非下列情况之一成立,否则不能调用未实现的方法:

  • 接收者具有静态类型dynamic
  • 接收者具有定义未实现方法的静态类型(抽象是可以的),接收者的动态类型具有不同于类Object中的noSuchMethod()的实现。

枚举类型

枚举类型,通常称为枚举,是一种特殊类型的类,用于表示固定数量的常量值。

使用枚举

使用enum关键词来声明一个枚举类型

enum Color { red, green, blue }

枚举中的每个值都有一个index索引,它返回枚举声明中值的从零开始的位置。例如,第一个值具有索引0,第二个值具有索引1。

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

若要获取枚举中所有值的列表,请使用枚举的values常量。

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

你可以在switch语句中使用枚举,如果不处理枚举的所有值,将会收到警告:

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // 没有default,将会报错
    print(aColor); // 'Color.blue'
}

枚举类型有以下限制:

  • 你不能子类化、混合或实现枚举。
  • 不能显式实例化枚举。

给类添加一个特性:混合(mixin)

混合(mixin)是一种在多个类层次中重用类代码的方法。

若要使用mixin,请使用with关键字后跟一个或多个mixin名称。下面的示例显示了使用mixins的两个类:

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

要实现mixin,请创建一个扩展对象的类,声明没有构造函数,也没有对super的调用。例如:

abstract class 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');
    }
  }
}

注意:一些对mixin的限制正在被取消。有关详细信息,请参见建议的mixin规范。


类变量和类方法

使用static关键字实现类范围的变量和方法。

静态变量

静态变量(类变量)对于类范围的状态和常量非常有用:

静态变量在使用之前不会初始化。

静态方法

静态方法(类方法)不能在实例操作,因此它没有访问this的权限。例如:

import 'dart:math';

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);
  }
}

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);
}

注意:为了常用或广泛使用的实用程序和功能,考虑使用顶层函数,而不是静态方法。

可以使用静态方法作为编译时常量。例如,可以将静态方法作为参数传递给常量构造函数。

PS:本文整理自官方文档,若有发现问题请致邮 caoyanglee92@gmail.com

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容