Dart基础(二)

Dart是一种基于Mixin继承机制的面向对象的语言。除了NULL之外所有的类都是Object类的子类。Dart是一个单继承的语言,每个类只有一个超类。
可以通过Mixin来实现一个类使用多个mixin的方法实现,通过方法扩展在不更改类不创建子类的情况下追加方法功能。
Dart遵循《风格推荐指南》,所有的类名都是以驼峰形式命名。

类成员的使用

对象的成员函数数据(即方法和实例变量)组成。方法的调用要通过对象来完成。

构造函数

构造函数可以通过 类名 或者 类名.标识符 的形势来调用,比如:

var point = Point(1,1);
var point2 = Point.formatJson({'x':1,'y':2})

Dart 中new关键字可以省略

如果在定义构造函数时加上了const则表示声明了一个常量构造函数,那么使用时,使用const则表示声明了一个编译时常量,两个相同的常量构造函数和相同的参数声明的对象是相同的。

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

print(identical(a, b)); //true

方法和变量的调用

  • 通过(.)来调用对象的变量和方法
  • 通过(?.)来避免对象实例为空的情况下调用变量和方法时发生异常
  • 静态的变量和方法,直接通过类.变量名 / 类.方法名来调用

获取对象类型

通过Object类的属性runtimeType来获取对象的类型

类成员的定义

实例变量的定义

有以下几种方式定义类的实例变量

class TestClass {
    // 定义一个可空的变量,变量初始值为null
    double? canBeNullVariable;
    // 定义一个可空的变量,显示声明变量初始值为null
    double? canBeNullVariable2 = null;
    // 定义一个不可空变量,必须显示的声明变量初始值为非null的字面量
    double cantBeNullVariable = 1.0;
    // 使用late修饰一个不可空的变量,可以不赋初始值,在使用前赋值
    late double cantBeNullVariable2;
    //  使用final修饰的不可变量,必须赋初始值
    final double cantBeNullVariable3 = 2.0;
    // 使用late修饰的不可变量,可以不赋初始值,在使用前赋值,只能赋值一次
    late final double cantBeNullVariable4;
}

所有的实例变量都会隐式的声明一个Getter方法,所有非不可变量的实例变量或者时late final的不可变量都会隐式的声明Setter方法。late final的set方法只能调用一次,再次调用的时候抛出异常。

区别于Java,在不添加late修饰时,全局变量定义引用其他变量是不正确的,在编译时会报错。

class TestClass {
double a = 1;
double b = 2;
// 这样定义是不正确的,编译器会报错
//double c = a + b;

// 必须加上 late
late double c = a + b;
// 只有在late修饰的情况下, 才可以使用this
late double d = this.a + this.c;

}

构造函数的定义

通常构造函数是使用与类名同样名称的函数来定义。例如:

class Point {
  double x = 0;
  double y = 0;

  Point(double x, double y) {
    // 设置实例的初始值
    this.x = x;
    this.y = y;
  }
}

Dart提供了特殊的语法糖来在构造函数时给非空变量和不可变量赋初始值或默认值;也就是说可以不在定义的时候赋值。

class Point {
  double x;
  final double y;
  late double z;

  Point(this.x, this.y);
}

在构造函数的参数中通过 this 关键字指定非空变量或不可变量来赋值

  • 每个类都默认有一个无参的构造函数
  • 子类不会继承父类的构造函数,没有定义子类的构造函数时,子类只有默认的无参构造函数
  • Dart支持命名式构造函数
    class Point{
      double x;
      double y;
    
      Point.origin():
          x = 10,
          y = 20;
    }
    
    // 调用命名构造函数
    var p = Point.origin();
    

    命名构造函数也不可以继承,子类如果要声明一个跟父类同名的命名构造函数,必须显示的声明。

    class Person{
      String? name;
    
      Person.initName(String name):
          this.name = name;
    }
    
    class Employee extends Person{
    
      Employee(super.name):super.initName();
    
      Employee.initName(super.name):super.initName();
    }
    
    • 上面例子中的 super.name属于超类参数,可以避免重复参数赋值。使用super关键字。在超类的构造函数中就不需要再次使用相应的参数了
    • 初始化参数,在构造函数体执行之前还可以初始化参数,使用:进行初始化,每个参数之间使用 , 隔开。
    Point.fromJson(MapM<String, double> json):
      x = json['x']!, y = json['y']!{
          print(`x is ${this.x} & y is ${this.y}`);
      }
    
    • 重定向构造函数,使用:this 来指向重定向的构造函数
    class Point {
      double x = 0;
      double y = 0;
    
      Point(this.x, this.y);
    
      Point.onlyXAxios(double x): this(x,0);
    }
    
    • 常量构造函数,在构造函数前加上 const,并确保数据都是final的,可以定义一个编译时的常量对象。
    • 使用 factory 关键字标识类的构造函数将会令该构造函数变为工厂构造函数,这将意味着使用该构造函数构造类的实例时并非总是会返回新的实例对象。

    工厂构造函数中,不能使用this关键字

