首页 > 其他分享 >Flutter/Dart第15天:Dart类构造函数

Flutter/Dart第15天:Dart类构造函数

时间:2023-10-22 19:13:25浏览次数:37  
标签:15 Point double class Dart 父类 final 构造函数

Dart官方文档:https://dart.dev/language/constructors

重要说明:本博客基于Dart官网文档,但并不是简单的对官网进行翻译,在覆盖核心功能情况下,我会根据个人研发经验,加入自己的一些扩展问题和场景验证。

如下代码样例,和Java类似,最常用的生成式构造函数:

class Point {
  double x = 0;
  double y = 0;

  Point(double x, double y) {
    this.x = x;
    this.y = y;
  }
}

最佳实战:在Dart中,仅当命名冲突时,才使用this关键字,否则一般可以省略this关键字。

初始化参数列表

如上最常用的构造函数,Dart可以进一步优化如下初始化参数形式。同时,也可以为非空变量设置默认值。

class Point {
  final double x;
  final double y;

  // 在构造函数体执行之前,初始化实例变量
  Point(this.x, this.y);
}

默认构造函数

和Java类似,类如果没有申明构造函数,那么它会有个默认的构造函数。默认构造函数没有入参,它只会调用父类的没有入参的构造函数。

构造函数无法继承

子类无法继承父类的构造函数,如果子类没有申明构造函数,那么这个子类就只有默认构造函数(无论父类是否有其他构造函数)。

命名构造函数

在Dart中,通过命名构造函数,可以为类提供多个构造函数,并且在创建对象时更加清晰。

const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  final double x;
  final double y;

  Point(this.x, this.y);

  // `origin`命名构造函数
  Point.origin()
      : x = xOrigin,
        y = yOrigin;
}

特别注意:如上节提到,子类无法继承父类的构造函数,包括父类的命名构造函数。如果子类想使用父类的某个命名构造函数,那么子类必须实现该命名构造函数。

调用父类构造函数

默认情况下,子类无入参的非命名构造函数会调用父类的无入参的非命名构造函数。父类的构造函数在构造函数体之前被调用。如果有初始化参数列表,那么初始化参数列表在父类构造函数调用之前被调用。

构造函数相关的调用顺序如下:

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

特别注意:如果父类没有通过无入参且非命名的构造函数,那么我们必须手工调用父类的一个构造函数,通过冒号:后面紧跟父类构造函数。

如下代码样例,Person是父类,它仅申明了一个命名构造参数。Employee是继承Person的子类,由于父类没有申明无入参非命名的构造函数,因此在它构造函数都必须手工调用父类的某个构造函数。如命名构造函数fromJson后面,通过冒号:调用了父类的命名构造函数。

// 未申明:无入参、非命名的构造函数
class Person {
  String? firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // 手工调用父类的构造函数:super.fromJson()
  Employee.fromJson(super.data) : super.fromJson() {
    print('in Employee');
  }
}

void main() {
  var employee = Employee.fromJson({});
  print(employee);
  // 结果:
  // in Person
  // in Employee
  // Instance of 'Employee'
}

特别注意:由于构造函数参数是在调用构造函数之前计算,因此构造函数的参数可以是表达式,如函数调用等。父类的构造函数不能使用this.关键字,因为参数可以是表达式、静态函数等,并不一定是类实例。

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

手工调用父类的构造函数,然后逐一设置入参比较繁琐,如果我们想要简化,那么可以父类的初始值构造函数。这个功能不能与重定向构造函数一起使用(因为语法冲突)。

class Vector2d {
  final double x;
  final double y;

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

class Vector3d extends Vector2d {
  final double z;

  // 默认情况下,我们的使用方法:
  // Vector3d(final double x, final double y, this.z) : super(x, y);
  
  // 简化版本:
  Vector3d(super.x, super.y, this.z);
}

如下代码样例,父类的初始化构造函数可以通过命名参数调用。

class Vector2d {
  // ...

  Vector2d.named({required this.x, required this.y});
}

class Vector3d extends Vector2d {
  // ...

  Vector3d.yzPlane({required super.y, required this.z}) : super.named(x: 0);

  // 等价调用
  // Vector3d.yzPlane({required double y, required this.z}) : super.named(x: 0, y: y);
}

初始化列表

在构造函数执行之前,我们可以调用父类的构造函数,还可以初始化实例变量。实例变量初始化通过逗号分隔。

Point.fromJson(Map<String, double> json)
    : x = json['x']!,
      y = json['y']! {
  print('In Point.fromJson(): ($x, $y)');
}

开发阶段,我们可以在初始化列表中增加断言:

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

初始化列表在设置final不可变量时非常有用:

import 'dart:math';

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

