Dart 构造函数最详细的解析

作者:阿拉阿伯 来源:blog.csdn.net 更新时间:2023-05-25 21:55
dart中的有趣的构造函数写法

dart语法中比较有意思的构造函数的写法 比如下面这三个构造函数其实是完全等价的

  DeviceType storeType;
  Map<String, dynamic> params;

  factory FluxAction.of(DeviceType type) {
    return new FluxAction(type);
  }

  FluxAction.off(DeviceType type): storeType = type, params = new Map();


  FluxAction.offf(DeviceType type): this(type);
 
  FluxAction(DeviceType type) {
        storeType = type;
        params = new Map();
  }



//这个是dart中的一种有趣的现象 ,实际的执行结果是返回了FluxAction对象,并且执行了FluxAction的toString()方法 ,方法先于返回。
  factory FluxAction.of(DeviceType type) {
    return new FluxAction(type)
      ..toString();
  }



工厂构造函数 常量构造函数 普通构造函数的关系

    工厂构造函数和const构造函数实现完全不同的目的.
    const构造函数允许在编译时常量表达式中具有自定义类的实例.
    工厂构造函数和返回类的新实例的常量方法更相似.
    不同之处在于,工厂构造函数与普通构造函数一样使用new进行调用,并且具有常量方法所没有的一些限制.
    普通构造函数和工厂构造函数之间的主要区别在于,
    工厂构造函数可以影响实际创建新实例以及它的具体类型.

factory构造函数

当执行构造函数并不总是创建这个类的一个新实例时,则使用 factory 关键字。 例如,一个工厂构造函数可能会返回一个 cache 中的实例, 或者可能返回一个子类的实例。
一个工厂构造函数中返回一个 cache 中的实例

//从cache中获取已经初始化过的实例
class Symbol {
  final String name;
  static Map<String, Symbol> _cache = new Map<String, Symbol>();

  factory Symbol(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final symbol = new Symbol._internal(name);
      _cache[name] = symbol;
      return symbol;
    }
  }

  Symbol._internal(this.name);
}
main() {
  var x = new Symbol('X');
  var alsoX = new Symbol('X');

  print(identical(x, alsoX));  // true
}



提示: 工厂构造函数无法访问 this。

很显然,x和alsoX是同一个对象 ,这是factory关键字的第一种用途 一般在框架层会用到
返回一个子类的实例

通常情况下我们的子类实现某些限制型接口由工厂类来负责具体的实例化子类的内容,来构造具体的子类对象

//此代码只是语法正确的代码,实际并不会如此实现
void main() {
  Cat cat = new Animal("cat");
  Dog dog = new Animal("dog");
}

abstract class Animal {
  factory Animal(String type) {
    switch(type) {
      case "cat":
        return new Cat();
      case "dog":
        return new Dog();
      default:
        throw "The '$type' is not an animal";
    }
  }
}

class Cat implements Animal {
}

class Dog implements Animal {
}

  

抽象类的工厂构造函数可以(默认)返??回此抽象类的某些默认实现。
dart 中的Future<T>就是一个很直观的例子

abstract class Future<T> {
   factory Future(computation()) {
    _Future result = new _Future<T>();
    Timer.run(() {
      try {
        result._complete(computation());
      } catch (e, s) {
        result._completeError(e, s);
      }
    });
    return result;
  }
}



常量构造函数

重点:Const构造函数仅用于创建编译时常量值
const构造函数创建一个“规范化”实例。

const构造函数创建一个“规范化”实例。也就是说,所有常量表达式都开始规范化,然后使用这些“规范化”符号来识别这些常量的等效性。

规范化:一种将具有多个可能表示的数据转换为“标准”规范表示的过程。可以这样做以比较不同表示形式的等效性,计算不同数据结构的数量,通过消除重复计算来提高各种算法的效率,或者可以强加有意义的排序顺序。

这意味着const表达式之类的表达式const Foo(1, 1)可以表示对虚拟机比较有用的任何可用形式。
VM仅需要按在const表达式中出现的顺序考虑值类型和参数。并且,减少它们以进行优化。
常量不会每次都重新创建。它们在编译时被规范化,并存储在特殊的查找表中(在其中通过规范签名进行哈希处理),以后可以从中重新使用它们。
const 和 final

讲到const 就不得不说和他有些相似的关键字 final

任何类都可以具有final字段,也可以具有const构造函数。

Dart中的字段实际上是一个匿名存储位置,结合了自动创建的getter和setter来读取和更新存储,还可以在构造函数的初始化列表中对其进行初始化。

final字段是相同的,只是没有设置器,因此设置其值的唯一方法是在构造函数初始化器列表中,并且此后无法更改值-因此是“ final”。

