End

Dart 开发语言概览2 类

本文地址


目录

Dart 开发语言概览

  • Dart 是支持基于 mixin 继承机制的面向对象语言,除了 class Null 以外的所有的类都继承自 Object
  • 每个类都只有一个超类(Object? 除外)
  • 扩展方法 是一种在不更改类或创建子类的情况下,向类添加功能的方式
  • 使用 ?. 代替 . 可以避免因为左边表达式为 null 而导致的空指针问题

实例变量

  • 所有未初始化的实例变量其值均为 null
  • 所有实例变量均会隐式地声明一个 Getter 方法
  • 非终值的实例变量和 late final 声明但未声明初始化的实例变量,会隐式地声明一个 Setter 方法
  • 没有使用 late 修饰的变量,会在实例初始化时早于构造方法进行赋值
    • 因此,没有使用 late 修饰的变量无法访问到 this
class Point {
  late final int a; // late final 声明但未声明初始化
  late final int b = Random().nextInt(9);
  final int c; // final 声明但未声明初始化

  Point(this.c); // final 且非 late 变量,必须在声明时初始化,或在构造方法中初始化
  Point.unnamed() : c = 1;
}

常量上下文

常量上下文 场景中,可以省略掉 const 关键字

const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

// 可以只保留第一个 const 关键字,其余的全部省略
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

构造函数

  • 构造函数的命名方式: 类名(...)类名.标识符(...)
  • 构造实例时,构造函数名前面的的关键字 new 是可选的
  • 可以使用 Object 对象的 runtimeType 属性在运行时获取一个对象的类型
var p1 = Point(2, 2);
var p2 = new Point(2, 2);
var p3 = Point.fromJson({'x': 1, 'y': 2});
print(p1.runtimeType); // Point

默认构造函数

如果没有声明构造函数,那么 Dart 会自动生成一个无参数的构造函数,并且该构造函数会调用其父类的无参数构造方法

构造函数不被继承

子类不会继承父类的构造函数,如果子类没有声明构造函数,那么只会有一个默认无参数的构造函数

命名式构造函数

  • 可以为一个类声明多个命名式构造函数,来表达更明确的意图
  • 因构造函数不能被继承,因此子类也不能继承父类的命名式构造函数
class Point {
  final double x;
  final double y;

  Point(this.x, this.y);

  Point.origin() // 命名式构造函数
      : x = 1,
        y = 2;
}

调用父类构造函数

  • 默认情况下,子类的构造函数会调用父类的无参数构造函数,并且该调用会在子类构造函数的函数体代码执行前
  • 如果子类构造函数还有一个 初始化列表,那么该初始化列表会在调用父类的该构造函数之前被执行
  • 如果父类没有无参数构造函数,那么子类必须显式调用父类的其中一个构造函数

调用顺序:

  1. 初始化列表
  2. 父类的无参数构造函数
  3. 当前类的构造函数
class Employee extends Person {
  Employee() : super.fromJson(getData()); // 调用父类 Person 的命名构造函数
}

注意: 传递给父类构造函数的参数不能使用 this,因为此时子类构造函数尚未执行,子类的实例对象也就还未初始化,因此所有的实例成员都不能被访问。

超类参数

  • 超类参数: 直接在子类的构造函数中使用超类构造的某个参数
  • 超类参数不能和重定向的参数一起使用
class V2 {
  final int x;
  final int y;

  V2(this.x, this.y);
}

class V3 extends V2 {
  final int z;

  V3(super.x, super.y, this.z);                  // 超类参数
  V3.named1(super.x, super.y, this.z);           // 超类参数
  V3.named2(int a, int b, this.z) : super(a, b); // 重定向的参数

  // Positional super parameters can't be used when the super constructor invocation has a positional argument
  V3.named3(super.x, this.z) : super(x, x); // 报错
}

初始化列表

  • 可以在构造函数的函数体执行之前初始化实例变量, 实例变量之间使用逗号分隔
  • 初始化列表表达式右边的语句,不能使用 this 关键字
class Point {
  final int x;
  final int y;

  Point(int i)
      : x = i + 1, // 初始化列表
        y = i - 1; // 不能访问 x(因为不能使用 this 关键字)

  Point.named(int i)
      : x = i + 1,
        y = i - 1 {
    print("named"); // 构造函数可以有函数体,也可以省略
  }
}

在开发模式下,可以在初始化列表中使用 assert 验证输入数据:

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

重定向构造函数

  • 可在函数签名后使用 : this 重定向到其它构造函数
  • 仅用于调用类中其它构造函数,而不能有函数体: Redirecting constructors can't have a body
class Point {
  double x, y;
  Point(this.x, this.y);           // main constructor
  Point.aa(double x) : this(x, 0); // 重定向到 main constructor
}

常量构造函数

  • 在构造函数名之前加 const 关键字即可定义 常量构造函数
  • 需确保所有实例变量均为 final: Can't define a const constructor for a class with non-final fields
  • 常量构造函数 可用来创建编译时常量