方法

操作符方法

除了定义普通的方法外,Dart中的运算符也是特殊名称的实例方法。以下运算符是可以被你当成方法使用:

< + | >>> > / ^ [] <= ~/ & []= >= * << ~% >> ==

使用 operator 标识来进行标记来重写操作符。下面是重写 + 和 - 操作符的例子

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);

  @override
  bool operator ==(Object other) =>
      other is Vector && x == other.x && y == other.y;

  @override
  int get hashCode => Object.hash(x, y);

  @override
  String toString() => 'the x is $x , the y is $y';
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  print(v + w ); //the x is 4 , the y is 5
  print(v - w ); //the x is 0 , the y is 1
}

Getter 和 Setter 方法

默认情况下,Dart会为每个变量隐式的创建Getter和Setter方法。但如果显示的声明了get 或set方法后,Dart将不会自动创建Getter或Setter方法。

class TestGetAndSet {
    double a = 1;
    double b = 2;
    // 可以使用this关键字
    double get c => this.a + this.b;
        set c(double x)=>this.a = this.b + this.x;
    // 不能调用其set方法
    double get d => this.a + this.c;
    // 不能调用其 get方法
    set e(double x)=> this.a = this.d + x;

}

void main(){
    var test = TestGetAndSet();
    print(test.a); // 1
    print(test.b); // 2
    print(test.c); // 3
    print(test.d); // 4
    // e 没有get方法,因此不能调用test.e
    // print(test.e);
    test.c = 10;
    print(test.a); // 12
    print(test.c); // 14
    test.e = 10;
    print(test.a); // 36
    print(test.c); // 38
    print(test.d); // 74
    // d 没有set方法,不能赋值
    //test.d = 10;
    
}

像自增(++)这样的操作符不管是否定义了 Getter 方法都会正确地执行。为了避免一些不必要的异常情况,运算符只会调用 Getter 一次,然后将其值存储在一个临时变量中。

抽象方法

没有方法体的函数就是抽象方法,抽象方法必须在抽象类中。

抽象类

使用关键字 abstract 标识类可以让该类成为 抽象类,抽象类将无法被实例化。抽象类常用于声明接口方法、有时也会有具体的方法实现。

抽象类常常会包含抽象方法。

隐式接口

一个类可以通过关键字 implements 来实现一个或多个接口并实现每个接口定义的除了构造函数之外的所有函数(包括Getter & Setter)。

class Person{
    String name;

    Person(this.name);

    String sayHello(){
        return 'hello guys, I am $name';
    }
}

class Employee {
    //必须定义,get set方法也要重新
    String name;

    Employee.named(String name):this.name=name;

    // 必须重写Person类中定义的所有方法,且不可以调用super来实现
    String sayHello(){
        return 'hello emplpyee, I am $name';
    }
}

扩展类

使用 extends 关键字来创建一个子类,并可使用 super 关键字引用一个父类:
通过 @override来标记重写一个类成员。

  • 重写的方法返回类型必须要和超类方法的返回类型一致;
  • 重写的方法的参数类型,参数个数必须和超类的方法的类型,个数一致;子类参数类型可以上父类方法类型的超类。
  • 位置参数个数一定要一致
  • 重写泛型方法必须也是泛型,重写非泛型方法必须也是非泛型

扩展方法

扩展方法可通过extension-on的关键字来实现。

extension NumberParsing on String {
    int parseInt(){
        return int.parse(this);
    }
}

使用时,需要先导入定义的文件。如果上述代码定义在test.dart文件中。

import 'test.dart';

void main(){
    print('42'.parseInt().runtimeType); //int
}

如果是一个动态类型,那么扩展方法将不可用 dynamic a = '123' 变量a将不能使用 a.parseInt()这一扩展方法,但是 var a = '123' 通过类型推断可以推断出是String,则可以使用扩展方法。

扩展方法冲突

如果有多个扩展文件中定义了相同名称的扩展方法时,扩展方法则会不生效。

  • 可使用 hide进行屏蔽
  • 可以指定使用扩展命名
  • 如果文件有相同的扩展名,则可以使用别称。
/// test、test1、test2 都包含parseInt的扩展方法
/// test、test3 的扩展都为NumberParse
/// test2的扩展名为你NumberParse1
import 'test.dart';
import 'test2.dart';
import 'test3.dart' as alisName;

print(NumberParse('10').parseInt());
print(NumberParse1('10').parseInt());
print(alisName.NumberParse('10').parseInt());

Mixin添加功能

Mixin是一种在多重继承中复用某个类中代码的方法模式。可以理解中是一个将公共方法提取出来的工具类。
使用 with 关键字并在其后跟上 Mixin 类的名字来使用 Mixin 模式

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

实现一个Mixin类;请创建一个继承自 Object 且未声明构造函数的类。除非你想让该类与普通的类一样可以被正常地使用,否则请使用关键字 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');
    }
  }
}

