Dart是一个面向对象的语言,具有类和基于mixin的继承。所有的对象都是一个类的实例,所有的类都继承自Object。基于mixin的继承意味着尽管任何一个类(除了Object)都只有一个父类,但是类主体可以在多个类层次结构中复用。
对象具有由函数和数据组成的成员(分别是成员方法和成员变量)。当你调用方法时,你是在对象上调用它:该方法可以访问该对象的函数和数据。
使用 . 来引用实例变量和方法。
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));
使用 ?. 来代替 . ,当最左边的操作数为null时可以避免出现异常。
// If p is non-null, set its y value to 4.
p?.y = 4;
你可以使用构造函数创建一个对象,构造函数的名称可以是 类名(ClassName) 或者是 ClassName.identifier。例如,下面的代码创建 Point 对象使用构造方法 Point() 或 Point.fromJson() .
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
下面的代码具有同样的的效果,但在构造函数名称之前使用可选的 new 关键字:
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
有些类提供常量构造函数,可以使用它来来创建一个编译时常量。在构造方法之前添加一个 const 关键字。
var p = const ImmutablePoint(2, 2);
构造两个相同的编译时常量会产生一个规范的实例:
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // They are the same instance!
在常量上下文中,可以省略构造函数或者字面量之前的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关键字之外的所有const关键字:
// Only one const, which establishes the constant context.
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
如果常量构造函数在常量上下文之外,并且在没有const的情况下调用,那么它将会创建一个非常量对象。
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!
在运行时获取一个对象的类型,可以使用 runtimeType 属性,它会返回一个 Type 对象。
print('The type of a is ${a.runtimeType}');
如何声明实例变量,如下:
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方法。非final实例变量也有一个隐式的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.
}
注: 如果你在定义实例变量的地方初始化它(而不是在构造器或者方法中初始化),初始化的值会在创建实例的时候设置,它发生在构造函数和初始化列表执行之前。
通过创建一个名称与类名称相同的方法来声明构造方法。
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;
}
}
将构造函数参数赋值给实例变量的模式非常常见,Dart有语法糖是的这个操作更简单:
class Point {
num x, y;
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}
如果你没有声明一个构造器,系统会提供一个默认的构造器。默认构造器没有参数并且会调用父类的没有参数的构造器。
子类不会继承父类的构造器。一个子类没有声明构造器那就只有一个默认的没有参数,没有名字的构造器。
使用命名构造函数为类实现多个构造函数或提供功能更清晰的构造函数:
class Point {
num x, y;
Point(this.x, this.y);
// Named constructor
Point.origin() {
x = 0;
y = 0;
}
}
默认情况下子类构造函数会调用父类的匿名,无参构造函数。父类构造函数在构造函数体的开头调用。如果同时使用了初始化列表,那么它在父类构造函数调用之前执行。综上所述,执行顺序如下:
如果父类无匿名无参构造函数,那么你必须手动调用父类构造函数中的一个。在冒号(:)后面构造函数体前面指定父类构造函数。
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';
}
因为传递给父类构造函数的参数是在调用构造函数之前计算的,所以参数可以是一个表达式例如函数调用:
class Employee extends Person {
Employee() : super.fromJson(getDefaultData());
// ···
}
除了调用父类构造函数之外,你也可以在构造函数体运行之前初始化实例变量。使用逗号分隔初始化项。
// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
开发阶段,你可以在初始化列表中使用 assert 来验证输入:
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
初始化列表用来设置final字段很方便,例如:
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);
}
有时一个构造函数的目的仅仅是重定向到同一个类中的另一个构造函数。重定向构造函数的函数体是空的,构造函数调用出现在冒号之后:
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);
}
如果类产生永不改变的对象,你可以使这些对象为编译时常量。为此,定义一个const构造函数并确保所有实例变量都是final。
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final num x, y;
const ImmutablePoint(this.x, this.y);
}
常量构造函数并不总是创建常量。
当实现的构造器并不总是创建一个新的实例时请使用 factory 关键字。例如,一个工厂构造函数可能从缓存中返回一个实例,或者它可能会返回子类的一个实例。
下面的例子演示了一个工厂构造器从缓存中返回对象:
class Logger {
final String name;
bool mute = false;
// _cache is library-private, thanks to
// the _ in front of its name.
static final Map _cache =
{};
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);
}
}
调用工厂构造函数就跟调用其他构造函数一样:
var logger = Logger('UI');
logger.log('Button clicked');
方法是为一个对象提供行为的函数。
一个对象上的实例方法可以访问实例变量和 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方法。你可以使用get和set关键字通过实现getter和setter来创建其他属性:
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);
}
使用getters和setters,你可以从实例变量开始,然后使用方法包装他们,而无需更改客户端代码。
实例,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, so the method is not abstract here...
}
}
使用 abstract 修饰符来定义一个抽象类:一个不能被实例化的类。抽象类对于定义接口很有用。如果你想让你的抽象类开起来是可以实例化的,那么可以定义工厂构造方法。
抽象类通常有抽象方法。下面是定义一个有抽象方法的抽象类的例子:
// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
// Define constructors, fields, methods...
void updateChildren(); // Abstract method.
}
每一个类隐式定义了一个包含这个类的所有实例成员以及它实现的任何接口的接口。如果你想创建一个类A,并且它支持类B的API,而不需要继承类B的实现,那么类A应该实现类B的接口。
类通过在 implements 子句中声明要实现的接口,然后提供接口所需的API来实现一个或多个接口。
// 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()));
}
指定一个类实现多个接口:
class Point implements Comparable, Location {...}
使用 extends 创建一个子类,使用 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() {...}
// ···
}
要在类型安全的代码中缩小方法参数或实例变量的类型,可以使用 covariant 关键字。
你可以重写下表显示的操作符。例如:如果你定义一个Vector 类,你可以定义一个 + 方法来做两个vector的加法。
< | + | | | [] |
< | / | ^ | []= |
<= | ~/ | & | ~ |
>= | * | << | == |
- | % | >> |
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));
}
如果你重写 == ,你也必须重写对象的 hashCode getter。重写 == ,和 hashCode 的例子如下:
class Person {
final String firstName, lastName;
Person(this.firstName, this.lastName);
// Override hashCode using strategy from Effective Java,
// Chapter 11.
@override
int get hashCode {
int result = 17;
result = 37 * result + firstName.hashCode;
result = 37 * result + lastName.hashCode;
return result;
}
// You should generally implement operator == if you
// override hashCode.
@override
bool operator ==(dynamic other) {
if (other is! Person) return false;
Person person = other;
return (person.firstName == firstName &&
person.lastName == lastName);
}
}
void main() {
var p1 = Person('Bob', 'Smith');
var p2 = Person('Bob', 'Smith');
var p3 = 'not a person';
assert(p1.hashCode == p2.hashCode);
assert(p1 == p2);
assert(p1 != p3);
}
为了检测和应对任何时候代码尝试使用一个不存在的方法或者不存在的实例变量,你可以重写 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}');
}
}
除非满足下列条件之一,否则不能调用未实现的方法:
枚举类型也称为枚举,是一种特殊的类,用于表示固定数量的常量值。
enum Color { red, green, blue }
枚举类型中的每一个值有一个 index getter,它返回基于 0 的在枚举声明中的位置。例如,第一个值的索引为 0,第二个值的索引为 1.
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
获取枚举类型中的所有值的一个列表,使用枚举的 values 常量。
List 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'
}
枚举类型有如下限制:
Mixins是一种在多个类层次结构中重用类代码的方法。
要使用mixin,请使用 with 关键字,后跟一个或多个mixin名称。下面的例子显示了两个类使用mixins:
class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}
要实现mixin,创建一个继承Object并且不声明构造器的类。除非你希望你的mixin可以作为常规类使用,使用 mixin 关键字来代替 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可以调用它没有定义的方法,使用 on 来指定父类:
mixin MusicalPerformer on Musician {
// ···
}
使用 static 关键字来实现类范围的变量和方法。
静态变量对于类范围的状态和常量很有用:
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
静态变量直到它们被使用的时候才会被初始化
静态方法不对实例进行操作,从而无法访问 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);
}
泛型通常会要求类型安全,但是它们有更多的好处:
var names = List();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error
另一个使用泛型的原因是可以减少重复代码量。泛型允许你在多种类型之间共享单个接口和实现,例如:假设你创建了一个用于缓存对象的接口:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
然后你发现你需要一个String版本的该接口,所以你需要创建另一个接口:
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
最后,你决定你需要一个数字类型版本的该接口…
泛型可以解决创建这么接口的问题。取而代之,你可以创建单个接口,这个接口带有一个类型参数:
abstract class Cache {
T getByKey(String key);
void setByKey(String key, T value);
}
在此代码中,T是代替类型,它只是一个占位符,你可以将它视为开发人员稍后定义的类型。
列表和字典字面量也可以参数化。参数化字面量就跟之前讲的字面量一样,不同的地方在于你在开始中括号之前为列表添加了 类型,为字典添加了
var names = ['Seth', 'Kathy', 'Lars'];
var pages = {
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
在使用构造函数时指定一个或多个类型,将类型放在类名后的尖括号 <. . .> 里。例如:
var names = List();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = Set.from(names);
下面的代码创建了一个键为int类型,值为View类型的字典:
var views = Map();
Dart泛型类型被定义,这意味着它们在运行时携带它们的类型信息。例如,你可以测试集合的类型:
var names = List();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List); // true
当实现一个泛型类型时,你可能想限制参数的类型。你可以使用 extends .
class Foo {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
可以使用 SomeBaseClass 或者它的子类作为泛型参数:
var someBaseClassFoo = Foo();
var extenderFoo = Foo();
也可以不指定泛型参数:
var foo = Foo();
print(foo); // Instance of 'Foo'
如果指定任何不是 SomeBaseClass 的类型会导致错误:
var foo = Foo
最初,Dart泛型仅限于支持类。你个新的语法,叫 泛型方法(generic methods),允许类型参数使用在方法或者函数上:
T first(List ts) {
// Do some initial work or error checking, then...
T tmp = ts[0];
// Do some additional checking or processing...
return tmp;
}
这里在 first 上的泛型类型参数 允许你在以下几个地方使用类型参数 T :