Android学Dart学习笔记第十六节 类-构造方法

序言

在dart中,允许多种形式的构造方法,上篇类中,也有涉略。在这篇文章中我们进行深入的学习。

构造 方法的类型

Generative constructors、Default constructors、Named constructors、Constant constructors、Redirecting constructors、Factory constructors、Redirecting factory constructors以及Constructor tear-offs

这些都是官方文档中给到的构造方法的类型,下面我们逐一了解

Generative constructors

是一种类似java的构造函数,基础的

dart 复制代码
class Point {
  // Instance variables to hold the coordinates of the point.
  double x;
  double y;

  // Generative constructor with initializing formal parameters:
  Point(this.x, this.y);
}

Default constructors

默认构造,当你没有明确声明构造方法时,就会使用这个

Named constructors

命名构造,当你给构造函数起了一个其他名字时,就是命名构造,但命名必须遵循class.name的形式,如下

dart 复制代码
const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  final double x;
  final double y;

  // Sets the x and y instance variables
  // before the constructor body runs.
  Point(this.x, this.y);

  // Named constructor
  Point.origin() : x = xOrigin, y = yOrigin;
}

子类不会继承父类的命名构造

Constant constructors

如果类生成不变的对象 ,将这些对象设置为编译时常量。要使对象成为编译时常量,定义一个const构造函数 ,并将所有实例变量设置为final

dart 复制代码
class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);

  final double x, y;

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

常量构造函数并不总是创建常量。它们可以在非常量上下文中调用。

如下

dart 复制代码
void main() {
  const ImmutablePoint p = ImmutablePoint(10, 20);
  var p1 = const ImmutablePoint(10, 20);
  var p2 = ImmutablePoint(10, 20);//这个叫非常量上下文
  print(p == p1);//true
  print(p == p2);//false
  print(p1 == p2);//false
}
class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);

  final double x, y;

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

Redirecting constructors

构造函数可以重定向到同一个类中的另一个构造函数。重定向构造函数有一个空主体。构造函数使用this代替冒号(:)后面的类名。

dart 复制代码
class Point {
  double x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(double x) : this(x, 0);
}

Factory constructors

这个上篇文章也讲到了

当遇到下列两种实现构造函数的情况之一时,请使用factory关键字:

构造函数并不总是创建类的新实例。尽管工厂构造函数不能返回null,但它可能返回:

复制代码
从缓存中创建一个现有实例,而不是创建一个新的实例

子类型的新实例

在构建实例之前,你需要执行一些重要的工作。这可能包括检查参数或进行任何在初始化列表中无法处理的其他处理。

dart 复制代码
var logger = Logger('UI');
logger.log('Button clicked');

var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);

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

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

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

工厂构造不能访问this

Redirecting factory constructors

有了上面的经验这个也不难理解了

dart 复制代码
factory Listenable.merge(List<Listenable> listenables) = _MergingListenable

普通的工厂构造函数似乎可以创建和返回其他类的实例。这将使重定向工厂变得不必要。

重定向工厂有几个优点:

抽象类可能会提供一个使用另一个类的常量构造函数的常量构造函数。

重定向工厂构造函数避免了转发器重复形式参数及其默认值的需要。

Constructor tear-offs

支持撕开,看下面的例子

dart 复制代码
final List<int> charCodes = [72, 101, 108, 108, 111, 33]; // "Hello!"

// Use a tear-off for a named constructor:
var strings = charCodes.map(String.fromCharCode);

// Use a tear-off for an unnamed constructor:
var buffers = charCodes.map(StringBuffer.new);


上面的代码中使用了lamada,去除后如下

dart 复制代码
// Instead of a lambda for a named constructor:
var strings = charCodes.map((code) => String.fromCharCode(code));

// Instead of a lambda for an unnamed constructor:
var buffers = charCodes.map((code) => StringBuffer(code));

Instance variable initialization 实例变量初始化

dart中有3种变量初始化方式

Initialize instance variables in the declaration

声明时初始化

dart 复制代码
class PointA {
  double x = 1.0;
  double y = 2.0;

  // The implicit default constructor sets these variables to (1.0,2.0)
  // PointA();

  @override
  String toString() {
    return 'PointA($x,$y)';
  }
}

Use initializing formal parameters

使用初始化形式参数,初始化参数也可以是可选的

dart 复制代码
class PointB {
  final double x;
  final double y;

  // Sets the x and y instance variables
  // before the constructor body runs.
  PointB(this.x, this.y);

  // 初始化参数也可以是可选参数
  PointB.optional([this.x = 0.0, this.y = 0.0]);
}

私有字段不能用作命名初始化形式变量。可以使用下面的方式

dart 复制代码
class PointB {
// ...

  PointB.namedPrivate({required double x, required double y})
      : _x = x,
        _y = y;

// ...
}

上面的写法也适用于命名变量

dart 复制代码
class PointC {
  double x; // must be set in constructor
  double y; // must be set in constructor

  // Generative constructor with initializing formal parameters
  // with default values
  PointC.named({this.x = 1.0, this.y = 1.0});

