Dart语言基础之类

原文:https://www.dartlang.org/guides/language/language-tour
Dart是一种面向对象的语言,具有类和基于mixin的继承。 每个对象都是一个类的实例,所有类都来自Object。 基于Mixin的继承意味着虽然每个类(除了Object)只有一个超类,但是类体可以在多个类层次结构中重用。
用点(.) 来访问实例变量和方法:

var p = Point(2, 2);

// Set the value of the instance variable y.
p.y = 3;

// Get the value of y.
assert(p.y == 3);

// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4, 4));

?. i代替 .来避免异常当左对象是null:

// If p is non-null, set its y value to 4.
p?.y = 4;

使用构造函数

你可以用构造函数来创建一个对象. 构造函数可以用 类名或者类名.标识符. 例如, 下面的代码使用使用 Point()Point.fromJson() 构造函数来创建 Point 对象:

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

下面的代码具有同样的效果, but uses the optional new keyword before the constructor name:

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

Version note: new关键字在Dart 2中是可选的.

一些类提供 constant constructors. 用常量构造函数来创建编译时常量, 将 const 关键字放在构造函数前面:

var p = const ImmutablePoint(2, 2);

Constructing two identical compile-time constants results in a single, canonical instance:

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

assert(identical(a, b)); // They are the same instance!

常量上下文(constant context)中, 你可以省略 const :

// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

省略const :

// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

若常量构造函数没有在常量上下文中被调用,或者没有用const调用, 会产生 non-constant object错误:

var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant

assert(!identical(a, b)); // NOT the same instance!

Version note: const keyword became optional within a constant context in Dart 2.

获得对象类型

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

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

实例变量

声明:

class Point {
  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 and setters.

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.
}

当你在声明实例变量的地方初始化它 (不是在构造函数和方法内),当实例变量创建的时候就会被赋值, 在构造方法和初始化列表执行之前.

Dart语法糖:

class Point {
  num x, y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

默认构造函数

如果你没有声明构造函数, 会为你提供一个默认构造函数. 默认构造函数没有任何参数并且会调用父类的无参构造函数。构造函数不能被继承。

子类不能继承父类的构造函数. 没有声明构造函数的子类只有一个默认构造函数.

命名构造函数

使用命名构造函数来为类提供多个构造函数:

class Point {
  num x, y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin() {
    x = 0;
    y = 0;
  }
}

父类的命名构造函数不能被子类继承.如果你想用父类的命名构造函数来创建子类, 你必须在子类中实现它.

调用父类的非默认构造函数

默认的, 子类的构造函数调用父类的非命名无参构造函数. 父类的构造函数在子类构造函数体的开始执行之前被调用. 如果使用了初始化列表 ,初始化列表会在父类构造函数之前执行. 总之,执行顺序如下:

  1. 初始化列表 initializer list
  2. 父类的构造函数
  3. 主类的构造函数

如果父类中没有非命名无参构造函数, 你必须手动调用父类的一个构造函数. 在冒号 (:)之后指定父类构造函数.

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

  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}

因为父类构造函数的参数会在调用父类构造函数之前被计算出来,参数可以是一个表达式,例如一个函数调用:

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

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

初始化列表

除了调用父类构造函数,你还可以在构造函数执行之前初始化实例变量. 用逗号分隔初始化代码.

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

警告: The right-hand side of an initializer does not have access to this.

在开发中,你可以在初始化列表中用assert来验证输入.

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}
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);
}

重定位构造函数

有时构造函数的唯一目的就是定位到另一个同类中的其他构造函数. 重定位构造函数的函数体是空的,with the constructor call appearing after a colon (:).

class Point {
  num x, 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.

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

  final num x, y;

  const ImmutablePoint(this.x, this.y);
}

常量构造函数并不总是创建常量. 细节请看using constructors.

工厂构造函数

当实现一个并不总是创建新实例对象的构造函数,使用factory关键词. 例如,一个工厂构造函数可能从缓存中返回一个对象,或者它可能返回一个子类的实例.

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

  // _cache is library-private, thanks to
  // the _ in front of its name.
  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);
  }
}