  Point(double x, double y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

void main() {
  var p = Point(2, 3);
  print(p.distanceFromOrigin);
  // 输出:3.605551275463989
}

重定向构造函数

重定向构造函数,就是使用类的其他的构造函数,重定向到的构造函数使用this关键字:

class Point {
  double x, y;

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

  // 重定向到主构造函数
  Point.alongXAxis(double x) : this(x, 0);
}

常量构造函数

如果对象的数据不会改变,这些对象可以作为编译期常量。

常量构造函数要求:实例变量都是final不可变量,定义一个const修饰符的构造函数。

特别注意:上一文我们有提到常量构造函数,常量构造函数创建的对象并不一定<?都是常量(当创建的对象没有const修饰符,或者对象不是在const常量上下文中,那么该对象就不是常量)!

如下代码样例,ImmutablePoint有常量构造函数,它创建的3个对象中,前面2个是常量,后面1个并非常量。

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

  final double x, y;

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

// `a`和`b`对象是常量,且它们属于同一个实例
var a = const ImmutablePoint(1, 2);
var b = const ImmutablePoint(1, 2);

assert(identical(a, b));

// `c`对象并非常量,它也和`a`或者`b`不是同一个实例
var c = ImmutablePoint(1, 2);
assert(!identical(a, b));

工厂构造函数

在一个不总是创建实例的类中,使用factory关键字实现一个构造函数,即为工厂构造函数。

工厂构造函数常用使用场景:如通过构造函数,从缓存获取对象,或者创建其子类,或者创建不可变常量但是又不想提供初始化参数列表等。

如下代码样例,Logger工厂构造函数优先从缓存获取对象,而Logger.fromJson()工厂构造函数则初始化了一个final不可实例变量:

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

  static final Map<String, Logger> _cache = <String, Logger>{};

  // 优先从缓存获取对象,如果不存在则新增
  factory Logger(String name) {
    print('默认构造函数:$name');
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  // 命名构造函数,通过工厂构造函数获取对象(缓存,或新增)
  factory Logger.fromJson(Map<String, Object> json) {
    print('命名构造函数:$json');
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

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

工厂构造函数的使用,和普通构造函数无本质区别:

var logger = Logger('UI');
logger.log('Hi NTopicCN.');
// 结果:
// 默认构造函数:UI
// Hi NTopicCN.

var loggerJson = Logger.fromJson({'name': 'UI'});
loggerJson.log('Hello Logger.');
// 结果:
// 命名构造函数:{name: UI}
// 默认构造函数:UI
// Hello Logger.

我的本博客原地址:https://ntopic.cn/p/2023102101


标签:15,Point,double,class,Dart,父类,final,构造函数
From: https://www.cnblogs.com/obullxl/p/NTopic2023102101.html

相关文章

  • Python教程(15)——Python流程控制语句详解
    Python流程控制是Python编程中非常重要的一部分,它用于控制程序的执行流程。Python提供了多种流程控制语句,包括if语句、while循环、for循环、break和continue语句等。这种流程控制在各个语言中都是大同小异的,如果你已经学过其他的语言,那么这章节就可以直接跳过。if语句if语句用于......
  • Xcode 15.0.1 (15A507) 发布下载 - Apple 平台 IDE
    Xcode15.0.1(15A507)-Apple平台IDEIDEforiOS/iPadOS/macOS/watchOS/tvOS/visonOS请访问原文链接:https://sysin.org/blog/apple-xcode-15/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.orgvisonOS支持已更新。Xcode15使您能够为所有Apple平台开发、测......
  • 15_File类与IO流
    ......
  • Python教程(15)——Python流程控制语句详解
    Python流程控制是Python编程中非常重要的一部分,它用于控制程序的执行流程。Python提供了多种流程控制语句,包括if语句、while循环、for循环、break和continue语句等。这种流程控制在各个语言中都是大同小异的,如果你已经学过其他的语言,那么这章节就可以直接跳过。if语句if语句用......
  • Flutter/Dart第14天:Dart类详解
    Dart官方文档:https://dart.dev/language/classes重要说明:本博客基于Dart官网文档,但并不是简单的对官网进行翻译,在覆盖核心功能情况下,我会根据个人研发经验,加入自己的一些扩展问题和场景验证。Dart类Dart语言基于类和Mixin继承,是一门面向对象语言。任何对象都是某个类的实例,除Nu......
  • ZJOI2015 地震后的幻想乡
    「ZJOI2015」地震后的幻想乡前言:想了很久,最后只能失败告终。基本分析到了一半,只是没有将其转化为古典概型后考虑求解方案数。说实话有点可惜……题意:给定一张\(n\)个点\(m\)条边的无向连通图,每条边的边权是\([0,1]\)之间的随机实数,求其最小生成树上最大边权的期望值......
  • Codeforces Round 902 (Div. 2, based on COMPFEST 15 - Final Round)
    \(D.EffectsofAntiPimples\)对每个数字能到达的所有位置先预处理最大值,那么就代表选择这个数字之后真实的贡献,那么对这样的预处理值,最小值显然只有一种做法,为\(2^0\),第二小的值应该可以与最小值一起选择,所以答案为\(2^1\),以此类推之后,每个值乘上对应的2的幂次之后求和即......
  • [915] Implementation of zooming to layer and exporting to PDF in arcpy
    ref:Camera-ArcGISProref:Introductiontoarcpy.mp#Setthepathtoyourprojectfile(.aprx)project_file=r"Map1.3Heritage.aprx"#Referencetheprojectaprx=arcpy.mp.ArcGISProject(project_file)#getthesitebufferlayerm=aprx......
  • 用惨痛教训换来的156条MySQL设计规约
    怎么才能很好地避免低级故障?以下规范在大型互联网公司经过了充分验证,尤其适用于并发量大、数据量大的业务场景。 在设计数据库技术方案时,我们是有自己的设计理念或者原则,还是更多依据直觉去设计?是否曾经懊悔线上发生过的一次低级故障?是否思考过怎样才能避免?......
  • gcc对构造函数的调用生成
    identifierC++的前端对identifier做了扩展,在每个identifier中还包含了两个cxx_binding字段:namespace_bindings和bindings。当通过字符串找到一个identifier的时候,同时顺带获得了两个binding信息。/*Language-dependentcontentsofanidentifier.*/structGTY(())lang_id......