Flutter之Dart基础语法(二)
一.类和对象
-
与java一样,Dart是一门面向对象的语言,对象具有特定的属性和动作,每个对象都是一个类的实例,对象的属性对应一个类中的成员变量,而对象的动作则对应一个类中的方法,所有的类都继承自Object,通过创建一个对象,可以访问对象的成员变量和方法
var p = Point(2, 2); print(p.toString());//打印结果 {x: 2, y: 2} p.y = 3; print(p.toString());//打印结果 {x: 2, y: 3} class Point { int x = 0; int y = 0; Point(int x, int y) { this.x = x; this.y = y; } @override String toString() { // TODO: implement toString return '{x: ${this.x}, y: ${this.y}}'; } }
-
为避免对象为空导致的空指针异常,在访问对象的成员变量和方法时,可以用’?.‘替换’.’
print(p?.y);//打印结果 3 p = null; print(p?.y);//打印结果 null
-
在Dart2的语法中,创建对象时,可以省略new关键字不写
var p1 = Point(2, 2);//等价于 var p1 = new Point(2, 2); print(p1);//打印结果 {x: 2, y: 2}
-
当一个类的构造函数是常量构造函数(构造函数被const修饰)时,要使用常量构造函数创建编译时常量,需要将const关键字放在构造函数名之前:
var p2 = const ImmutablePoint(2, 2); print(p2);//打印结果 {x: 2, y: 2} class ImmutablePoint { final int x; final int y; const ImmutablePoint(this.x, this.y); @override String toString() { // TODO: implement toString return '{x: ${this.x}, y: ${this.y}}'; } }
-
构造两个相同的编译时常量会生成一个单一的、规范的实例:
var p3 = const ImmutablePoint(1, 1); var p4 = const ImmutablePoint(1, 1); print(identical(p3, p4));//打印结果 true:表明p3和p4是同一个对象
-
要在运行时获得对象类型,可以使用对象的runtimeType属性,该属性返回一个类型对象。
print('The type of p3 is ${p3.runtimeType}');//打印结果 The type of p3 is ImmutablePoint
-
成员变量的声明:
class Point { num x; //声明成员变量,初值为null num y; //声明成员变量,初值为null num z = 0; //声明成员变量,初值为0 }
二.构造方法
-
使用构造方法生成一个类的新实例
class Point { num x, y; Point(num x, num y) { this.x = x; this.y = y; } }
-
使用构造方法的参数为实例赋值
class Point { num x, y; Point(this.x, this.y); }
-
默认构造方法:如果不声明构造方法,则为您提供默认构造方法。默认构造方法没有参数,并在其父类中调用无参数构造方法
var child = Child();//打印结果 Parent class Parent { Parent() { print('Parent'); } } class Child extends Parent { }
-
子类不会继承父类的构造方法,没有声明构造方法的子类只有默认的构造方法(没有参数,没有名称)而不是继承父类的构造方法。
-
命名构造方法:使用命名构造方法可以在一个类中定义多个构造方法
class Point { num x, y; Point(this.x, this.y); // Named constructor Point.origin() { x = 0; y = 0; } }
-
调用非默认的父类构造方法:默认情况下,子类中的构造方法调用父类的未命名的无参数构造方法。父类的构造方法在子类构造方法体的开始处被调用。如果子类中有使用初始化列表,初始化列表将在调用父类类之前执行。综上所述,执行顺序如下:
1.子类初始化列表
2.父类中的无参数构造方法被调用
3.子类的无参构造方法被调用如果父类没有未命名的无参数构造方法,则必须手动调用父类中的一个构造方法。在冒号(:)之后,在构造方法体(如果有的话)之前指定父类构造方法
class Person { String firstName; Person.fromJson(Map data) { print('in Person'); } } class Employee extends Person { Employee.fromJson(Map data) : super.fromJson(data) { print('in Employee'); } } main() { var emp = new Employee.fromJson({}); if (emp is Person) { // Type check emp.firstName = 'Bob'; } (emp as Person).firstName = 'Bob'; } ///结果输出为 in Person in Employee
-
初始化列表:除了调用父类构造方法之外,还可以在构造方法主体运行之前初始化实例变量。初始值设定项用逗号分开
var p5 = Point.fromJson({'x': 2, 'y': 3}); print(p5);//打印结果 {x: 2, y: 3} class Point { int x = 0; int y = 0; Point(int x, int y) { this.x = x; this.y = y; } //命名构造方法 Point.fromJson(Map<String, int> data) : x = data['x'],y = data['y'] { } @override String toString() { // TODO: implement toString return '{x: ${this.x}, y: ${this.y}}'; } }
在开发期间,可以通过在初始化列表中使用assert来验证输入
var p6 = Point.withAssert(-1, 1);//此处会抛异常 print(p6); class Point { int x = 0; int y = 0; Point(int x, int y) { this.x = x; this.y = y; } //命名构造方法 Point.fromJson(Map<String, int> data) : x = data['x'],y = data['y'] { } Point.withAssert(this.x, this.y) : assert(x >= 0) { print('In Point.withAssert(): ($x, $y)'); } @override String toString() { // TODO: implement toString return '{x: ${this.x}, y: ${this.y}}'; } }
初始化列表在设置final字段时很方便
var p7 = new Point(2, 3); print(p7.distanceFromOrigin);//打印结果 3.605551275463989 class Point { final num x; final num y; final num distanceFromOrigin; Point(x, y) : x = x, y = y, distanceFromOrigin = sqrt(x * x + y * y); }
-
重定向构造方法:重定向构造方法的唯一目的是重定向到同一个类中的另一个构造方法。重定向构造方法的主体为空,构造方法调用出现在冒号(:)之后。
var p8 = Point.alongXAxis(3); print(p8); class Point { int x = 0; int y = 0; Point(int x, int y) { this.x = x; this.y = y; } //命名构造方法 Point.fromJson(Map<String, int> data) : x = data['x'],y = data['y']; Point.withAssert(this.x, this.y) : assert(x >= 0) { print('In Point.withAssert(): ($x, $y)'); } //重定向构造方法 Point.alongXAxis(int x) : this(x, 0); @override String toString() { // TODO: implement toString return '{x: ${this.x}, y: ${this.y}}'; } }
-
常量构造方法:定义一个常量构造方法(使用const修饰的构造方法),需要确保所有成员变量都是final类型的,通过常量构造方法生成的对象是一个编译时常量
class ImmutablePoint { final int x; final int y; const ImmutablePoint(this.x, this.y); @override String toString() { // TODO: implement toString return '{x: ${this.x}, y: ${this.y}}'; } }
-
工厂构造方法:在实现构造方法时使用factory关键字,该构造方法并不总是创建类的新实例。例如,工厂构造方法可以从缓存返回实例,也可以返回子类型的实例,工厂构造方法不能访问this关键字
var logger = Logger('UI'); logger.log("Button clicked");//打印结果 Button clicked class Logger { final String name; // _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) { print(msg); } }
三.成员方法
-
实例方法:对象的实例方法可以访问对象的实例变量
print(p8.distanceTo(Point(5, 5)));//打印结果 5.385164807134504 class Point { int x = 0; int y = 0; Point(int x, int y) { this.x = x; this.y = y; } //命名构造方法 Point.fromJson(Map<String, int> data) : x = data['x'],y = data['y']; Point.withAssert(this.x, this.y) : assert(x >= 0) { print('In Point.withAssert(): ($x, $y)'); } //重定向构造方法 Point.alongXAxis(int x) : this(x, 0); @override String toString() { // TODO: implement toString return '{x: ${this.x}, y: ${this.y}}'; } //成员方法 double distanceTo(Point other) { var dx = x - other.x; var dy = y - other.y; return sqrt(dx * dx + dy * dy); } }
-
getter和setter方法:getter和setter是对对象属性的读写访问的特殊方法。回想一下,每个实例变量都有一个隐式的getter,如果需要的话还可以加上一个setter。使用get和set关键字来实现getter和setter方法可以来读写其他属性
-
抽象方法:实例方法、getter和setter方法可以是抽象方法,只定义一个接口但是将具体实现留给其他类。抽象方法只能存在于抽象类中,抽象方法是没有方法体的。
abstract class Doer { void doSomething(); } class EffectiveDoer extends Doer { void doSomething() { } }
四.抽象类
-
使用abstract修饰符定义的不能被实例化的类称为抽象类
abstract class AbstractContainer { void updateChildren(); }
五.隐式接口
- 每个类都隐式地定义一个接口,该接口包含类的所有实例成员及其实现的任何接口。如果您想创建一个类A,它支持类B的API而不继承B的实现,那么类A应该实现B接口
六.继承与重写
- 与java类似
七.枚举类型
-
使用enum关键字声明一个枚举类型
enum Color { red, green, blue }
-
枚举中的每个值都有一个索引getter,它返回enum声明中值的从0开始的位置。例如,第一个值有索引0,第二个值有索引1
assert(Color.red.index == 0); assert(Color.green.index == 1); assert(Color.blue.index == 2);
-
要获取枚举中所有值的列表,请使用enum的values常量
List<Color> colors = Color.values; assert(colors[2] == Color.blue);
八.类方法和类变量
- 类方法: 在类中,被static修饰的方法
- 类变量: 在类中,被static修饰的成员变量
九.箭头函数
- 如果函数体只有一行返回值,可以使用箭头函数:
bool isZero(int num) => num == 0;
//等价于
bool isZero(int num) {
return num == 0;
}