Note: 工厂构造函数不能访问this.

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

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

方法

实例方法

对象的实例方法可以使用 this. :

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 and setters

Getters and setters 是为类属性提供读写途径的特殊方法。 重新强调,每个实例变量都有一个隐式的getter和 setter . 你可以通过实现getters 和 setters来创建额外的属性, 使用 getset 关键词:

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

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

  // Define two calculated properties: right and bottom.
  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);
}

With getters and setters, you can start with instance variables, later wrapping them with methods, all without changing client code.

Note: Operators such as increment (++) work in the expected way, whether or not a getter is explicitly defined. To avoid any unexpected side effects, the operator calls the getter exactly once, saving its value in a temporary variable.

抽象方法

getter, setter 可以是抽象的, 定义一个接口并把它的实现交给其他类. 抽象方法只能存在于 抽象类.

abstract class Doer {
  // 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...
  }
}

隐式接口

每个类都隐式的定义了一个包含一个类和它实现的所有接口的所有实例变量和方法. 如果你想创建一个支持B类API但不继承其实现的类A,A类应该实现B类接口.

一个类可以实现一个或多个接口:

// 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()));
}
class Point implements Comparable, Location {...}

扩展类

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

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

重写方法

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

可重载运算符

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

Note: 你可能会被通知!=不是个可重载的操作符.表达式1e1 != e2 `i只是!(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);

  // Operator == and hashCode not shown. For details, see note below.
  // ···
}

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

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

如果你重载了==, 你应该重写ObjecthashCode getter. 例子请看 Implementing map keys.

更多信息Extending a class

noSuchMethod()

为了探测和对是否调用了类中不存在的方法和变量作出反应,你可以重写noSuchMethod():

class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

不能调用 未实现的方法除非以下之一成立:

  • 接收者有静态类型 dynamic.
  • 接收者是静态类型定义了未实现的方法 (抽象的也行), 接收者的动态类型有异于ObjectnoSuchMethod()的实现.

更多请看noSuchMethod forwarding specification.

Enum类型

通常叫列举枚举, 是一种特殊的类用来代表常量值的固定数字.

使用enum

enum Color { red, green, blue }

enum中的每个值都有个 index getter, 返回从0开始的值在enum中声明的位置.例如, 第一个值的index为0, 第二个为1.

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

To get a list of all of the values in the enum, use the enum’s values constant.

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

你可以在 switch statements中使用enum, 你如果没有处理全部值你会得到一个警告:

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // Without this, you see a WARNING.
    print(aColor); // 'Color.blue'
}

enum类型有如下限制:

  • You can’t subclass, mix in, or implement an enum.
  • You can’t explicitly instantiate an enum.

更多请看Dart language specification.

为类添加特性: 混入

混入是一种在多层级类关系重用类代码的方法。

使用混入,将一个或多个mixin名放在with后面。

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

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

实现一个mixin,创建一个继承自Object的类,并且不声明任何构造函数。除非你想你的mixin可以像正常类一样被使用,否则用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');
    }
  }
}

To specify that only certain types can use the mixin — for example, so your mixin can invoke a method that it doesn’t define — use on to specify the required superclass:

mixin MusicalPerformer on Musician {
  // ···
}

Version note: mixin关键词在 Dart 2.1中引进. 早期版本的代码使用abstract class 代替. 更多请看Dart SDK changelog and 2.1 mixin specification.

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

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,312评论 0 10
  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 9,448评论 0 13
  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,739评论 2 9
  • 时间过得真快,不知不觉迷迷糊糊的2月份就过完了。 今天听了讲服装的断舍离,适合的不适和的该怎样处理。去年才做过一次...
    傲雪_352a阅读 123评论 0 1
  • 那一夜自己究竟是如何回来,现在已无法记起,烧烤摊上的壶装烈酒让我的思维能力几近于低能儿。次日醒来,只觉得头痛如裂,...
    似沫渐消阅读 674评论 0 2