class ImmutablePoint {
  final int x, y;
  const ImmutablePoint(this.x, this.y); // 定义常量构造函数
  ImmutablePoint.named(this.x) : y = x; // 定义命名构造函数
}

const origin = ImmutablePoint(0, 0); // 编译时常量
  • 两个使用相同构造函数、相同参数值构造的编译时常量,是同一个对象
  • 注意: 常量构造函数 创建的实例并不总是常量
void main() {
  var a = const ImmutablePoint(1, 1); // 编译时常量
  var b = const ImmutablePoint(1, 1); // 编译时常量
  assert(identical(a, b));            // 是同一个对象
  
  var c = ImmutablePoint(1, 1); // 不是常量
  assert(!identical(c, b));     // 不是同一个对象
}

工厂构造函数

  • 使用 factory 关键字标识的构造函数叫 工厂构造函数
  • 工厂构造函数 中无法访问 this
  • 使用 工厂构造函数 构造类的实例时,并非总是会返回新的实例对象
    • 可以从缓存中返回一个已经存在的实例
    • 也可以返回一个子类型的实例
class Logger {
  final String name;

  Logger.named(this.name);

  factory Logger(String name) {
    return Logger.named("qt-$name");
  }
}

print(Logger("白乾涛").name);

方法

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

实例方法

对象的实例方法可以访问实例变量和 this

操作符

Operators are instance methods with special names。

Dart 允许使用以下名称定义操作符:

+	-	*	/	~/	%
>	<	>=	<=	==
|(按位或)	&(按位与)	^(按位异或)	~(补码)
<<	>>	>>>		
[]	[]=

有一些 操作符 没有出现在列表中,例如 !=, 因为它们仅仅是语法糖。
例如,表达式 e1 != e2 仅仅是 !(e1 == e2) 的一个语法糖。

重写操作符

An operator declaration is identified using the built-in identifier operator

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

  @override
  bool operator ==(Object o) => o is Vector && x == o.x && y == o.y;
}

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

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

Getter 和 Setter

Getter 和 Setter 是一对用来读写对象属性的特殊方法,可以使用 getset 关键字为额外的属性添加 Getter 和 Setter 方法。

import 'dart:convert';

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

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

  int get right => left + width;

  set right(int value) => left = value - width;

  int get bottom => top + height;

  set bottom(int value) => top = value - height;

  Map<String, dynamic> toJson() => {
    'left': left,
    'top': top,
    'right': right,
    'bottom': bottom,
  };
}

void main() {
  var rect = Rectangle(0, 0, 10, 10);
  print(jsonEncode(rect)); // {"left":0,"top":0,"right":10,"bottom":10}
  rect.right = 15;
  rect.bottom = 0;
  print(jsonEncode(rect)); // {"left":5,"top":-10,"right":15,"bottom":0}
}

备注: 像 ++ 这样的操作符, 不管是否定义了 Getter 方法都会正确地执行。
为了避免一些不必要的异常情况,运算符只会调用 Getter 一次,然后将其值存储在一个临时变量中。

继承 extends

使用 extends 关键字来创建一个子类,并可使用 super 关键字引用一个父类。

抽象类

使用关键字 abstract 标识类可以让该类成为 抽象类。抽象类无法被实例化。

抽象方法 只能存在于抽象类中, 实例方法、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
}

重写类成员

子类可以重写父类的实例方法,包括 操作符、 Getter、Setter 方法。

An overriding method declaration must match the method (or methods) that it overrides in several ways:

  • 返回类型必须相同或其子类型。The return type must be the same type as (or a subtype of) the overridden method’s return type.
  • 参数类型必须相同或其超类型。Argument types must be the same type as (or a supertype of) the overridden method’s argument types.
  • 位置参数的数量必须保持一致。If the overridden method accepts n positional parameters, then the overriding method must also accept n positional parameters.
  • 泛型和非泛型间不能相互覆盖。A generic method can’t override a non-generic one, and a non-generic method can’t override a generic one.

注意: 如果重写 == 操作符,必须同时重写对象 hashCode 的 Getter 方法。

隐式接口

每一个类都隐式地定义了一个接口并实现了该接口,这个接口包含所有这个类的实例成员以及这个类所实现的其它接口。

如果想要创建一个 A 类支持调用 B 类的 API 且不想继承 B 类,则可以实现 B 类的接口。

class Animal {
  String name;
  final int age;
  final String _xx; // In the interface, but visible only in this library

  Animal(this.name, this.age, this._xx); // constructor in the interface

  void hello() => print('Hello, I am $name.');
}

class Person implements Animal {
  @override
  String name = "人类";

  @override
  int get age => 10086;

  @override
  String get _xx => "人类的xx";

  @override
  void hello() => print('你好, 我是 $name.');
}

void main() {
  Animal animal = Person();
  print("${animal.name}, ${animal.age}, ${animal._xx}");
  animal.hello();
}

noSuchMethod 方法