const构造函数的目的不是初始化final字段,任何生成构造函数都可以做到这一点。关键是创建编译时常量值:在编译时已经知道所有字段值的对象,而不执行任何语句。

这对类和构造函数施加了一些限制。const构造函数不能具有主体(不执行任何语句!),并且其类不得具有任何非最终字段(在编译时我们“知道”的值以后不能更改)。初始化程序列表还必须仅将字段初始化为其他编译时常量,因此右侧仅限于“编译时常量表达式”(或实际上:“可能是编译时常量表达式”,因为它也可能引用构造函数参数。) 。并且必须以“ const”作为前缀-否则,将得到一个满足这些要求的普通构造函数。这不是const构造函数。

为了使用const构造函数实际创建一个编译时常量对象,然后在“ new”表达式中将“ new”替换为“ const”。当然也可以将“ new”与const构造函数一起使用,它仍然会创建一个对象,但是它将只是一个普通的新对象,而不是编译时常量值。也就是说:const构造函数还可以用作普通的构造函数,以在运行时创建对象,以及在编译时创建编译时常量对象。

class Point {
  static final Point ORIGIN = const Point(0, 0);
  final int x;
  final int y;
  const Point(this.x, this.y);
  Point.clone(Point other): x = other.x, y = other.y; //[2]
}

main() {
  // Assign compile-time constant to p0.
  Point p0 = Point.ORIGIN;
  // Create new point using const constructor.
  Point p1 = new Point(0, 0);
  // Create new point using non-const constructor.
  Point p2 = new Point.clone(p0);
  // Assign (the same) compile-time constant to p3.
  Point p3 = const Point(0, 0);
  print(identical(p0, p1)); // false
  print(identical(p0, p2)); // false
  print(identical(p0, p3)); // true!
}

  
   

编译时常量被规范化。这意味着无论写“ const Point(0,0)”多少次,都只能创建一个对象。这可能很有用-但效果不尽如人意,因为需要使用const变量来保存值并使用该变量。
编译时常量到底有什么用呢?

    它们对于枚举很有用。
    可以在切换情况下使用编译时常量值。
    它们用作注释。

在Dart切换到延迟初始化变量之前,编译时常量曾经更为重要。在此之前,只能声明一个初始化的全局变量,例如“ var x = foo;”。如果“ foo”是编译时常量。没有这个要求,大多数程序可以在不使用任何const对象的情况下编写
普通构造函数

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

class Point {
  num x, y;

  Point(num x, num y) {
    // 还有更好的方式来实现下面代码,敬请关注。
    this.x = x;
    this.y = y;
  }
}

  

使用 this 关键字引用当前实例。

    提示: 仅当存在命名冲突时,使用 this 关键字。 否则,按照 Dart 风格应该省略 this 。

通常模式下,会将构造函数传入的参数的值赋值给对应的实例变量, Dart 自身的语法糖精简了这些代码:

class Point {
  num x, y;

  // 在构造函数体执行前,
  // 语法糖已经设置了变量 x 和 y。
  Point(this.x, this.y);
}

  

默认构造函数

在没有声明构造函数的情况下, Dart 会提供一个默认的构造函数。 默认构造函数没有参数并会调用父类的无参构造函数。
构造函数不被继承

子类不会继承父类的构造函数。 子类不声明构造函数,那么它就只有默认构造函数 (匿名,没有参数) 。
命名构造函数

使用命名构造函数可为一个类实现多个构造函数, 也可以使用命名构造函数来更清晰的表明函数意图:

class Point {
  num x, y;

  Point(this.x, this.y);

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

   

切记,构造函数不能够被继承, 这意味着父类的命名构造函数不会被子类继承。 如果希望使用父类中定义的命名构造函数创建子类, 就必须在子类中实现该构造函数。
调用父类非默认构造函数

默认情况下,子类的构造函数会自动调用父类的默认构造函数(匿名,无参数)。 父类的构造函数在子类构造函数体开始执行的位置被调用。 如果提供了一个 initializer list (初始化参数列表), 则初始化参数列表在父类构造函数执行之前执行。 总之,执行顺序如下:

    initializer list (初始化参数列表)
    superclass’s no-arg constructor (父类的无名构造函数)
    main class’s no-arg constructor (主类的无名构造函数)

如果父类中没有匿名无参的构造函数, 则需要手工调用父类的其他构造函数。 在当前构造函数冒号 ( : ) 之后,函数体之前,声明调用父类构造函数。

下面的示例中,Employee 类的构造函数调用了父类 Person 的命名构造函数。

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

    1
    2

由于父类的构造函数参数在构造函数执行之前执行, 所以参数可以是一个表达式或者一个方法调用:

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

  

重定向构造函数

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

class Point {
  num x, y;

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

  // 指向主构造函数
  Point.alongXAxis(num x) : this(x, 0);
}