可以使用关键字 on 来指定哪些类可以使用该 Mixin 类,指定后,只有指定类和其子类可以使用这一Mixin类。

枚举

枚举类型是一种特殊的类型,用于定义一些固定数量的常量值。

所有的枚举都继承于 Enum类。枚举类是封闭的,即不能被继承、被实现、被mixin 混入或显式被实例化。

声明枚举

通过关键字 enum 来声明枚举。

enum Color {red, yellow, green};

增强型枚举

Dart 中的枚举也支持定义字段、方法和常量构造,常量构造只能构造出已知数量的常量实例
可以使用与定义 类 类似的语句来定义增强的枚举,但是这样的定义有一些限制条件:

  • 实例的字段必须是 final,包括由 mixin 混入的字段。
  • 所有的实例化构造 必须以 const 修饰。
  • 工厂构造 只能返回已知的一个枚举实例。
  • 由于 Enum 已经自动进行了继承,所以枚举类不能再继承其他类。
  • 不能重载 indexhashCode 和比较操作符==
  • 不能声明values字段,否则它将与枚举本身的静态 values getter 冲突。
  • 在进行枚举定义时,所有的实例都需要首先进行声明,且至少要声明一个枚举实例。

使用枚举

  • 枚举使用与使用静态变量一致
  • 每个枚举都包含一个名为 index 成员变量的 Getter 方法;索引从0开始。
  • 想要获取全部的枚举值,可通过values来获取

异常

异常的定义

Dart 可以抛出和捕获异常,如果代码抛出的异常没有被捕获,程序就会被终止。与Java不一样的是,Dart的不需要在方法上声明抛出哪些异常,也就是说所有异常都是运行时异常。Dart提供了ExceptionError两种异常,你可以抛出/捕获他们以及他们的子类。

throw FormatException('Expected at least 1 section');

Dart也不限定一定要抛出ExceptionError的类型,事实上,可以抛出任意非null的类型。但是强烈不建议这么做。

throw 'Out of llamas!';

我们在定义抛出异常时,应该使用Exception及其子类,而避免使用Error,Error通常表示无法预料和捕获的错误,一旦出错终止程序将是最安全的操作。

异常的捕获

  • 可以通过on catchcatch来捕获异常,on catch指定异常类型捕获,而catch是捕获所有的异常信息
try {
  // ···
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

catch的第一个参数表示异常的对象, 而第二个参数表示异常的堆栈信息

  • 可以通过rethrow在catch中重新抛出异常。
void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // Runtime error
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // Allow callers to see the exception.
  }
}

调用misbehave的时候,仍然需要catch异常,否则程序会终止。

  • 无论是否捕获异常,finally都会执行,如果捕获异常,则finallycatch之后执行,如果没有捕获异常,那么finally执行完成后,抛出异常。

泛型

泛型是表示一个参数化的类型,使用指定的泛型类型可以更好的帮助代码生成,减少重复代码。

泛型的使用

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}

使用泛型,就不需要为每个类型的值都定义一个Cache类,同时也避免定义Object类型再做强制类型转换。

  • 集合字面量定义;List、Set 以及 Map 字面量也可以是参数化的。定义参数化的 List 只需在中括号前添加 <type>;定义参数化的 Map 只需要在大括号前添加 <keyType, valueType>
  • 类型参数化构造函数 如:var map = Map<String,String>();

泛型类型

Dart中的泛型是固化的,在运行时也能保持类型信息。这一点与java不同,java的泛型类型在运行时时擦除的,在运行时是不会保持类型信息的。

var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

限制参数类型

有时使用泛型的时候,有时需要限制可作为参数的泛型范围,也就是参数必须是指定类型的子类,这时候可以使用 extends 关键字。

限制非空类型时,使用 T extends Object,而非 Object?

泛型方法

在方法中使用泛型function<T>,可以有

  • 返回值使用泛型
  • 参数使用泛型
  • 局部变量使用泛型
T func<T>(List<T> params){
    T temp = params[0];
    return temp;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容

  • 级别: ★☆☆☆☆标签:「Flutter 」「Dart」「Dart Functions」作者: WYW审校: Q...
    QiShare阅读 1,554评论 0 4
  • 前言:笔者最近看了Flutter相关的内容,而Flutter的基础库是由Dart编写的,所以笔者学习了关于Dart...
    Lucky_Man阅读 569评论 0 1
  • 总体概述 运算符 流程控制 类和对象 泛型 库的使用 一、运算符 1.1、Dart 所有的运算符描述运算符一元后缀...
    IIronMan阅读 496评论 2 3
  • 概览 一、运算符二、流程控制三、类和对象四、泛型五、库的使用 一、运算符 你可能会疑惑,Dart为什么要搞出这么多...
    得_道阅读 502评论 0 0
  • 前文链接: Dart简介 Dart语法(上) 内容: 函数(方法)函数定义及各类函数;函数参数;闭包 面向对象定义...
    玉圣阅读 2,253评论 0 2