如果调用了对象上不存在的方法或实例变量将会触发 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}');
  }
}

void main() {
  dynamic a = A();
  a.hello();
}

枚举类型

枚举类型用于定义一些 固定数量常量值

  • 所有的枚举都继承于 Enum
  • 枚举类是封闭的,即不能被继承、被实现、被 mixin 混入或显式被实例化
  • 抽象类和 mixin 可以显式的实现或继承 Enum,但只有枚举可以实现或混入这个类,其他类无法享有同样的操作

简单的枚举类型

使用关键字 enum 来定义简单的枚举类型和枚举值:

enum Color { red, green, blue }

You can also use trailing commas when declaring an enumerated type to help prevent copy-paste errors.

增强的枚举类型

Dart 中的枚举也支持定义字段、方法和常量构造,常量构造只能构造出已知数量的常量实例(已定义的枚举值)。

你可以使用与定义 类似的语句来定义增强的枚举,但是这样的定义有一些限制条件:

  • 实例的字段必须是 final,包括由 mixin 混入的字段
  • 所有的 实例化构造 必须以 const 修饰
  • 工厂构造 只能返回已知的一个枚举实例
  • 由于 Enum 已经自动进行了继承,所以枚举类不能再继承其他类
  • 不能重载 indexhashCode 和比较操作符 ==
  • 不能声明 values 字段,否则它将与枚举本身的静态 values getter 冲突
  • 在进行枚举定义时,所有的实例都需要首先进行声明,且至少要声明一个枚举实例
enum A implements Comparable<A> {
  car(tips: "小汽车", price: 5), // 枚举实例
  bus(tips: "大巴车", price: 8);

  const A({required this.tips, required this.price}); // 实例化构造

  final String tips; // 成员变量
  final int price;

  String get hello => "我是 $tips,价格 $price"; // getter 方法

  @override
  int compareTo(A other) => price - other.price; // 实现接口
}

使用枚举

  • 每一个枚举值都有一个名为 index 成员变量的 Getter 方法,该方法将会返回以 0 为基准索引的位置值
  • 想要获得全部的枚举值,使用枚举类的 values 方法获取包含它们的列表
  • 可以使用 .name 属性获取一个枚举值的名称
  • 可以在 Switch 语句中使用枚举
enum Color { red, green, blue }

main() {
  final color = Color.blue;
  if (color == Color.blue) {
    print('Your color is blue!');
  }

  assert(Color.red.index == 0);

  List<Color> colors = Color.values;
  assert(colors[2] == Color.blue);
  print(colors); // [Color.red, Color.green, Color.blue]

  switch (color) {
    case Color.red:
    case Color.green:
      print('red or green');
      break;
    default: // Without this, you see a WARNING.
      print(color.name); // 'Color.blue'
  }
}

使用 Mixin 为类添加功能

Mixin 是一种在多重继承中复用某个类中代码的方法模式。

定义及使用 mixin

  • 一个 Mixin 类必须是 继承自 Object未声明构造函数 的类
  • 除非你想让该类与普通的类一样可以被正常地使用,否则请使用关键字 mixin 替代 class
mixin class MixinA {}
mixin MixinB {}

class A {}
class B {}
abstract class C {}

class All extends A with MixinA, MixinB implements B, C {}

main() {
  MixinA mixinA = MixinA();
  MixinB mixinB = MixinB(); // 报错: Mixins can't be instantiated
}

限制 mixin 使用范围

可以使用关键字 on 来指定哪些类可以使用该 Mixin 类。

mixin MixinA on A {}

class A {}
class A1 implements A {}
class A2 extends A {}

class B extends A with MixinA {}
class B1 extends A1 with MixinA {}
class B2 extends A2 with MixinA {}

class C1 with MixinA {} // 报错。'MixinA' can't be mixed onto 'Object' because 'Object' doesn't implement 'A'.
class C2 with MixinA implements A {} // 报错。'MixinA' can't be mixed onto 'Object' ...

with 多个 mixin

class Base {
  void base() => print("base");
}

mixin MixinA on Base {
  void mixinA() => print("mixinA");
}
mixin MixinB on Base {
  void mixinB() => print("mixinB");
}

mixin MixinC on MixinA, MixinB {
  void mixinC() => print("mixinC");
}

class All extends Base with MixinA, MixinB, MixinC {}

main() {
  All all = All();
  all.base();
  all.mixinA();
  all.mixinB();
  all.mixinC();
}

类变量和类方法

使用关键字 static 可以声明类变量(静态变量) 或 类方法(静态方法)。

  • 静态变量在其首次被使用的时候才被初始化
  • 静态方法不能对实例进行操作,因此不能使用 this
  • 对于一些通用或常用的静态方法,应该将其定义为 顶级函数 而非 静态方法
class B {
  static const size = 10086;
  static void hello() => print(size);
}

void main() {
  B.hello();
}

2023-07-21

posted @ 2023-07-21 01:07  白乾涛  阅读(93)  评论(0编辑  收藏  举报