  @override
  String toString() {
    return 'PointC.named($x,$y)';
  }
}

// Constructor using named variables.
final pointC = PointC.named(x: 2.0, y: 2.0);

所有通过初始化形式参数引入的变量都是final变量,并且只在初始化变量的作用域内。

要执行无法在初始化列表中表达的逻辑,请使用该逻辑创建工厂构造函数或静态方法。然后,您可以将计算值传递给普通构造函数。

构造函数的参数可以声明为可空类型,并且可以被初始化或不被初始化

dart 复制代码
class PointD {
  double? x; // null if not set in constructor
  double? y; // null if not set in constructor

  // Generative constructor with initializing formal parameters
  PointD(this.x, this.y);

  @override
  String toString() {
    return 'PointD($x,$y)';
  }
}

Use an initializer list

使用初始化列表

在运行构造函数体之前,可以初始化实例变量。用逗号分隔初始化方法。

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

初始化列表的右侧不能访问this

在开发期间,可以通过初始话列表使用assert验证输入参数

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

初始化列表也可以用来设置final值

如下

dart 复制代码
class A {

  final int x;

  // A(this.x);
  A(int u) : x = u;

}

Constructor inheritance

构造函数的继承

子类不会从超类或直接父类继承构造方法。如果类没有声明构造方法,那么只能使用默认的构造方法。

类可以继承超类的参数。这些称为超参数(super parameter)

构造函数的工作方式与调用静态方法链的方式有些类似。每个子类都可以调用超类的构造函数来初始化实例,就像子类调用超类的静态方法一样。这个过程不会"继承"构造函数体或签名。

Non-default superclass constructors

非默认超类构造

Dart按以下顺序执行构造函数:

初始化器列表

超类的未命名、无参数的构造函数

主类的无参数构造函数

如果超类没有未命名的、无参数的构造方法,就调用超类中的一个构造方法。在构造函数主体之前(如果有的话),在冒号(:)之后指定超类的构造函数。

dart 复制代码
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().
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

由于Dart在调用构造函数之前计算超类构造函数的参数,因此参数可以是类似于函数调用的表达式

dart 复制代码
class Employee extends Person {
  Employee() : super.fromJson(fetchDefaultData());
  // ···
}

超类构造函数的参数无法访问this。例如,参数可以调用静态方法,但不能调用实例方法

Super parameters

为了避免将每个参数都传入构造函数的父类调用中,可以使用 super-initializer 参数(超类初始化参数)将参数直接转发到指定或默认的父类构造函数。

此功能不能与重定向构造函数一起使用。

dart 复制代码
class Dog extends Animal {
  final String breed;
  
  // 传统方式:需要将 name 手动传递给 super
  Dog(String name, this.breed) : super(name);
}
dart 复制代码
class Dog extends Animal {
  final String breed;
  
  // 新方式:使用 super-initializer 参数
  Dog(super.name, this.breed);
  // ↑ super.name 自动将 name 参数转发给父类 Animal 构造函数
}

下面是个错误案例

dart 复制代码
  Vector3d.xAxisError(super.x): z = 0, super(0); // BAD

这个命名构造函数试图设置x的值两次:一次是在super构造函数中,另一次是作为位置超参数。因为它们都指向位置参数x,所以会导致错误。

当超类构造函数有命名参数时,你可以将它们分配到:

命名的 super 参数(如下例中的 super.y)

超类构造函数调用中的命名参数(如 super.named(x: 0))

dart 复制代码
class Vector2d {
  // ...
  Vector2d.named({required this.x, required this.y});
}

class Vector3d extends Vector2d {
  final double z;

  // Forward the y parameter to the named super constructor like:
  // Vector3d.yzPlane({required double y, required this.z})
  //       : super.named(x: 0, y: y);
  Vector3d.yzPlane({required super.y, required this.z}) : super.named(x: 0);
}

小结

太灵活了

相关推荐
TimeFine2 小时前
Android AI解放生产力(一):开始使用Codex Cli
android
代码游侠2 小时前
学习笔记——线程控制 - 互斥与同步
linux·运维·笔记·学习·算法
爱吃泡芙的小白白2 小时前
Agent学习——xiaomi MiMo V2 Flash大模型的API申请
学习·agent·xiaomi mimo
四谎真好看2 小时前
MySQL 学习笔记(进阶篇1)
笔记·学习·mysql·学习笔记
三品吉他手会点灯2 小时前
STM32F103学习笔记-19-SysTick-系统定时器(第1节)-功能框图讲解和优先级配置
笔记·stm32·单片机·嵌入式硬件·学习
小易吾2 小时前
VISIO导出高清PDF有效方法
笔记·pdf
لا معنى له2 小时前
学习笔记:Transformer
人工智能·笔记·深度学习·学习·机器学习·transformer
JosieBook2 小时前
【Vue】03 Vue技术——Vue.js 入门学习笔记:Hello World 案例详解
vue.js·笔记·学习
li星野2 小时前
打工人日报#20251217
笔记