作者:opLW
目的:在阅读Dart概览后进行的总结,主要记录Dart相比Java的新特性,包括许多亲自实验后的总结。如有错误还望指正😄
其他文章:
Dart – 较Java新特性(一)Dart – 较Java新特性(二)

目录

8.异常
9.类


8.异常
  • 8.1 概述
  • 与Java不同Dart中没有必检异常和非必检异常之分,Dart中只有非必检异常。
  1. 即Dart不要求在一个方法中声明抛出异常(即没有和Java一样的关键字throws)
  2. Dart不会要求必须使用try..catch结构捕获异常。
  • Dart除了抛出传统的异常类ExceptionError外,还可以抛出不为任何不为null的对象。
void main() {
  throw "I am a String";
}
// 抛出异常:Uncaught Error: I am a String
  • 8.2 throwJava类似用于抛出一个异常。
  • 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");
}
  • rethrowrethrow关键字可以将异常重新抛出,所以我们不仅可以在发生异常时处理异常,也可以将异常抛出反馈给函数调用者。
void fun() {
    try {
       throw "null null";
    } catch(e, s) {
       // 对异常进行预处理
       rethrow;
    }
}

void main() {
    try {
        fun()
    } catch(e) {
        // 函数调用者进行处理
    }
}
  • 8.4 finallyJava类似。


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);
}
  • 注意
  1. 开发时,可以在初始化列表中使用assert进行判断
  2. 初始化列表中,只能使用静态方法
  • 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) {}
}
  • 注意
  1. 当初始化列表存在和调用父类的构造函数同时存在时,会优先执行初始化列表 > 父类构造函数 > 子类构造函数。虽然说Dart初始化列表类似Java中对成员变量进行初始化,但在java中父类的构造函数会先于子类的成员变量初始化,而Dart则相反子类的初始化列表先执行。
  2. 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.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方法 通过自定义gettersetter添加自己的内容。详细注意点见:
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.
}
  • 注意
  1. 重写的gettersetter方法具有同时声明变量的能力,不需要额外声明变量
  2. getter方法内,除了return语句外,其他地方不能访问该变量
  3. setter方法内,不需要额外使用如y = value的赋值语句,同时只能通过value访问该变量
  4. 如自减、自增等需要多次访问变量的运算符,Dart只会调用一次getter,然后用临时变量暂存避免重复调用引发错误
  • 9.3 抽象类 与Java基本相同。
  • 9.4 隐式接口 Dart中不再显示的提供interface用于声明接口,而是为每一个类隐式的生成接口。即我们可以实现一个普通的类,此时会要求实现该类的所有实例方法(不包括静态方法和构造方法)、gettersetter及该类实现的接口的方法。
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方法,所以相同的重写==符号时,需要重写hashCodegetter方法。
  • 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只能作为被混入的对象,而不能作为被继承的类

  • 9.7 枚举类 和Java一样,Dart的枚举类不能被继承、实例化。
  • 9.8 获取实例对象的类型 所有类的父类Object提供了一个属性runtimeType用于获取实例的类型。
print('The type of a is ${a.runtimeType}');

万水千山总是情,麻烦手下别留情。
如若讲得有不妥,文末留言告知我,
如若觉得还可以,收藏点赞要一起。