Skip to content

Instantly share code, notes, and snippets.

@linsea
Created December 23, 2019 01:38
Show Gist options
  • Save linsea/cb593881299230f4eead82622862e60c to your computer and use it in GitHub Desktop.
Save linsea/cb593881299230f4eead82622862e60c to your computer and use it in GitHub Desktop.

Dart 类

Dart 是面向对象语言, 使用基于 mixin 的继承方式. 所有类都是 Object 类的子类.

构造函数

构造函数名称可以是 ClassNameClassName.identifier。后者称为命名构造函数. 例如,以下代码使用 Point()Point.fromJson() 构造函数创建 Point 对象:

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

或者在 Dart 2 之前, 使用 new 关键字构造对象:

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

所有实例变量都会生成一个隐式的 getter 方法。非final 实例变量也会生成隐式的 setter 方法。

因为使用构造函数的参数给实例变量是很普遍的行为, 所以 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;
  }
}

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

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

默认地, 子类调用父类的非命名的, 无参的构造函数, 并且须在子类的构造函数方法体的开始处调用. 如果使用了初始化列表( initializer list ), 则它会在父类构造函数之前执行, 总结它们的执行顺序如下:

  • 初始化列表
  • 父类的无参构造函数
  • 本类的无参构造函数

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

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

输出:

in Person
in Employee

初始化列表( initializer list )

可以在构造函数体运行之前运行初始化列表, 以给实例变量赋值, 使用逗号把初始化列表中的各值分隔.

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

// 技巧: 开发阶段, 使用 assert 函数来验证输入参数是否合法
Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}


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

  Point(x, y)
      : x = x, // 使用初始化列表来给 final 字段赋值
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(2, 3);
  print(p.distanceFromOrigin);
}

重定向构造函数

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

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

如果常量构造函数在常量上下文之外,并且在不使用 const 构造方法创建对象,则它将创建一个非常量对象:

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!

工厂构造函数

当实现并非总是创建其类的新实例的构造函数时,请使用 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) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

  Logger._internal(this.name); //私有的命名构造函数

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

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

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

Getter 和 Setter 函数

通过定义 GetterSetter 函数, 可以创建自定义的属性.

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

抽象类

使用 abstract 修饰符定义一个抽象类-一个无法实例化的类。抽象类通常用于某些实现中,用于定义接口, 有时还有部分实现。如果您希望抽象类看起来可实例化,请定义一个工厂构造函数。

// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
  // Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}

隐式接口

每个类都隐式定义了一个接口,该接口包含该类的所有实例成员及其实现的任意接口。如果您要创建一个支持B类API的A类而不继承B的实现,则A类应实现B接口。 这是与 JAVA 或其他语言的重大区别.

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

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

继承类

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

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

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

覆盖成员

子类可以覆盖实例方法,getter和setter。您可以使用 @override 批注指示您有意覆盖成员:

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

如果要 窄化(narrow) 方法的参数或者实例变量, 并且它是类型安全的, 可以使用 covariant 关键字.

操作符重载

可重符的操作符如下:

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

这是一个覆盖+和-运算符的类的示例:

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

如果您重载 ==,则还应该覆盖Object的 hashCode 方法.

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 的静态类型。
  • 接收者具有定义未实现方法的静态类型(抽象方法是可以的),接收者的动态类型具有 noSuchMethod()的实现,该实现与 Object 类中的实现不同。

枚举

定义:

enum Color { red, green, blue }

//使用 index 属性
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);

枚举类型有以下限制:

  • 不可被继承, 混合(mixin), 实现一个枚举类型
  • 不可实例化一个枚举枚举

混合(mixins)

混合是一种在多个类层次结构中重用类代码的方法。声明一个 mixin 有两个方法:

  • 隐式声明. 与声明一个类类似, 额外的要求的是, 它不能有构造函数. 这时显然它其实就是一个类, 只是没有显式的构造函数.
  • 显式声明. 与第一种方式基本一样, 所不同的是, 把类声明中的 class 关键字替换为 mixin 关键字. 使用这种方式声明的 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');
    }
  }
}

