Methods 方法
方法是为对象提供行为的函数。
Instance methods 实例方法
对象上的实例方法可以访问实例变量和 this
.以下示例中的 distanceTo()
方法是实例方法的一个示例:
import 'dart:math';
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);
double distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
Operators 运算符
大多数运算符是具有特殊名称的实例方法。Dart 允许你使用以下名称定义运算符:
< | > | <= | >= | == | ~ |
- | + | / | ~/ | * | % |
| | ˆ | & | << | >>> | >> |
[]= | [] |
信息提示
您可能已经注意到,某些运算符 (如 !=
)不在名称列表中。这些运算符不是实例方法。他们的行为是 Dart 内置的。
要声明运算符,请使用内置标识符 运算符
,则为要定义的运算符。以下示例定义向量加法 (+)、
减法 (-
) 和相等 (==
):
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 other) =>
other is Vector && x == other.x && y == other.y;
@override
int get hashCode => Object.hash(x, 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));
}
Getters and setters getter 和 setter
getter 和 setter 是提供读取和写入的特殊方法 访问对象的属性。回想一下,每个实例变量都有 隐式 getter,如果合适,还会加上 setter。您可以创建 其他属性,方法是使用 get
和 set
关键字:
class Rectangle {
double left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// Define two calculated properties: right and bottom.
double get right => left + width;
set right(double value) => left = value - width;
double get bottom => top + height;
set bottom(double value) => top = value - height;
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
使用 getter 和 setter,您可以从实例变量开始,然后用方法包装它们,所有这些都无需更改客户端代码。
信息提示
诸如 increment (++) 之类的运算符以预期的方式工作,无论是否显式定义了 getter。为避免任何意外的副作用,运算符只调用 getter 一次,将其值保存在临时变量中。
Abstract methods 抽象方法
instance、getter 和 setter 方法可以是抽象的,定义一个接口,但将其实现留给其他类。抽象方法只能存在于抽象类或 mixin中。
要使方法抽象化,请使用分号 (;
) 而不是方法主体:
abstract class Doer {
// Define instance variables and methods...
void doSomething(); // Define an abstract method.
}
class EffectiveDoer extends Doer {
void doSomething() {
// Provide an implementation, so the method is not abstract here...
}
}
Extend a class 扩展类
使用 extends
创建一个子类,使用 super
来引用超类:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
有关 extends
的另一种用法,请参阅 Generics (泛型) 页面上的参数化类型 。
Overriding members 覆盖成员
子类可以覆盖实例方法(包括运算符 )、getter 和 setter。您可以使用 @override
注释来指示您有意覆盖成员:
class Television {
// ···
set contrast(int value) {
...
}
}
class SmartTelevision extends Television {
@override
set contrast(num value) {
...
}
// ···
}
重写方法声明必须以多种方式匹配它重写的方法(或方法):
- 返回类型必须与被覆盖方法的返回类型相同(或是其子类型)。
- 参数类型必须与被覆盖方法的参数类型相同(或为超类型)。在前面的示例中,
SmartTV
的contrast
setter 将参数类型从int
更改为超类型num
。 - 如果被覆盖的方法接受 n 个位置参数,则覆盖方法也必须接受 n 个位置参数。
- 泛型方法不能覆盖非泛型方法,非泛型方法也不能覆盖泛型方法。
有时您可能希望缩小 方法参数或实例变量。 这违反了正常规则,并且 它与 downcast 类似,因为它可以在运行时导致类型错误。 不过,缩小类型范围是可能的 如果代码可以保证不会发生类型错误。 在这种情况下,您可以使用 covariant 关键字 在参数声明中。
警告请注意
如果你覆盖 ==
,你也应该覆盖 Object 的 hashCode
getter。有关重写 ==
和 hashCode
的示例,请查看 实现 map 键 。
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}',
);
}
}
除非 以下情况之一为 true:
-
接收器具有 static 类型
dynamic
。 -
接收器具有定义未实现方法的 static 类型(abstract 也可以),接收器的 dynamic 类型具有
noSuchMethod()
的实现 这与Object
类中的那个不同。
Mixins 混入
Mixin 是一种定义代码的方法,可以在多个类层次结构中重用。它们旨在提供整体成员实现。
要使用 mixin,请使用 with
关键字,后跟一个或多个 mixin 名称。以下示例显示了两个使用 (或是) mixin 的子类的类:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
要定义 mixin,请使用 mixin
声明。在极少数情况下,您需要同时定义 mixin 和 class,您可以使用 mixin 类声明 。
Mixin 和 mixin 类不能有 extends
子句,并且不得声明任何生成构造函数。
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}
Specify members a mixin can call on itself
指定 mixin 可以调用自身的成员
有时 mixin 依赖于能够调用方法或访问字段,但不能自己定义这些成员(因为 mixin 不能使用构造函数参数来实例化自己的字段)。
以下部分介绍了不同的策略,以确保 mixin 的任何子类定义 mixin 的行为所依赖的任何成员。
Define abstract members in the mixin
在 mixin 中定义抽象成员
在 mixin 中声明抽象方法会强制使用该 mixin 的任何类型来定义其行为所依赖的抽象方法。
mixin Musician {
void playInstrument(String instrumentName); // Abstract method.
void playPiano() {
playInstrument('Piano');
}
void playFlute() {
playInstrument('Flute');
}
}
class Virtuoso with Musician {
@override
void playInstrument(String instrumentName) { // Subclass must define.
print('Plays the $instrumentName beautifully');
}
}
访问 mixin 的子类中的 state
声明 abstract 成员还允许您通过在 mixin 上调用定义为 abstract 的 getter 来访问 mixin 的子类上的 state:
/// Can be applied to any type with a [name] property and provides an
/// implementation of [hashCode] and operator `==` in terms of it.
mixin NameIdentity {
String get name;
@override
int get hashCode => name.hashCode;
@override
bool operator ==(other) => other is NameIdentity && name == other.name;
}
class Person with NameIdentity {
final String name;
Person(this.name);
}
Implement an interface 实现接口
与声明 mixin 抽象类似,在 mixin 上放置 implements
子句,同时不实际实现接口,也将确保为 mixin 定义任何成员依赖项。
abstract interface class Tuner {
void tuneInstrument();
}
mixin Guitarist implements Tuner {
void playSong() {
tuneInstrument();
print('Strums guitar majestically.');
}
}
class PunkRocker with Guitarist {
@override
void tuneInstrument() {
print("Don't bother, being out of tune is punk rock.");
}
}
Use the on
clause to declare a superclass
使用 on
子句声明超类
on
子句用于定义解析 super
调用所针对的类型。因此,仅当您需要在 mixin 中进行 super
调用时,才应该使用它。
on
子句强制任何使用 mixin 的类也成为 on
子句中类型的子类。如果 mixin 依赖于超类中的成员,则可以确保这些成员在使用 mixin 的地方可用:
class Musician {
musicianMethod() {
print('Playing music!');
}
}
mixin MusicalPerformer on Musician {
performerMethod() {
print('Performing music!');
super.musicianMethod();
}
}
class SingerDancer extends Musician with MusicalPerformer { }
main() {
SingerDancer().performerMethod();
}
在此示例中,只有扩展或实现 Musician
类的类才能使用 mixin MusicalPerformer
。因为 SingerDancer
扩展了 Musician
, SingerDancer
可以在 MusicalPerformer
中混音。
class
, mixin
, or mixin class
?
类
、Mixin
还是 Mixin 类
?
mixin
声明定义 mixin。 类
声明定义类 。mixin 类
声明定义了一个类,该类既可用作常规类,也可用作 mixin,具有相同的名称和类型。
mixin class Musician {
// ...
}
class Novice with Musician { // Use Musician as a mixin
// ...
}
class Novice extends Musician { // Use Musician as a class
// ...
}
适用于类或 mixin 的任何限制也适用于 mixin 类:
- Mixin 不能有
extends
或with
子句,所以Mixin 类
也不能。 - 类不能有
on
子句,所以mixin 类
也不能。
Enumerated types 枚举类型
枚举类型(通常称为枚举或枚举 )是一种特殊的类,用于表示固定数量的常量值。
信息提示
所有枚举都会自动扩展 Enum 类。它们也是密封的,这意味着它们不能被子类化、实现、混合或以其他方式显式实例化。
抽象类和 mixin 可以显式实现或扩展 Enum
,但除非它们随后由 enum 声明实现或混合到枚举声明中,否则任何对象都无法实际实现该类或 mixin 的类型。
Declaring simple enums 声明简单枚举
要声明简单的枚举类型,请使用 enum
关键字并列出要枚举的值:
enum Color { red, green, blue }
灯泡小提示
在声明枚举类型时,还可以使用尾随逗号来帮助防止复制粘贴错误。
Declaring enhanced enums
声明增强的枚举
Dart 还允许 enum 声明使用字段、方法和 const 构造函数来声明类,这些函数被限制为固定数量的已知常量实例。
要声明增强的枚举,请遵循类似于普通类的语法,但有一些额外的要求:
- 实例变量必须是
final
,包括由 mixin 添加的变量。 - 所有生成构造函数都必须是常量。
- 工厂构造函数只能返回一个固定的已知枚举实例。
- 由于 Enum 是自动扩展的,因此不能扩展其他类。
不能有 index
、hashCode
、相等运算符==
的覆盖。- 名为
values
的成员不能在枚举中声明,因为它会与自动生成的静态值
getter 冲突。 - 枚举的所有实例都必须在声明的开头声明,并且必须至少声明一个实例。
增强枚举中的实例方法可以使用它来引用
当前枚举值。
下面是一个示例,该示例声明了具有多个实例、实例变量、getter 和已实现接口的增强枚举:
enum Vehicle implements Comparable<Vehicle> {
car(tires: 4, passengers: 5, carbonPerKilometer: 400),
bus(tires: 6, passengers: 50, carbonPerKilometer: 800),
bicycle(tires: 2, passengers: 1, carbonPerKilometer: 0);
const Vehicle({
required this.tires,
required this.passengers,
required this.carbonPerKilometer,
});
final int tires;
final int passengers;
final int carbonPerKilometer;
int get carbonFootprint => (carbonPerKilometer / passengers).round();
bool get isTwoWheeled => this == Vehicle.bicycle;
@override
int compareTo(Vehicle other) => carbonFootprint - other.carbonFootprint;
}
Using enums 使用枚举
#
像访问任何其他静态变量一样访问枚举值:
final favoriteColor = Color.blue;
if (favoriteColor == Color.blue) {
print('Your favorite color is blue!');
}
枚举中的每个值都有一个索引
getter,它返回枚举声明中值的从零开始的位置。例如,第一个值的索引为 0,第二个值的索引为 1。
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
要获取所有枚举值的列表,请使用枚举的 values
常量。
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
您可以在 switch 语句中使用枚举,如果您不处理枚举的所有值,则会收到警告:
var aColor = Color.blue;
switch (aColor) {
case Color.red:
print('Red as roses!');
case Color.green:
print('Green as grass!');
default: // Without this, you see a WARNING.
print(aColor); // 'Color.blue'
}
如果需要访问枚举值的名称,例如 Color.blue
中的 'blue'
,请使用 .name
属性:
print(Color.blue.name); // 'blue'
您可以像在普通对象上一样访问 enum 值的成员:
print(Vehicle.car.carbonFootprint);
Extension 方法
扩展方法向现有库添加功能。你可能在不知情的情况下使用扩展方法。例如,当您在 IDE 中使用代码完成时,它会建议扩展方法和常规方法。
Overview
当您使用其他人的 API 或实现广泛使用的库时,更改 API 通常是不切实际或不可能的。但您可能仍需要添加一些功能。
例如,请考虑以下将字符串分析为整数的代码:
int.parse('42')
将该功能放在 String
上可能会更好 — 更短且更易于与工具一起使用:
'42'.parseInt()
要启用该代码,您可以导入包含 String
类扩展的库:
import 'string_apis.dart';
void main() {
print('42'.parseInt()); // Use an extension method.
}
扩展不仅可以定义方法,还可以定义其他成员,例如 getter、setter 和运算符。此外,扩展可以具有名称,这在出现 API 冲突时可能会有所帮助。以下是使用对字符串进行作的扩展(名为 NumberParsing
)实现扩展方法 parseInt()
的方法:
lib/string_apis.dart
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
}
Using extension methods
和所有的 Dart 代码一样,扩展方法也位于库中。您已经了解了如何使用扩展方法——只需导入它所在的库,然后像普通方法一样使用它:
// Import a library that contains an extension on String.
import 'string_apis.dart';
void main() {
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.
}
这就是使用扩展方法通常需要了解的全部内容。在编写代码时,您可能还需要了解扩展方法如何依赖于静态类型(而不是动态
类型)以及如何解决 API 冲突 。
Static types and dynamic
静态类型和动态
您不能对 dynamic
类型的变量调用扩展方法。例如,以下代码会导致运行时异常:
dynamic d = '2';
print(d.parseInt()); // Runtime exception: NoSuchMethodError
扩展方法确实可以与 Dart 的类型推断一起使用。以下代码很好,因为变量 v
被推断为具有 String
类型:
var v = '2';
print(v.parseInt()); // Output: 2
dynamic
不起作用的原因是扩展方法是针对接收器的 static 类型进行解析的。由于扩展方法是静态解析的,因此它们与调用静态函数一样快。
API conflicts
如果扩展成员与接口或其他扩展成员冲突,则您有几个选项。
一个选项是更改导入冲突扩展的方式,使用 show
或 hide
来限制公开的 API:
// Defines the String extension method parseInt().
import 'string_apis.dart';
// Also defines parseInt(), but hiding NumberParsing2
// hides that extension method.
import 'string_apis_2.dart' hide NumberParsing2;
void main() {
// Uses the parseInt() defined in 'string_apis.dart'.
print('42'.parseInt());
}
另一种选择是显式应用扩展,这会导致代码看起来好像扩展是一个包装类:
// Both libraries define extensions on String that contain parseInt(),
// and the extensions have different names.
import 'string_apis.dart'; // Contains NumberParsing extension.
import 'string_apis_2.dart'; // Contains NumberParsing2 extension.
void main() {
// print('42'.parseInt()); // Doesn't work.
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());
}
如果两个扩展具有相同的名称,则可能需要使用前缀导入:
// Both libraries define extensions named NumberParsing
// that contain the extension method parseInt(). One NumberParsing
// extension (in 'string_apis_3.dart') also defines parseNum().
import 'string_apis.dart';
import 'string_apis_3.dart' as rad;
void main() {
// print('42'.parseInt()); // Doesn't work.
// Use the ParseNumbers extension from string_apis.dart.
print(NumberParsing('42').parseInt());
// Use the ParseNumbers extension from string_apis_3.dart.
print(rad.NumberParsing('42').parseInt());
// Only string_apis_3.dart has parseNum().
print('42'.parseNum());
}
如示例所示,即使您使用前缀导入,也可以隐式调用扩展方法。唯一需要使用前缀的时间是避免在显式调用扩展时发生名称冲突。
Implementing extension methods
实现扩展方法
使用以下语法创建扩展:
extension <extension name>? on <type> { // <extension-name> is optional
(<member definition>)* // Can provide one or more <member definition>.
}
例如,以下是在 String
类上实现扩展的方法:
lib/string_apis.dart
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
double parseDouble() {
return double.parse(this);
}
}
扩展的成员可以是 methods、getter、setter 或 operators。扩展还可以具有静态字段和静态帮助程序方法。要访问扩展声明之外的静态成员,请通过声明名称(如类变量和方法 )调用它们。
Unnamed extensions 未命名的扩展
声明扩展时,可以省略名称。未命名的扩展仅在声明它们的库中可见。由于它们没有名称,因此无法显式应用它们来解决 API 冲突 。
extension on String {
bool get isBlank => trim().isEmpty;
}
信息提示
只能在扩展声明中调用未命名扩展的静态成员。
Implementing generic extensions
实现泛型扩展
扩展可以具有泛型类型参数。例如,下面是一些代码,它使用 getter、运算符和方法扩展了内置的 List<T>
类型:
extension MyFancyList<T> on List<T> {
int get doubleLength => length * 2;
List<T> operator -() => reversed.toList();
List<List<T>> split(int at) => [sublist(0, at), sublist(at)];
}
类型 T
根据调用方法的列表的静态类型进行绑定。
Resources 资源
有关扩展方法的更多信息,请参阅以下内容:
- Article: Dart Extension Methods Fundamentals文章:Dart 扩展方法基础知识
- Feature specification 功能规格
- Extension methods sample扩展方法示例
Extension types 扩展类型
扩展类型是一种编译时抽象,它使用不同的、仅限静态的接口“包装”现有类型。它们是静态 JS 互作的主要组件,因为它们可以轻松修改现有类型的接口(对于任何类型的互作都至关重要),而不会产生实际包装器的成本。
扩展类型对基础类型的对象 (称为表示类型 ) 可用的作 (或接口) 强制执行规则。在定义扩展类型的接口时,可以选择重用表示类型的某些成员、省略其他成员、替换其他成员以及添加新功能。
以下示例包装 int
类型以创建仅允许对 ID 号有意义的作的扩展类型:
extension type IdNumber(int id) {
// Wraps the 'int' type's '<' operator:
operator <(IdNumber other) => id < other.id;
// Doesn't declare the '+' operator, for example,
// because addition does not make sense for ID numbers.
}
void main() {
// Without the discipline of an extension type,
// 'int' exposes ID numbers to unsafe operations:
int myUnsafeId = 42424242;
myUnsafeId = myUnsafeId + 10; // This works, but shouldn't be allowed for IDs.
var safeId = IdNumber(42424242);
safeId + 10; // Compile-time error: No '+' operator.
myUnsafeId = safeId; // Compile-time error: Wrong type.
myUnsafeId = safeId as int; // OK: Run-time cast to representation type.
safeId < IdNumber(42424241); // OK: Uses wrapped '<' operator.
}
信息提示
扩展类型的用途与包装类相同,但不需要创建额外的运行时对象,当您需要包装大量对象时,这可能会变得昂贵。由于扩展类型是纯静态的,并且在运行时编译掉,因此它们基本上是零成本。
扩展方法 (也称为 “扩展”)是类似于扩展类型的静态抽象。但是,扩展方法会直接添加功能 添加到其底层类型的每个实例中。 扩展类型不同; 扩展类型的接口仅适用于其 static type 为该扩展类型的表达式。默认情况下,它们不同于其底层类型的接口。
Syntax 语法
#Declaration 声明
#
使用扩展类型
声明和名称定义新的扩展类型,后跟括号中的表示类型声明 :
extension type E(int i) {
// Define set of operations.
}
制图表达类型声明 (int i)
指定扩展类型 E
的基础类型为 int
,并且对制图表达对象的引用命名为 i
。该宣言还介绍了:
- 表示对象的隐式 getter,表示类型为返回类型:
int get i
。 - 隐式构造函数:
E(int i) : i = i
.
表示对象为扩展类型提供对基础类型的对象的访问。该对象位于扩展类型 body 的范围内,你可以使用其名称作为 getter 来访问它:
- 在扩展类型 body 中使用
i
(或构造函数中的this.i
)。 - 使用
e.i
进行属性提取的外部 (其中e
将扩展类型作为其静态类型)。
扩展类型声明还可以包含类型参数 就像类或扩展一样:
extension type E<T>(List<T> elements) {
// ...
}
Constructors 构造 函数
您可以选择在扩展类型的主体中声明构造函数 。表示声明本身是一个隐式构造函数,因此默认情况下,它取代了扩展类型的未命名构造函数。任何其他非重定向生成构造函数必须在其初始化器列表或形式参数中使用 this.i
初始化表示对象的实例变量。
extension type E(int i) {
E.n(this.i);
E.m(int j, String foo) : i = j + foo.length;
}
void main() {
E(4); // Implicit unnamed constructor.
E.n(3); // Named constructor.
E.m(5, "Hello!"); // Named constructor with additional parameters.
}
或者,你可以将表示声明命名为 constructor,在这种情况下,主体中有一个未命名的构造函数的空间:
extension type const E._(int it) {
E(): this._(42);
E.otherName(this.it);
}
void main2() {
E();
const E._(2);
E.otherName(3);
}
您还可以完全隐藏构造函数,而不仅仅是定义一个新的构造函数,对类使用相同的私有构造函数语法 _
。例如,如果你只希望客户端使用 String
构造 E
,即使底层类型是 int
:
extension type E._(int i) {
E.fromString(String foo) : i = int.parse(foo);
}
您还可以声明转发生成构造函数或工厂构造函数 (也可以转发到子扩展类型的构造函数)。
Members 成员
在扩展类型的主体中声明成员,以定义其接口,就像定义类成员一样。扩展类型成员可以是方法、getter、setter 或运算符(非外部实例变量和抽象成员 不允许):
extension type NumberE(int value) {
// Operator:
NumberE operator +(NumberE other) =>
NumberE(value + other.value);
// Getter:
NumberE get myNum => this;
// Method:
bool isValid() => !value.isNegative;
}
默认情况下 ,表示类型的界面成员不是扩展类型的界面成员。要使表示类型的单个成员在扩展类型上可用,必须在扩展类型定义中为其编写一个声明,如 NumberE
中的运算符 +
。您还可以定义与表示类型无关的新成员,例如 i
getter 和 isValid
方法。
Implements 实现
#您可以选择使用 implements
子句来:
- 在扩展类型上引入子类型关系,AND
- 将 representation 对象的成员添加到扩展类型 interface。
implements
子句引入了适用性 关系,例如扩展方法与其 on
类型之间的关系。适用于超类型的成员也适用于子类型,除非该子类型具有具有相同成员名称的声明。
扩展类型只能实现:
-
其表示类型 。这使得表示类型的所有成员都隐式地可用于扩展类型。
extension type NumberI(int i) implements int{ // 'NumberI' can invoke all members of 'int', // plus anything else it declares here. }
-
其表示类型的超类型 。这使得超类型的成员可用,但不一定是表示类型的所有成员。
extension type Sequence<T>(List<T> _) implements Iterable<T> { // Better operations than List. } extension type Id(int _id) implements Object { // Makes the extension type non-nullable. static Id? tryParse(String source) => int.tryParse(source) as Id?; }
-
对同一表示类型有效的另一种扩展类型 。这允许跨多个扩展类型重用作(类似于多重继承)。
extension type const Opt<T>._(({T value})? _) { const factory Opt(T value) = Val<T>; const factory Opt.none() = Non<T>; } extension type const Val<T>._(({T value}) _) implements Opt<T> { const Val(T value) : this._((value: value)); T get value => _.value; } extension type const Non<T>._(Null _) implements Opt<Never> { const Non() : this._(null); }
@redeclare
声明与超类型成员共享名称的扩展类型成员不是类之间的重写关系,而是重新声明 。扩展类型成员声明将完全替换任何具有相同名称的 supertype 成员。无法为同一函数提供替代实现。
你可以使用 package:meta
中的 @redeclare 注解来告诉编译器你有意选择使用与超类型成员相同的名称。然后,如果这实际上不是真的,例如,如果其中一个名称输入错误,分析器将警告您。
import 'package:meta/meta.dart';
extension type MyString(String _) implements String {
// Replaces 'String.operator[]'.
@redeclare
int operator [](int index) => codeUnitAt(index);
}
您还可以启用 lint annotate_redeclares 在声明扩展类型方法时收到警告 ,这将隐藏 superInterface 成员, 并且不使用 @redeclare
进行批注。
Usage 用法
要使用扩展类型,请像使用 class 一样创建一个实例:通过调用构造函数:
extension type NumberE(int value) {
NumberE operator +(NumberE other) =>
NumberE(value + other.value);
NumberE get next => NumberE(value + 1);
bool isValid() => !value.isNegative;
}
void testE() {
var num = NumberE(1);
}
然后,您可以像对类对象一样在对象上调用成员。
扩展类型有两个同样有效但截然不同的核心用例:
- 提供现有类型的扩展接口。
- 为现有类型提供不同的接口。
信息提示
在任何情况下,扩展类型的表示类型从来都不是它的子类型,因此在需要扩展类型的地方,表示类型不能互换使用。
1. 为现有类型提供扩展接口
当扩展类型实现其表示类型时,可以将其视为 “透明”,因为它允许扩展类型“看到”基础类型。
透明扩展类型可以调用表示类型的所有成员(未重新声明 )以及它定义的任何辅助成员。这将为现有类型创建一个新的扩展接口。新接口可用于 static 类型为扩展类型的表达式。
这意味着您可以调用表示类型的成员(与非透明的 extension 类型),如下所示:
extension type NumberT(int value)
implements int {
// Doesn't explicitly declare any members of 'int'.
NumberT get i => this;
}
void main () {
// All OK: Transparency allows invoking `int` members on the extension type:
var v1 = NumberT(1); // v1 type: NumberT
int v2 = NumberT(2); // v2 type: int
var v3 = v1.i - v1; // v3 type: int
var v4 = v2 + v1; // v4 type: int
var v5 = 2 + v1; // v5 type: int
// Error: Extension type interface is not available to representation type
v2.i;
}
您还可以拥有一个 “mostly-transparent” 扩展类型,该扩展类型通过从超类型重新声明给定的成员名称来添加新成员并适应其他成员。例如,这将允许你对方法的某些参数或不同的默认值使用更严格的类型。
另一种最透明的扩展类型方法是实现一个类型,该类型是表示类型的超类型。例如,如果表示类型是 private,但其 supertype 定义了对 Client 端重要的接口部分。
2. 为现有类型提供不同的接口
不透明的扩展类型 (不实现其表示类型)被静态地视为一个全新的类型,与它的表示类型不同。不能将其分配给其制图表达类型,并且它不会公开其制图表达类型的成员。
例如,以我们在 Usage 下声明的 NumberE
扩展类型为例:
void testE() {
var num1 = NumberE(1);
int num2 = NumberE(2); // Error: Can't assign 'NumberE' to 'int'.
num1.isValid(); // OK: Extension member invocation.
num1.isNegative(); // Error: 'NumberE' does not define 'int' member 'isNegative'.
var sum1 = num1 + num1; // OK: 'NumberE' defines '+'.
var diff1 = num1 - num1; // Error: 'NumberE' does not define 'int' member '-'.
var diff2 = num1.value - 2; // OK: Can access representation object with reference.
var sum2 = num1 + 2; // Error: Can't assign 'int' to parameter type 'NumberE'.
List<NumberE> numbers = [
NumberE(1),
num1.next, // OK: 'next' getter returns type 'NumberE'.
1, // Error: Can't assign 'int' element to list type 'NumberE'.
];
}
您可以通过这种方式使用扩展类型来替换现有类型的接口。这允许你对新类型的约束有意义的接口进行建模(如简介中的 IdNumber
示例),同时还可以从简单的预定义类型(如 int
)的性能和便利性中受益。
此用例最接近完整的封装 包装类(但实际上只是一个 某种程度上受保护的抽象)。
Type considerations 类型注意事项
扩展类型是编译时包装构造。在运行时,绝对没有扩展类型的跟踪。任何类型查询或类似的运行时作都适用于表示类型。
这使得扩展类型成为不安全的抽象,因为您始终可以在运行时找到表示类型并访问基础对象。
动态类型测试 (e is T
)、强制转换 (e as T
) 和其他运行时类型查询 (如 switch (e) ...
或 if (e case ...)
) 都计算为基础的表示对象,并针对该对象的运行时类型进行类型检查。当 e
的静态类型是扩展类型时,以及针对扩展类型进行测试时(case MyExtensionType(): ...
)时,情况确实如此。
void main() {
var n = NumberE(1);
// Run-time type of 'n' is representation type 'int'.
if (n is int) print(n.value); // Prints 1.
// Can use 'int' methods on 'n' at run time.
if (n case int x) print(x.toRadixString(10)); // Prints 1.
switch (n) {
case int(:var isEven): print("$n (${isEven ? "even" : "odd"})"); // Prints 1 (odd).
}
}
同样,匹配值的 static 类型是此示例中扩展类型的 static 类型:
void main() {
int i = 2;
if (i is NumberE) print("It is"); // Prints 'It is'.
if (i case NumberE v) print("value: ${v.value}"); // Prints 'value: 2'.
switch (i) {
case NumberE(:var value): print("value: $value"); // Prints 'value: 2'.
}
}
在使用扩展类型时,请务必注意此质量。请始终记住,扩展类型在编译时存在并且很重要,但在编译过程中会被擦除。
例如,考虑一个表达式 e
,其静态类型为扩展类型 E
,表示类型 E
为 R
。然后,e
值的运行时类型是 R
的子类型。 甚至类型本身也被擦除; List<E>
与运行时的 List<R>
完全相同。
换句话说,真正的包装类可以封装包装的对象,而扩展类型只是包装对象的编译时视图。虽然真正的包装器更安全,但权衡是扩展类型允许您选择避免使用包装器对象,这在某些情况下可以大大提高性能。
Callable objects 可调用对象
要允许像函数一样调用 Dart 类的实例,请实现 call()
方法。
call()
方法允许定义它的任何类的实例模拟函数。此方法支持与普通函数相同的功能 例如参数和返回类型。
在下面的示例中,WannabeFunction
类定义了一个 call()
函数,该函数采用三个字符串并将它们连接起来,每个字符串之间用空格分隔,并附加一个感叹号。单击 Run 以执行代码。