作者:opLW
目的:在阅读Dart概览后进行的总结,主要记录Dart相比Java的新特性,包括许多亲自实验后的总结。如有错误还望指正😄
其他文章:
Dart – 较Java新特性(一)
Dart – 较Java新特性(二)
目录
8.异常
- 8.1 概述
- 与Java不同Dart中没有必检异常和非必检异常之分,Dart中只有非必检异常。
- 即Dart不要求在一个方法中声明抛出异常(即没有和Java一样的关键字
throws
) - Dart不会要求必须使用
try..catch
结构捕获异常。
- 8.2 throw 与
Java
类似用于抛出一个异常。 - 8.3 catch
- on、catch 与Java不同Dart将声明异常类型的部分分离出来,用
on
关键字声明,用catch
关键字捕获异常。try { // 发生异常的代码 } on Exception catch (e) { // 用on声明异常的类型 print('Unknown exception: $e'); } catch (e) { // 没有用on声明异常类型,所以对于任何异常都能处理 print('Something really unknown: $e'); }
catch
块可以指定两个参数:第一个为e
,代表异常;第二个为s
,代表StackTrace
异常的栈信息。try { throw "null null"; } catch(e, s) { print("$s"); } // 同时如果你不喜欢e和s,也可以换成别的关键字如: try { throw "null null"; } catch(a, b) { print("$b"); }
- rethrow
rethrow
关键字可以将异常重新抛出,所以我们不仅可以在发生异常时处理异常,也可以将异常抛出反馈给函数调用者。void fun() { try { throw "null null"; } catch(e, s) { // 对异常进行预处理 rethrow; } } void main() { try { fun() } catch(e) { // 函数调用者进行处理 } }
- on、catch 与Java不同Dart将声明异常类型的部分分离出来,用
- 8.4 finally 与
Java
类似。
9.类
- 9.1 构造函数
- 9.1.1 简化的构造函数 如果不在函数体中做特别的事情,可将函数简化为:
// 此时必须有“;” Bicycle(this.cadence, this.speed, this.gear); // 等价于 Bicycle(int cadence, int speed, int gear) { this.cadence = cadence; this.speed = speed; this.gear = gear; }
- 9.1.2 默认构造函数 与java一样,Dart会提供一个默认的无参构造函数,在默认构造函数内会默认调用父类的无参构造函数。
- 9.1.3 重写构造函数 与java类似,Dart也可以创建自己的构造函数。与java一样,自定义构造函数之后,会失去默认的无参构造函数。
void main() { // var a1 = A(); 发生错误,因为无参构造函数被覆盖了 var a2 = A(1); } class A { var i; A(this.i); }
- 9.1.4 构造函数不能被继承 与java一样,构造函数不能被子类继承。
- 9.1.5 命名构造函数 除了常规的构造函数外,Dart引入了命名构造函数。我们可能因为不同的目的创建一个类,为构造函数命名有利于更好的识别不同的目的。形式如
类名.命名构造函数名字(){方法体}
,具体如下:
由于构造函数不能继承,所以父类的命名构造函数和子类的命名构造函数没有任何关系。void main() { var p1 = Point(1, 2); var p2 = Point.makeFromOtherPoint(p1); // 使用命名构造函数 } class Point { num x, y; Point(this.x, this.y); // 声明命名构造函数 Point.makeFromOtherPoint(Point other) { x = other.x; y = other.y; } }
- 9.1.6 初始化列表 Java会在调用构造函数前,对成员变量进行初始化。同样的Dart中通过初始化列表在构造函数调用前进行初始化。
注意class Point { final num x; final num y; final num distanceFromOrigin; // 初始化例表在构造方法签名后,方法体前 Point(x, y) : x = x, y = y, distanceFromOrigin = sqrt(x * x + y * y); }
- 开发时,可以在初始化列表中使用
assert
进行判断 - 初始化列表中,只能使用静态方法
- 开发时,可以在初始化列表中使用
- 9.1.7 调用父类构造函数 都知道无参的默认构造函数会默认先调用父类的无参构造函数。如果想调用父类的有参构造函数,需在子类的构造函数末尾添加
: super(参数)
,具体如下:
注意class A { var a = 1; A(this.a); } class B extends A { final c; var d = 4; // 调用父类的有参构造函数 B() : super(1) {} // 初始化列表和super结合时,super部分应该放在末尾 B(int p1) : c = 3, d = 4, super(p1) {} }
- 当初始化列表存在和调用父类的构造函数同时存在时,会优先执行初始化列表 > 父类构造函数 > 子类构造函数。虽然说Dart初始化列表类似Java中对成员变量进行初始化,但在java中父类的构造函数会先于子类的成员变量初始化,而Dart则相反子类的初始化列表先执行。
- 在
super()
的方法体中允许调用函数,但该函数需为静态函数或者其他非本类和非父类的实例函数,同时不能使用this
。
- 9.1.8 调用本类的其他构造函数
class Point { num x, y; // 类的主构造函数。 Point(this.x, this.y); // 指向主构造函数 Point.alongXAxis(num x) : this(x, 0); }
- 9.1.9 常量构造函数 如果一个类的所有对象总是固定不变的,那么可以将这些对象声明为编译期常量。声明为编译期常量,需要将所有属性声明为常量并且声明一个常量构造函数。
注意 上述步骤只是让一个类具备了成为编译期常量的能力,然而只有正确使用这个能力才能创建编译期常量。如下代码:class ImmutablePoint { final num x, y; const ImmutablePoint(this.x, this.y); }
// 方法一:在实例化对象前添加const var a = const ImmutablePoint(1, 1); // 创建一个常量对象 var b = ImmutablePoint(1, 1); // 创建一个非常量对象 assert(!identical(a, b)); // 两者不是同一个实例! // 方法二:创建一个常量上下文,此后在常量上下文内创建的对象都为常量(前提是改类型含有常量构造函数) const pointsMap = { 'point1': ImmutablePoint(0, 0), 'point2': ImmutablePoint(1, 10) };
- 9.1.10 工厂构造函数 传统的构造函数都是创建一个新的对象,工厂构造函数让构造函数不再总是创建新对象。工厂构造函数的调用与正常构造函数一样。
注意 由于工厂构造函数并不总是创建新的对象,所以无法在工厂构造函数内使用class Logger { final String name; // _cache 是私有属性。 static final Map<String, Logger> _cache = <String, Logger>{}; Logger(this.name); factory Logger(String name) { if (_cache.containsKey(name)) { // 从缓存中获取 return _cache[name]; } else { // 调用正常的构造函数创建对象并设置到缓存中 final logger = Logger(name); _cache[name] = logger; return logger; } } }
this
。
- 9.1.1 简化的构造函数 如果不在函数体中做特别的事情,可将函数简化为:
- 9.2 getter和setter
- 9.2.1 默认getter和setter Dart默认为所有变量提供默认
getter
以及为非常量提供setter
方法。
class Point { num x; } void main() { var point = Point(); point.x = 4; // 实际上调用setter方法进行设置 }
- 9.2.2 重载getter和setter方法 通过自定义
getter
和setter
添加自己的内容。详细注意点见:
注意class Point { // 不需要声明变量x num get x { print("access val x"); return x; } set x(num value) { print("the x is $value now"); } num get y { //print("$y"); 错误:访问y,导致循环调用y.get()方法,导致栈溢出 return y; } set y(num value) { //y = value; 错误:设置y,引发栈溢出 print("$value"); // 只能通过value访问 } } void main() { var point = Point(); point.x = 4; // Use the setter method for x. }
- 重写的
getter
和setter
方法具有同时声明变量的能力,不需要额外声明变量 getter
方法内,除了return
语句外,其他地方不能访问该变量setter
方法内,不需要额外使用如y = value
的赋值语句,同时只能通过value
访问该变量- 如自减、自增等需要多次访问变量的运算符,Dart只会调用一次
getter
,然后用临时变量暂存避免重复调用引发错误
- 9.2.1 默认getter和setter Dart默认为所有变量提供默认
- 9.3 抽象类 与Java基本相同。
- 9.4 隐式接口 Dart中不再显示的提供
interface
用于声明接口,而是为每一个类隐式的生成接口。即我们可以实现一个普通的类,此时会要求实现该类的所有实例方法(不包括静态方法和构造方法)、getter
和setter
及该类实现的接口的方法。
如果嫌需要实现的内容太多则通过继承抽象类的方式。class A { void aFun() { print("I am aFun in A Class."); } } class B implements A { fianl b1 = 1; var b2 = 2; } class C implements B { // 此时会要求实现A.aFun()、B.b1的getter方法、B.b2的getter和setter }
- 9.5 继承 与Java基本相同。
- 重写方法 通过
@override
关键字标记重写方法。 - 重写运算符 Dart允许对运算符进行重载,这样就可以让两个对象相加了。
- 允许重写的运算符
<
、<=
、>
、>=
、+
、-
、/
、~/
、*
、%
、|
、^
、&
、<<
、>>
、[]
(下标访问符)、[]=
(下标赋值符)、~
、==
- 重写运算符的函数模式 如:
返回值 operator 运算符(参数) {方法体}
总体上多了关键字operator
并把方法名改为对应要重写的符号class Vector { final int x, y; Vector(this.x, this.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)); }
- 重写== Dart的
==
类似于Java中的equals
方法,所以相同的重写==
符号时,需要重写hashCode
的getter
方法。
- 允许重写的运算符
- 重写方法 通过
- 9.6 快速扩展类的功能–Mixin
- 参考文章 混入–Mixin
- Java中实现接口存在的问题 Java中如果几个类具有相同的功能,我们往往将这些共同的功能抽象为接口,这样有什么问题?Java中往往会出现一种情况就是不同的类需要实现同一个接口,并且实现的代码是一样的,如狗和鸟都需要实现走路这个方法,这样就带来了重复性工作;同时鸟除了走路之外还会飞,所以还需要另外的接口。因此就有较多的接口,较多的方法需要实现
- Dart的Mixin能解决的问题 Dart中通过Mixin,使得一个类如果需要某个方法或属性,只需简单的通过
with TargetClass
来快速获取该类的方法。如下代码所示,此时P拥有A的fun1
方法,不需要在P中再实现。class A { void fun1(); } class P with A
- 注意点 假设Mixin的多个类中含有相同的方法,则后者会覆盖前者对该方法的实现。
mixin A { void fun1(); } mixin B { void fun1(); } class P with A, B // A和B具有相同的方法最后P得到的fun1为B中的实现
tip 可通过将A和B的class关键字换为mixin来让A和B只能作为被混入的对象,而不能作为被继承的类
- 注意点 假设Mixin的多个类中含有相同的方法,则后者会覆盖前者对该方法的实现。
- 9.7 枚举类 和Java一样,Dart的枚举类不能被继承、实例化。
- 9.8 获取实例对象的类型 所有类的父类Object提供了一个属性
runtimeType
用于获取实例的类型。print('The type of a is ${a.runtimeType}');
万水千山总是情,麻烦手下别留情。
如若讲得有不妥,文末留言告知我,
如若觉得还可以,收藏点赞要一起。