要使用 mixin , 只需使用 with 关键字, 后接一个或多个 mixin , 如以下两个类所示:

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

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

要指定只有某些类型可以使用 mixin(例如,以便您的 mixin 可以调用它未定义的方法),请使用 on 来指定所需的超类:

mixin MusicalPerformer on Musician {
  // ··· mixin 中可以调用 Musician 中的方法.
}

显然, 如果一个类使用了 MusicalPerformer , 那么它必然是 Musician 的子类.

mixin 的作用

Java 中没有 C++ 中的多重继承, 但有 interface 这种类型, 然后就实现了类似 C++ 中的多重继承功能. Dart 中有 Java 中的 interface 这种概念, 但是不能人为显式声明 interface 这种类型, 甚至 interface 都不是一个关键字, 它只是在声明类时, 系统隐式地声明了同名的 interface . mixin 不是 interface, 因为在它里面可以存储成员变量, 而不仅仅是函数, 其实它更像是一个抽象类, 在早期的 Dart 版本中, 就是使用抽象类来代替 mixin 这个概念的, mixin 关键字也是后期版本加入的. mixin 更像是 C++ 中的多重继承, 并且约束比之更少, 没有 private, protect, public 继承的概念. 它可以任意混合功能, 非常灵活.

当我们想要在不共享同一类层次结构的多个类之间共享行为时,或者在超类中实现这种行为没有意义时,mixin 非常有用。它有点像是使用继承的方式直接代替了其他语言使用组合达到的目的. 更多参考: https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3

函数

对于仅包含一个表达式的函数,可以使用简写箭头语法:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

可选参数

函数可以具有两种类型的参数:必需参数和可选参数。首先列出必需的参数,然后列出所有可选参数。可选参数又分两种: 命名参数或位置参数。可选参数可以是命名参数,也可以是位置参数,但不能同时存在。

定义函数时,请使用{param1,param2,…}指定命名参数:

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold, bool hidden}) {...}

调用函数时,可以使用 paramName:value 的方式指定命名参数。例如:

enableFlags(bold: true, hidden: false);

[]中指定一组参数会将其标记为可选的位置参数:

String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

两种调用方式:

assert(say('Bob', 'Howdy') == 'Bob says Howdy');

assert(say('Bob', 'Howdy', 'smoke signal') ==
    'Bob says Howdy with a smoke signal');

默认参数值

您的函数可以使用 = 来定义命名参数和位置参数的默认值。默认值必须是 编译时常量 。如果未提供默认值,则默认值为 null

/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold = false, bool hidden = false}) {...}

// bold will be true; hidden will be false.
enableFlags(bold: true);

旧代码可能使用冒号(:)而不是=来设置命名参数的默认值。原因是命名参数最初仅支持冒号(:)。该支持可能已被弃用,因此我们建议您使用=指定默认值。

下一个示例显示如何为位置参数设置默认值:

String say(String from, String msg,
    [String device = 'carrier pigeon', String mood]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  if (mood != null) {
    result = '$result (in a $mood mood)';
  }
  return result;
}

assert(say('Bob', 'Howdy') ==  // device 参数没有传值, 使用了默认值
    'Bob says Howdy with a carrier pigeon');

匿名函数

匿名函数看起来类似于命名函数-括号之间的零个或多个参数,以逗号和可选的类型注释分隔。下面的代码块包含函数的主体:

([[Type] param1[, …]]) {
  codeBlock;
};

以下示例遍历列表时, 提供了一个匿名函数来处理每一个列表项:

var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

如果该函数仅包含一个语句,则可以使用箭头符号简写为:

list.forEach(
    (item) => print('${list.indexOf(item)}: $item'));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment