類
Dart是一種面向?qū)ο蟮恼Z言实夹,具有類和基于mixin的繼承。每個對象都是一個類的實例啥酱,所有類都來自Object合是。 基于Mixin的繼承意味著雖然每個類(除了Object)只有一個超類,但是類體可以在多個類層次結(jié)構(gòu)中重用飒赃。
使用類的成員
對象具有由函數(shù)和數(shù)據(jù)(分別為方法和 實例變量)組成的成員利花。調(diào)用方法時,可以 在對象上調(diào)用它:該方法可以訪問該對象的函數(shù)和變量载佳。
使用點(.)來引用實例變量或方法:
var p = Point(2, 2);
// Set the value of the instance variable y.
p.y = 3;
// Get the value of y.
assert(p.y == 3);
// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4, 4));
當最左邊的操作數(shù)有可能為null時炒事,使用?.而不是.避免異常:
// If p is non-null, set its y value to 4.
p?.y = 4;
使用構(gòu)造函數(shù)
構(gòu)造函數(shù)用來創(chuàng)建對象. 構(gòu)造函數(shù)的名稱可以為類名或者其他類方法. 比如你可以用Point的Point()構(gòu)造函數(shù) 或者Point.fromJson():
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
以下代碼具有相同的效果,但new在構(gòu)造函數(shù)名稱之前使用可選關(guān)鍵字:
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
版本注:該new關(guān)鍵字Dart2成為可選項蔫慧。
有些類提供常量構(gòu)造函數(shù)羡洛。要使用常量構(gòu)造函數(shù)創(chuàng)建編譯時常量,請將const
關(guān)鍵字放在構(gòu)造函數(shù)名稱之前:
有些類提供常量構(gòu)造函數(shù)藕漱。要使用常量構(gòu)造函數(shù)創(chuàng)建編譯時常量,請將const
關(guān)鍵字放在構(gòu)造函數(shù)名稱之前:
var p = const ImmutablePoint(2, 2);
構(gòu)造兩個相同的編譯時常量會產(chǎn)生一個規(guī)范的實例:
常量構(gòu)造函數(shù)如果傳遞相同的參數(shù)崭闲,僅僅會存在一個對象肋联,不會重復創(chuàng)建!5蠹蟆橄仍!
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
//常量構(gòu)造函數(shù)如果傳遞相同的參數(shù),僅僅會存在一個對象,不會重復創(chuàng)建N攴薄B侵唷!
assert(identical(a, b)); // They are the same instance!
在常量上下文中宪哩,您可以省略const構(gòu)造函數(shù)或文字之前的內(nèi)容娩贷。例如,查看此代碼锁孟,該代碼創(chuàng)建一個const映射:
// Lots of const keywords here.
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
您可以省略除const關(guān)鍵字的第一次使用之外的所有內(nèi)容:
// Only one const, which establishes the constant context.
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
如果常量構(gòu)造函數(shù)在常量上下文之外并且在沒有const它的情況下調(diào)用彬祖,則會創(chuàng)建一個非常量對象:
var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant
assert(!identical(a, b)); // NOT the same instance!
獲取對象的類型
要在運行時獲取對象的類型,可以使用Object的runtimeType
屬性品抽,該屬性返回Type對象储笑。
print('The type of a is ${a.runtimeType}');
到目前為止,您已經(jīng)了解了如何使用類圆恤。本節(jié)的其余部分將介紹如何實現(xiàn)類扫尖。
實例變量
以下是聲明實例變量的方法:
class Point {
num x; // Declare instance variable x, initially null.
num y; // Declare y, initially null.
num z = 0; // Declare z, initially 0.
}
所有未初始化的實例變量都具有該值null
。
所有實例變量都生成一個隱式getter方法氢惋。非最終實例變量也會生成隱式setter方法嗜侮。有關(guān)詳細信息,請參閱Getters和setter弱左。
class Point {
num x;
num y;
}
void main() {
var point = Point();
point.x = 4; // Use the setter method for x.
assert(point.x == 4); // Use the getter method for x.
assert(point.y == null); // Values default to null.
}
如果聲明時初始化了實例變量(而不是構(gòu)造函數(shù)或方法)窄陡,則在創(chuàng)建實例時設置該值,該操作在構(gòu)造函數(shù)及其初始化列表執(zhí)行之前拆火。
構(gòu)造函數(shù)
通過創(chuàng)建與其類同名的函數(shù)來聲明構(gòu)造函數(shù)(另外跳夭,可選地,如命名構(gòu)造函數(shù)中所述的附加標識符 )们镜。最常見的構(gòu)造函數(shù)形式币叹,即生成構(gòu)造函數(shù),創(chuàng)建一個類的新實例:
class Point {
num x, y;
Point(num x, num y) {
// There's a better way to do this, stay tuned.
this.x = x;
this.y = y;
}
}
//該this關(guān)鍵字是指當前實例模狭。
注意: 有名稱沖突時颈抚,才使用this。否則嚼鹉,Dart風格省略了this贩汉。
將構(gòu)造函數(shù)參數(shù)賦值給實例變量的模式是如此常見,Dart具有語法糖锚赤,使其變得簡單:
class Point {
num x, y;
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}
默認構(gòu)造函數(shù)
如果您未聲明構(gòu)造函數(shù)匹舞,則會為您提供默認構(gòu)造函數(shù)。默認構(gòu)造函數(shù)沒有參數(shù)线脚,并在超類中調(diào)用無參數(shù)構(gòu)造函數(shù)赐稽。
構(gòu)造函數(shù)不能繼承的
子類不從其超類繼承構(gòu)造函數(shù)叫榕。聲明沒有構(gòu)造函數(shù)的子類只有默認(無參數(shù),無名稱)構(gòu)造函數(shù)姊舵。
命名構(gòu)造函數(shù)
使用命名構(gòu)造函數(shù)為類實現(xiàn)多個構(gòu)造函數(shù):
class Point {
num x, y;
Point(this.x, this.y);
// Named constructor
Point.origin() {
x = 0;
y = 0;
}
}
請記住晰绎,構(gòu)造函數(shù)不是繼承的,這意味著超類的命名構(gòu)造函數(shù)不會被子類繼承括丁。如果希望使用超類中定義的命名構(gòu)造函數(shù)創(chuàng)建子類荞下,則必須在子類中實現(xiàn)該構(gòu)造函數(shù)。
調(diào)用非默認的超類構(gòu)造函數(shù)
默認情況下躏将,子類中的構(gòu)造函數(shù)調(diào)用超類的未命名的無參數(shù)構(gòu)造函數(shù)锄弱。超類的構(gòu)造函數(shù)在構(gòu)造函數(shù)體的開頭被調(diào)用。如果 還使用初始化列表祸憋,則在調(diào)用超類之前執(zhí)行会宪。總之蚯窥,執(zhí)行順序如下:
- 初始化列表
- 超類的無參數(shù)構(gòu)造函數(shù)
- 主類的無參數(shù)構(gòu)造函數(shù)
如果超類沒有未命名的無參數(shù)構(gòu)造函數(shù)掸鹅,則必須手動調(diào)用超類中的一個構(gòu)造函數(shù)。在冒號(:)之后拦赠,在構(gòu)造函數(shù)體(如果有)之前指定超類構(gòu)造函數(shù)巍沙。
在下面的示例中,Employee類的構(gòu)造函數(shù)為其超類Person調(diào)用命名構(gòu)造函數(shù)荷鼠。
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';
}
因為在調(diào)用構(gòu)造函數(shù)之前會計算超類構(gòu)造函數(shù)的參數(shù)句携,所以參數(shù)可以是一個表達式,例如函數(shù)調(diào)用:
class Employee extends Person {
Employee() : super.fromJson(getDefaultData());
// ···
}
警告: 超類構(gòu)造函數(shù)的參數(shù)無權(quán)訪問this允乐。例如矮嫉,參數(shù)可以調(diào)用靜態(tài)方法,但不能調(diào)用實例方法牍疏。
初始化列表
除了調(diào)用超類構(gòu)造函數(shù)之外蠢笋,還可以在構(gòu)造函數(shù)體運行之前初始化實例變量。用逗號分隔初始化程序鳞陨。
// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
警告: 初始化程序的右側(cè)無權(quán)訪問this昨寞。
在開發(fā)期間,您可以通過assert在初始化列表中使用來驗證輸入厦滤。
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
設置最終字段時援岩,初始化程序列表很方便。以下示例初始化初始化列表中的三個最終字段掏导。
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);
}
重定向構(gòu)造函數(shù)
有時構(gòu)造函數(shù)的唯一目的是重定向到同一個類中的另一個構(gòu)造函數(shù)窄俏。重定向構(gòu)造函數(shù)的主體是空的,構(gòu)造函數(shù)調(diào)用出現(xiàn)在冒號(:)之后碘菜。
class Point {
num x, y;
// The main constructor for this class.
Point(this.x, this.y);
// Delegates to the main constructor.
Point.alongXAxis(num x) : this(x, 0);
}
常量構(gòu)造函數(shù)
如果您的類生成永遠不會更改的對象,則可以使這些對象成為編譯時常量。為此忍啸,請定義const構(gòu)造函數(shù)并確保所有實例變量都是final仰坦。
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final num x, y;
const ImmutablePoint(this.x, this.y);
}
常量構(gòu)造函數(shù)并不總是創(chuàng)建常量。有關(guān)詳細信息计雌,請參閱有關(guān)使用構(gòu)造函數(shù)的部分 悄晃。
工廠構(gòu)造函數(shù)
factory關(guān)鍵字在實現(xiàn)不總是創(chuàng)建其類的新實例的構(gòu)造函數(shù)時使用。例如凿滤,工廠構(gòu)造函數(shù)可能從緩存中返回實例妈橄,或者它可能返回子類型的實例。
以下示例演示了從緩存中返回對象的工廠構(gòu)造函數(shù):
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) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
注意: 工廠構(gòu)造函數(shù)無權(quán)訪問this翁脆。
像調(diào)用任何其他構(gòu)造函數(shù)一樣調(diào)用工廠構(gòu)造函數(shù):
var logger = Logger('UI');
logger.log('Button clicked');
方法
方法是為對象提供行為的函數(shù)眷蚓。
實例方法
對象的實例方法可以訪問實例變量和this。在distanceTo()下面的示例中方法是一個實例方法的一個例子:
import 'dart:math';
class Point {
num x, y;
Point(this.x, this.y);
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
Getters and setters
getter和setter是提供對象屬性的讀寫訪問權(quán)限的特殊方法反番∩橙龋回想一下,每個實例變量都有一個隱式getter罢缸,如果合適的話還有一個setter篙贸。您可以使用get和set關(guān)鍵字通過實現(xiàn)getter和setter來創(chuàng)建其他屬性 :
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// Define two calculated properties: right and bottom.
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num 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,您可以從實例變量開始枫疆,稍后使用方法包裝它們爵川,而無需更改客戶端代碼。
注意: 無論是否明確定義了getter息楔,增量(++)等運算符都以預期的方式工作寝贡。為避免任何意外的副作用,操作員只需調(diào)用一次getter钞螟,將其值保存在臨時變量中兔甘。
抽象方法
實例,getter和setter方法可以是抽象的鳞滨,定義一個接口洞焙,但將其實現(xiàn)留給其他類。抽象方法只能存在于抽象類中拯啦。
要使方法成為抽象澡匪,請使用分號(;)而不是方法體:
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...
}
}
抽象類
使用abstract
修飾符定義抽象類 - 無法實例化的類。抽象類對于定義接口非常有用褒链,通常還有一些實現(xiàn)唁情。如果希望抽象類看起來是可實例化的,請定義工廠構(gòu)造函數(shù)甫匹。
抽象類通常有抽象方法甸鸟。這是一個聲明具有抽象方法的抽象類的示例:
// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
// Define constructors, fields, methods...
void updateChildren(); // Abstract method.
}
隱式接口
每個類都隱式定義一個接口惦费,該接口包含該類的所有實例成員及其實現(xiàn)的任何接口。如果要在不繼承B實現(xiàn)的情況下創(chuàng)建支持B類API的A類抢韭,則A類應實現(xiàn)B接口薪贫。
類通過在implements子句中聲明它們?nèi)缓筇峁┙涌谒璧腁PI來實現(xiàn)一個或多個 接口。例如:
/ A person. The implicit interface contains greet().
class Person {
// In the interface, but visible only in this library.
final _name;
// Not in the interface, since this is a constructor.
Person(this._name);
// In the interface.
String greet(String who) => 'Hello, $who. I am $_name.';
}
// An implementation of the Person interface.
class Impostor implements Person {
get _name => '';
String greet(String who) => 'Hi $who. Do you know who I am?';
}
String greetBob(Person person) => person.greet('Bob');
void main() {
print(greetBob(Person('Kathy')));
print(greetBob(Impostor()));
}
這是一個指定類實現(xiàn)多個接口的示例:
class Point implements Comparable, Location {...}
擴展類
使用extends創(chuàng)建一個子類刻恭,并super指超:
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
復寫成員
子類可以覆蓋實例方法瞧省,getter和setter。您可以使用@override注釋來指示您有意覆蓋成員:
class SmartTelevision extends Television {
@override
void turnOn() {...}
// ···
}
要在類型安全的代碼中縮小方法參數(shù)或?qū)嵗兞康?類型鳍贾,可以使用covariant
關(guān)鍵字鞍匾。
可覆寫的運算符
您可以覆蓋下表中顯示的運算符。例如骑科,如果定義Vector類橡淑,則可以定義+添加兩個向量的方法。
< | + | | | [] |
---|---|---|---|
> | / | ^ | []= |
<= | ~/ | & | ~ |
>= | * | << | == |
– | % | >> |
注意:您可能已經(jīng)注意到纵散,這!=不是可覆蓋的運算符梳码。表達e1 != e2只是語法糖!(e1 == e2)。
這是一個覆蓋+和-運算符的類的示例:
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);
// Operator == and hashCode not shown. For details, see note below.
// ···
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
如果覆蓋==
伍掀,則還應覆蓋Object的hashCode
getter掰茶。用于覆蓋的一個例子==
和hashCode
,請參見 實施映射鍵蜜笤。
有關(guān)覆蓋的更多信息濒蒋,請參閱 擴展類。
noSuchMethod()
要在代碼嘗試使用不存在的方法或?qū)嵗兞繒r檢測或做出反應把兔,您可以覆蓋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}');
}
}
你不能調(diào)用一個未實現(xiàn)的方法除非滿足一下至少一個條件:
接收器具有靜態(tài)類型dynamic沪伙。
接收器有一個靜態(tài)類型,它定義了未實現(xiàn)的方法(抽象是OK)县好,接收器的動態(tài)類型的實現(xiàn)與類noSuchMethod() 中的實現(xiàn)不同Object围橡。
有關(guān)更多信息,請參閱非正式 noSuchMethod轉(zhuǎn)發(fā)規(guī)范缕贡。
枚舉類型
枚舉類型(通常稱為枚舉或枚舉)是一種特殊類翁授,用于表示固定數(shù)量的常量值。
使用枚舉
使用enum關(guān)鍵字聲明枚舉類型:
enum Color { red, green, blue }
枚舉中的每個值都有一個indexgetter晾咪,它返回枚舉聲明中值的從零開始的位置收擦。例如,第一個值具有索引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!');
break;
case Color.green:
print('Green as grass!');
break;
default: // Without this, you see a WARNING.
print(aColor); // 'Color.blue'
}
枚舉類型具有以下限制:
- 您不能子類化,混合或?qū)崿F(xiàn)枚舉仇哆。
- 您無法顯式實例化枚舉辽剧。
有關(guān)更多信息,請參閱Dart語言規(guī)范税产。
向類添加元素:mixins
Mixins是一種在多個類層次結(jié)構(gòu)中重用類代碼的方法。
要使用 mixin偷崩,請使用with關(guān)鍵字后跟一個或多個mixin名稱辟拷。以下示例顯示了兩個使用mixins的類:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
要實現(xiàn) mixin,請創(chuàng)建一個擴展Object的類阐斜,并且不聲明構(gòu)造函數(shù)衫冻。除非您希望mixin可用作常規(guī)類,否則請使用mixin關(guān)鍵字而不是class谒出。例如:
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');
}
}
}
要指定只有某些類型可以使用mixin - 例如隅俘,所以你的mixin可以調(diào)用它沒有定義的方法 - 用于on指定所需的超類:
mixin MusicalPerformer on Musician {
// ···
}
版本說明:
mixin
Dart 2.1中引入了對關(guān)鍵字的支持。通常使用早期版本中的代碼abstract class
笤喳。有關(guān)2.1 mixin更改的更多信息为居,請參閱 Dart SDK changelog和2.1 mixin規(guī)范。
類變量和方法
使用static關(guān)鍵字實現(xiàn)類范圍的變量和方法杀狡。
靜態(tài)變量
靜態(tài)變量(類變量)對于類范圍的狀態(tài)和常量很有用:
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
靜態(tài)變量在使用之前不會初始化蒙畴。
注意: 此頁面遵循 首選的常量名稱的樣式指南建議
駝峰體
。
靜態(tài)方法
靜態(tài)方法(類方法)不對實例進行操作呜象,因此無權(quán)訪問this膳凝。例如:
import 'dart:math';
class Point {
num x, y;
Point(this.x, this.y);
static num distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}
void main() {
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);
}
注意: 對于常用或廣泛使用的實用程序和功能,請考慮使用頂級函數(shù)而不是靜態(tài)方法恭陡。
您可以使用靜態(tài)方法作為編譯時常量蹬音。例如,您可以將靜態(tài)方法作為參數(shù)傳遞給常量構(gòu)造函數(shù)休玩。
泛型
如果您查看基本數(shù)組類型的API文檔 List著淆,您會看到該類型實際上是List<E>
。<...>表示法將List標記為 通用(或參數(shù)化)類型 - 具有正式類型參數(shù)的類型哥捕。按照慣例牧抽,大多數(shù)類型變量都有單字母名稱,例如E遥赚,T扬舒,S,K和V.
為什么要使用泛型
類型安全通常需要泛型凫佛,但它們比僅允許代碼運行有更多好處:
正確指定泛型類型會產(chǎn)生更好的生成代碼讲坎。
- 您可以使用泛型來減少代碼重復孕惜。
- 如果您希望列表只包含字符串,則可以將其聲明為List<String>(將其讀作“字符串列表”)晨炕。這樣衫画,您,您的程序員和您的工具可以檢測到將非字符串分配給列表可能是一個錯誤瓮栗。這是一個例子:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error
使用泛型的另一個原因是減少代碼重復削罩。泛型允許您在多種類型之間共享單個接口和實現(xiàn),同時仍然利用靜態(tài)分析费奸。例如弥激,假設您創(chuàng)建了一個用于緩存對象的接口:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
您發(fā)現(xiàn)需要此接口的特定于字符串的版本,因此您需要創(chuàng)建另一個接口:
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
之后愿阐,您決定要使用此接口的數(shù)字版本...您明白了微服。
通用類型可以省去創(chuàng)建所有這些接口的麻煩。相反缨历,您可以創(chuàng)建一個帶有類型參數(shù)的接口:
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
在此代碼中以蕴,T是替身類型。它是一個占位符辛孵,您可以將其視為開發(fā)人員稍后定義的類型丛肮。
使用集合文字
可以參數(shù)化列表,集和地圖文字觉吭。參數(shù)化文字就像你已經(jīng)看到的文字一樣腾供,除了你在開始括號之前添加 (對于列表和集合)或 (對于地圖)。以下是使用類型文字的示例:<type><keyType, valueType>
var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
使用帶有構(gòu)造函數(shù)的參數(shù)化類型
要在使用構(gòu)造函數(shù)時指定一個或多個類型鲜滩,請將類型放在<...>類名稱后面的尖括號()中伴鳖。例如:
var nameSet = Set<String>.from(names);
以下代碼創(chuàng)建一個具有整數(shù)鍵和View類型值的映射:
var views = Map<int, View>();
通用集合及其包含的類型
Dart泛型類型的具體化,這意味著他們隨身攜帶的類型信息在運行時徙硅。例如榜聂,您可以測試集合的類型:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
注意: 相反,Java中的泛型使用擦除嗓蘑,這意味著在運行時刪除泛型類型參數(shù)须肆。在Java中,您可以測試對象是否為List桩皿,但您無法測試它是否為
a List<String>
豌汇。
限制參數(shù)化類型
實現(xiàn)泛型類型時,您可能希望限制其參數(shù)的類型泄隔。你可以使用extends拒贱。
class Foo<T extends SomeBaseClass> {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
使用SomeBaseClass或其任何子類作為通用參數(shù)是可以的:
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
也可以不指定泛型參數(shù):
var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'
指定任何非SomeBaseClass類型會導致錯誤:
var foo = Foo < Object >();
使用通用方法
最初,Dart的通用支持僅限于課程。一種稱為泛型方法的新語法允許在方法和函數(shù)上使用類型參數(shù):
T first<T>(List<T> ts) {
// Do some initial work or error checking, then...
T tmp = ts[0];
// Do some additional checking or processing...
return tmp;
}
這里on first
(<T>
)的泛型類型參數(shù)允許你T
在幾個地方使用type參數(shù):
- 在函數(shù)的返回類型(
T
)中逻澳。 - 在參數(shù)類型(
List<T>
)中闸天。 - 在局部變量的類型(
T tmp
)。
有關(guān)泛型的更多信息斜做,請參閱 使用泛型方法苞氮。
庫與可見性
該import
和library
指令可以幫助您創(chuàng)建一個模塊化的,可共享的代碼庫瓤逼。庫不僅提供API笼吟,還是隱私單元:以下劃線(_)開頭的標識符僅在庫內(nèi)可見。每個Dart應用程序都是一個庫霸旗,即使它不使用library
指令赞厕。
可以使用包來分發(fā)庫。 有關(guān)pub(包含在SDK中的包管理器)的信息定硝,請參閱 Pub Package和Asset Manager。
使用庫
使用import
指定如何從一個庫中的命名空間在另一個庫的范圍內(nèi)使用毫目。
例如蔬啡,Dart Web應用程序通常使用dart:html 庫,它們可以像這樣導入:
import 'dart:html';
唯一需要的參數(shù)import是指定庫的URI镀虐。對于內(nèi)置庫箱蟆,URI具有特殊dart:方案。對于其他庫刮便,您可以使用文件系統(tǒng)路徑或package: 方案空猜。該package:方案指定由包管理器(如pub工具)提供的庫。例如:
import 'package:test/test.dart';
注意: URI代表統(tǒng)一資源標識符恨旱。 URL(統(tǒng)一資源定位符)是一種常見的URI辈毯。
指定庫前綴
如果導入兩個具有沖突標識符的庫,則可以為一個或兩個庫指定前綴搜贤。例如谆沃,如果library1和library2都有一個Element類,那么你可能有這樣的代碼:
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// Uses Element from lib1.
Element element1 = Element();
// Uses Element from lib2.
lib2.Element element2 = lib2.Element();
僅導入庫的一部分
如果只想使用庫的一部分仪芒,則可以有選擇地導入庫唁影。例如:
// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
懶惰地加載一個庫
延遲加載(也稱為延遲加載)允許應用程序根據(jù)需要加載庫,如果需要的話掂名。以下是您可能使用延遲加載的一些情況:
- 減少應用程序的初始啟動時間据沈。
- 例如,執(zhí)行A / B測試 - 嘗試算法的替代實現(xiàn)饺蔑。
- 加載很少使用的功能锌介,例如可選的屏幕和對話框。
要懶加載庫膀钠,必須先使用它導入它deferred as掏湾。
import 'package:greetings/hello.dart' deferred as hello;
當您需要庫時裹虫,loadLibrary()使用庫的標識符進行調(diào)用 。
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
在前面的代碼中融击,await
關(guān)鍵字暫停執(zhí)行筑公,直到加載庫。有關(guān)詳細信息async
尊浪,并await
請參閱異步支持匣屡。
您可以loadLibrary()
在庫上多次調(diào)用而不會出現(xiàn)問題。該庫只加載一次拇涤。
使用延遲加載時請記住以下內(nèi)容:
- 延遲庫的常量不是導入文件中的常量捣作。請記住,在加載延遲庫之前鹅士,這些常量不存在券躁。
- 您不能在導入文件中使用延遲庫中的類型。相反掉盅,請考慮將接口類型移動到由延遲庫和導入文件導入的庫也拜。
- Dart隱式插入
loadLibrary()
您使用的命名空間。該函數(shù)返回Future趾痘。deferred as *namespace*``loadLibrary()
Dart VM差異: 即使在調(diào)用之前慢哈,Dart VM也允許訪問延遲庫的成員loadLibrary()
。此行為可能會更改永票,因此 不要依賴于當前的VM行為卵贱。 有關(guān)詳細信息,請參閱問題#33118侣集。
實現(xiàn)庫
有關(guān) 如何實現(xiàn)庫包的建議键俱,請參閱 創(chuàng)建庫包,包括:
- 如何組織庫源代碼世分。
- 如何使用該
export
指令方妖。 - 何時使用該
part
指令。 - 何時使用該
library
指令罚攀。
異步支持
Dart庫中包含許多返回Future或Stream對象的函數(shù)党觅。這些函數(shù)是異步的:它們在設置可能耗時的操作(例如I / O)后返回,而不等待該操作完成斋泄。
在async
和await
關(guān)鍵字支持異步編程杯瞻,讓你寫異步代碼看起來類似于同步代碼。
Handling Futures
當您需要完成Future的結(jié)果時炫掐,您有兩個選擇:
- 使用
async
和await
魁莉。 - 使用Future API,如 庫瀏覽中所述。
使用async
和await
異步的代碼旗唁,但它看起來很像同步代碼畦浓。例如,這里有一些代碼await
用于等待異步函數(shù)的結(jié)果:
await lookUpVersion();
要使用await检疫,代碼必須在異步函數(shù)中 - 標記為的函數(shù)async
:
Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}
注意: 雖然異步函數(shù)可能會執(zhí)行耗時的操作讶请,但它不會等待這些操作。相反屎媳,異步函數(shù)只在遇到第一個
await
表達式(詳細信息)時執(zhí)行夺溢。然后它返回一個Future對象,僅在await
表達式完成后才恢復執(zhí)行烛谊。
使用try风响,catch和finally 處理使用await以下代碼的錯誤和清理:
try {
version = await lookUpVersion();
} catch (e) {
// React to inability to look up the version
}
您可以await在異步功能中多次使用。例如丹禀,以下代碼等待三次函數(shù)結(jié)果:
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
在状勤,await 表達式中; 表達式的值通常是一個Future類型,如果不是,那么這個值將會自動裝箱在Future中双泪。此Future對象象征返回object的承諾荧降。await 表達式
的值是返回的object對象。await表達式
阻塞直到返回object值為止攒读。
如果在使用時出現(xiàn)編譯時錯誤await,請確保await處于異步功能中辛友。 例如薄扁,要await在您的應用程序的main()功能中使用,main()必須將正文標記為async:
Future main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
聲明異步函數(shù)
一個異步函數(shù)是一個函數(shù)體標有async修改废累。
將async關(guān)鍵字添加到函數(shù)使其返回Future邓梅。例如,考慮這個同步函數(shù)邑滨,它返回一個String:
String lookUpVersion() => '1.0.0';
如果將其更改為異步函數(shù) - 例如日缨,因為將來的實現(xiàn)將非常耗時 - 返回的值是Future:
```dart
Future < String > lookUpVersion ()async => '1.0.0' ;
請注意,函數(shù)的主體不需要使用Future API掖看。如有必要匣距,Dart會創(chuàng)建Future對象。
如果您的函數(shù)沒有返回有用的值哎壳,請設置其返回類型Future<void>毅待。
處理流
當您需要從Stream獲取值時,您有兩個選擇:
- 使用
async
和異步for循環(huán)(await for
)归榕。 - 使用Stream API尸红,如 庫瀏覽中所述。
注意: 在使用之前await for,請確保它使代碼更清晰倦炒,并且您確實希望等待所有流的結(jié)果蚤假。例如悯森,你通常應該不使用await for的UI事件偵聽器,因為UI框架發(fā)送無盡的事件流鳖链。
異步for循環(huán)具有以下形式:
await for (varOrType identifier in expression) {
// Executes each time the stream emits a value.
}
值expression必須具有Stream類型。執(zhí)行過程如下:
- 等到流發(fā)出一個值风科。
- 執(zhí)行for循環(huán)的主體撒轮,將變量設置為該發(fā)出的值。
- 重復1和2贼穆,直到關(guān)閉流题山。
要停止偵聽流,可以使用breakor return語句故痊,該for語句會從for循環(huán)中斷開并從流中取消取消顶瞳。
如果在實現(xiàn)異步for循環(huán)時遇到編譯時錯誤,請確保await for它處于異步函數(shù)中愕秫。 例如慨菱,要在應用程序的main()函數(shù)中使用異步for循環(huán),main()必須將正文標記為async:
Future main() async {
// ...
await for (var request in requestServer) {
handleRequest(request);
}
// ...
}
有關(guān)異步編程的更多信息戴甩,請參閱 庫瀏覽的 dart:async部分符喝。另請參閱文章 Dart語言異步支持:階段1 和 Dart語言異步支持:階段2和Dart語言規(guī)范。
Generators
當您需要懶惰地生成一系列值時甜孤,請考慮使用生成器函數(shù)协饲。Dart內(nèi)置支持兩種發(fā)電機功能:
要實現(xiàn)同步生成器函數(shù)茉稠,請將函數(shù)體標記為sync*
,并使用yield
語句來傳遞值:
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
要實現(xiàn)異步生成器函數(shù)把夸,請將函數(shù)體標記為async*而线,并使用yield語句來傳遞值:
Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
如果您的生成器是遞歸的,您可以使用yield*以下方法來提高其性能:
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
有關(guān)生成器的更多信息恋日,請參閱文章 Dart語言異步支持:階段2膀篮。
Callable classes
要允許像函數(shù)一樣調(diào)用Dart類,請實現(xiàn)該call()方法岂膳。
在下面的示例中各拷,WannabeFunction該類定義了一個call()函數(shù),它接受三個字符串并連接它們闷营,用空格分隔每個字符串烤黍,并附加一個感嘆號知市。
class WannabeFunction {
call(String a, String b, String c) => '$a $b $c!';
}
main() {
var wf = new WannabeFunction();
var out = wf("Hi","there,","gang");
print('$out');
}
有關(guān)處理函數(shù)類的更多信息,請參閱 在Dart中模擬函數(shù)速蕊。
Isolates
大多數(shù)計算機嫂丙,即使在移動平臺上,也有多核CPU规哲。為了利用所有這些核心跟啤,開發(fā)人員傳統(tǒng)上使用并發(fā)運行的共享內(nèi)存線程。但是唉锌,共享狀態(tài)并發(fā)容易出錯隅肥,并且可能導致代碼復雜化。
所有Dart代碼都在隔離區(qū)內(nèi)運行袄简,而不是線程腥放。每個隔離區(qū)都有自己的內(nèi)存堆,確保不會從任何其他隔離區(qū)訪問隔離區(qū)的狀態(tài)绿语。
有關(guān)更多信息秃症,請參閱 dart:isolate庫文檔。
Typedefs
在Dart中吕粹,函數(shù)是對象种柑,就像字符串一樣,數(shù)字是對象匹耕。一個類型定義聚请,或功能型的別名,給出了一個函數(shù)類型聲明字段時稳其,您可以使用和返回類型的名稱驶赏。當函數(shù)類型分配給變量時,typedef會保留類型信息欢际。
請考慮以下代碼,它不使用typedef:
class SortedCollection {
Function compare;
SortedCollection(int f(Object a, Object b)) {
compare = f;
}
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
// All we know is that compare is a function,
// but what type of function?
assert(coll.compare is Function);
}
當分配類型信息丟失f到compare矾兜。類型 f是(Object, Object)→ int(其中→表示返回)损趋,但類型compare是功能。如果我們將代碼更改為使用顯式名稱并保留類型信息椅寺,則開發(fā)人員和工具都可以使用該信息浑槽。
typedef Compare = int Function(Object a, Object b);
class SortedCollection {
Compare compare;
SortedCollection(this.compare);
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
void main() {
SortedCollection coll = SortedCollection(sort);
assert(coll.compare is Function);
assert(coll.compare is Compare);
}
注意: 目前,typedef僅限于函數(shù)類型返帕。我們希望這會改變桐玻。
因為typedef只是別名,所以它們提供了一種檢查任何函數(shù)類型的方法荆萤。例如:
typedef Compare<T> = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
assert(sort is Compare<int>); // True!
}
元數(shù)據(jù)
使用元數(shù)據(jù)提供有關(guān)代碼的其他信息镊靴。元數(shù)據(jù)注釋以字符開頭@
铣卡,后跟對編譯時常量的引用(如deprecated
)或?qū)ΤA繕?gòu)造函數(shù)的調(diào)用。
所有Dart代碼都有兩個注釋:@deprecated
和 @override
偏竟。有關(guān)使用的示例@override
煮落,請參閱擴展類。以下是使用@deprecated
注釋的示例:
class Television {
/// _Deprecated: Use [turnOn] instead._
@deprecated
void activate() {
turnOn();
}
/// Turns the TV's power on.
void turnOn() {...}
}
您可以定義自己的元數(shù)據(jù)注釋踊谋。這是一個定義帶有兩個參數(shù)的@todo注釋的示例:
library todo;
class Todo {
final String who;
final String what;
const Todo(this.who, this.what);
}
以下是使用@todo注釋的示例:
import 'todo.dart';
@Todo('seth', 'make this do something')
void doSomething() {
print('do something');
}
元數(shù)據(jù)可以出現(xiàn)在庫蝉仇,類,typedef殖蚕,類型參數(shù)轿衔,構(gòu)造函數(shù),工廠睦疫,函數(shù)害驹,字段,參數(shù)或變量聲明之前以及導入或?qū)С鲋噶钪傲础D梢允褂梅瓷湓谶\行時檢索元數(shù)據(jù)裙秋。
注釋
Dart支持單行注釋,多行注釋和文檔注釋缨伊。
單行注釋
單行注釋以//開頭摘刑。//Dart編譯器會忽略行之間和行尾的所有內(nèi)容。
void main() {
// TODO: refactor into an AbstractLlamaGreetingFactory?
print('Welcome to my Llama farm!');
}
多行注釋
多行注釋以... /結(jié)尾/刻坊。介于兩者之間的/枷恕,并/用飛鏢編譯器忽略(除非該注釋是一個文檔注釋;見下一節(jié))。多行注釋可以嵌套谭胚。
void main() {
/*
* This is a lot of work. Consider raising chickens.
Llama larry = Llama();
larry.feed();
larry.exercise();
larry.clean();
*/
}
文檔注釋
文檔注釋是首先多行或單行注釋///或/**徐块。使用///連續(xù)的行上有一個多行文檔注釋同樣的效果。
在文檔注釋中灾而,Dart編譯器忽略所有文本胡控,除非它括在括號中。使用括號旁趟,您可以引用類昼激,方法,字段锡搜,頂級變量橙困,函數(shù)和參數(shù)。括號中的名稱在已記錄的程序元素的詞法范圍內(nèi)得到解析耕餐。
以下是文檔注釋的示例凡傅,其中引用了其他類和參數(shù):
/// A domesticated South American camelid (Lama glama).
///
/// Andean cultures have used llamas as meat and pack
/// animals since pre-Hispanic times.
class Llama {
String name;
/// Feeds your llama [Food].
///
/// The typical llama eats one bale of hay per week.
void feed(Food food) {
// ...
}
/// Exercises your llama with an [activity] for
/// [timeLimit] minutes.
void exercise(Activity activity, int timeLimit) {
// ...
}
}
在生成的文檔中,[Food]
成為Food類的API文檔的鏈接肠缔。
要解析Dart代碼并生成HTML文檔夏跷,您可以使用SDK的 文檔生成工具哼转。 有關(guān)生成的文檔的示例,請參閱Dart API文檔拓春。有關(guān)如何構(gòu)建注釋的建議释簿,請參閱 Dart Doc注釋指南。
摘要
本頁概述了Dart語言中常用的功能硼莽。正在實施更多功能庶溶,但我們希望它們不會破壞現(xiàn)有代碼。有關(guān)更多信息懂鸵,請參閱Dart語言規(guī)范和 Effective Dart偏螺。
要了解有關(guān)Dart核心庫的更多信息,請參閱 Dart Libraries之旅匆光。