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;
}
调用父类构造函数
- 默认情况下,子类的构造函数会调用父类的无参数构造函数,并且该调用会在子类构造函数的函数体代码执行前
- 如果子类构造函数还有一个 初始化列表,那么该初始化列表会在调用父类的该构造函数之前被执行
- 如果父类没有无参数构造函数,那么子类必须显式调用父类的其中一个构造函数
调用顺序:
- 初始化列表
- 父类的无参数构造函数
- 当前类的构造函数
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 aconst 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 是一对用来读写对象属性的特殊方法,可以使用 get
和 set
关键字为额外的属性添加 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 已经自动进行了继承,所以枚举类不能再继承其他类
- 不能重载
index
、hashCode
和比较操作符==
- 不能声明
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
本文来自博客园,作者:白乾涛,转载请注明原文链接:https://www.cnblogs.com/baiqiantao/p/17570101.html