- 1:使用类的成员
- 2:使用构造函数
- 3:获取对象的类型
- 4:实例变量
- 5:构造函数
- 5.1:默认构造函数
- 5.2:构造函数不是继承的
- 5.3:命名构造函数
- 5.4:调用非默认父类构造函数
- 5.5:初始化变量
- 5.6:重定向构造函数
- 5.7:常量构造函数
- 5.8:工厂构造函数
- 6:方法
- 6.1:成员方法
- 6.2:Getters和setters
- 6.3:抽象方法
- 7:抽象类
- 8:隐式接口
- 9:继承
- 9.1:重写方法
- 9.2:重写运算符
- 10:枚举
- 11:向类添加功能:mixins
- 12:静态变量和方法
- 12.1:静态变量
- 12.2:静态方法
Dart是一种具有类和基于Mixin的继承的面向对象语言。 每个对象都是一个类的实例,并且所有类都继承自Object。 基于Mixin的继承意味着尽管每个类(对象除外)都只有一个父类,但是一个类主体可以在多个类层次结构中重用。 扩展方法是一种在不更改类或创建子类的情况下向类添加功能的方法。
使用类的成员
对象的成员由方法和实例变量组成。 调用方法时,可以在对象上调用它:该方法可以访问该对象的方法和变量:
var p = Point(2, 2);
// 设置y
p.y = 3;
// 获取y
assert(p.y == 3);
// 执行distanceTo()方法
num distance = p.distanceTo(Point(4, 4));
利用 ?. 代替.避免在最左边的对象为null时而发生异常:
// 如果p不为null, 则设置y为4
p?.y = 4;
使用构造函数
可以使用构造函数创建对象。 构造函数名称可以是ClassName或ClassName.identifier。 例如,以下代码使用Point()和Point.fromJson()构造函数创建Point对象:
var p1 = Point(2, 2);
var p2 = 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)); // a和b是相同的对象
如果常量构造函数在常量上下文之外,并且在不使用const的情况下被调用,则它将创建一个非常量对象:
var a = const ImmutablePoint(1, 1); //创建一个常量
var b = ImmutablePoint(1, 1); //创建一个非常量
assert(!identical(a, b)); //不是同一个实例
获取对象的类型
要在运行时获取对象的类型,可以使用Object的runtimeType属性,该属性返回Type对象:
print('The type of a is ${a.runtimeType}');
实例变量
class Point {
num x; //声明实例变量x,默认为null。
num y; //声明y,默认为null。
num z = 0; //声明z,默认为0。
}
所有未初始化的实例变量的值都为null。
所有实例变量都会生成一个隐式的getter方法。 非final实例变量也会生成隐式的setter方法:
class Point {
num x;
num y;
}
void main() {
var point = Point();
point.x = 4;
assert(point.x == 4);
assert(point.y == null);
}
构造函数
通过创建一个与其类具有相同名称的函数来声明一个构造函数:
class Point {
num x, y;
Point(num x, num y) {
this.x = x;
this.y = y;
}
}
将构造函数参数分配给实例变量的模式非常普遍,Dart拥有语法糖使其变得简单:
class Point {
num x, y;
Point(this.x, this.y);
}
默认构造函数
如果不声明构造函数,则会提供默认的构造函数。 默认构造函数没有参数,并在父类中调用无参数构造函数。
构造函数不是继承的
子类不会从其父类继承构造函数。 声明没有构造函数的子类仅具有默认构造函数。
命名构造函数
使用命名构造函数可为一个类实现多个构造函数:
class Point {
num x, y;
Point(this.x, this.y);
// 命名构造函数
Point.origin() {
x = 0;
y = 0;
}
}
构造函数不是继承的,这意味着父类的命名构造函数不会被子类继承。 如果要使用父类中定义的命名构造函数创建子类,则必须在子类中实现该构造函数。
调用非默认父类构造函数
默认情况下,子类中的构造函数会调用父类的无参构造函数。 父类的构造函数在构造函数主体的开头被调用。 总而言之,执行顺序如下:
1、初始化变量
2、父类无参构造
3、主类无参构造
如果父类没有无参构造函数,则必须手动调用父类中的构造函数之一。 在构造函数主体之前,在冒号:之后指定父类构造函数:
class Person {
String firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
// Person 没有默认构造函数;
// 必须调用super.fromJson(data).
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
main() {
var emp = new Employee.fromJson({});
// 打印:
// in Person
// in Employee
if (emp is Person) {
emp.firstName = 'Bob';
}
(emp as Person).firstName = 'Bob';
}
初始化变量
除了调用父类构造函数外,还可以在构造函数主体运行之前初始化实例变量,用逗号分隔:
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
重定向构造函数
有时,构造函数的唯一目的是重定向到同一类中的另一个构造函数。 重定向构造函数的主体为空,构造函数调用出现在冒号:后面:
class Point {
num x, y;
Point(this.x, this.y);
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使其私有化
static final Map<String, Logger> _cache =
<String, Logger>{};
factory Logger(String name) {
return _cache.putIfAbsent(
name, () => Logger._internal(name));
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) print(msg);
}
}
就像调用其他构造函数一样来调用工厂构造函数:
var logger = Logger('UI');
logger.log('Button clicked');
方法
方法是提供对象行为的函数。
成员方法
对象上的成员方法可以访问实例变量。 以下示例中的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和setters
getters和setters是特殊的方法,可提供对对象属性的读写访问。 每个实例变量都有一个隐式的getter,如果合适的话还有一个setter。 可以通过使用get和set关键字实现getter和setter来创建其他属性:
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
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方法可以是抽象的,定义一个接口,但将其实现留给其他类使用。 抽象方法只能存在于抽象类中。
abstract class Doer {
void doSomething();
}
class EffectiveDoer extends Doer {
void doSomething() {
}
}
抽象类
使用abstract修饰符定义一个抽象类:一个无法实例化的类。 抽象类通常用于某些实现中,用于定义接口。 如果希望抽象类看起来可实例化,可以定义一个工厂构造函数。
抽象类通常具有抽象方法。 这是一个声明具有抽象方法的抽象类的示例:
abstract class AbstractContainer {
void updateChildren();
}
隐式接口
每个类都隐式定义一个接口,其中包含该类及其实现的所有接口的所有实例成员。 如果想创建一个支持B类API的A类而不继承B,则A类应实现B接口。
类通过在Implements子句中声明一个或多个接口,然后提供implements所需的API来实现一个或多个接口。 例如:
// Person:隐式接口包含greet()
class Person {
final _name;
// 不在接口中,因为这是构造函数
Person(this._name);
//在接口中
String greet(String who) => 'Hello, $who. I am $_name.';
}
// 实现Person接口的类.
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() {...}
// ···
}
重写运算符
可以重写+,-,*等的运算符。 例如,如果定义一个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);
}
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。
枚举
枚举类型是一种特殊的类,用于表示固定数量的常量值。
使用enum关键字声明枚举类型:
enum Color { red, green, blue }
枚举中的每个值都有一个index 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);
枚举类型具有以下限制:
1、不能继承,mix或实现枚举。
2、无法显式实例化枚举。
向类添加功能:mixins
mixin是一种在多个类层次结构中重用类代码的方法。
要使用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,请使用on来指定所需的父类:
mixin MusicalPerformer on Musician {
// ···
}
静态变量和方法
使用static关键字实现类范围的变量和方法。
静态变量
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
静态变量在使用之前不会初始化。
静态方法
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);
}
可以使用静态方法作为编译时常量。 例如,可以将静态方法作为参数传递给常量构造函数。