[Flutter]flutter基础之Dart语言基础(三)

一、函数

函数用于将代码结构化,将复杂的问题简单化,实现根据功能拆分程序,使得代码可以实现复用。

Dart 中的入口函数为 main ,无论 main 函数放在哪都会从此开始执行代码。关于 main 函数的结构在第一篇文章中已经做了说明,这里不再赘述。Dart 中一且皆对象,因此,函数也是对象,为 Function 类型对象。

Dart 中提供了很多常用的函数库,通过这些函数能很方便的实现一些通用功能,在开发过程中可以直接使用。但是很多功能还需要开发人员自己实现。

Dart 中函数可以嵌套定义。

1. 自定义基本函数

一个函数的完成格式如下:

返回值 函数名(参数列表) {
    函数体
}

比如定义一个实现两个数相加的函数,如下:

num addFunction(num a, num b) {   
  return a + b;
}

其中,第一个 num 为返回值类型,addFunction 为函数名,括号内为函数的参数列表,这里是两个 num 类型的 ab 。因为 num 类型是 intdouble 的父类,所以这个函数可以完成整型和浮点型的相加操作。函数定义好后,需要在 main 函数中调用才会执行,如下:

main(){
  num sum = addFunction(10, 20);
  print(sum);  //输出 30
}
     
num addFunction(num a, num b) {
  return a + b;
}

调用函数并定义一个 num 类型的变量 sum 来接收函数的返回值。调用函数时,传入的参数类型应与函数的参数类型和数量相对应,否则会报错。

前面第一篇文章在介绍 main 函数时就说过,在 Dart 中,函数的返回值类型是可以省略的,其实,参数类型也是可以省略的,Dart 会根据调用时传入的参数类型来自动推断数据类型。而返回值类型,如果函数有返回值则根据返回值类型进行推断,如无返回值,则推断为 Null ,而非 void

main(){
  var sum = addFunction(10, 20);
  print(sum);  //输出 30
}
     
addFunction(a, b) {
  return a + b;
}

需要注意的是,如不定义参数类型,需要在函数体内做好边缘处理,防止类型错误而抛出异常。也需要确定传入的参数在函数内是否有意义,如函数处理功能与参数类型无关,或无法处理,则没有任何意义。

Dart 中,如果函数体内只有一个表达式,可以写成如下形式:

main(){
  num sum = addFunction(10, 20);
  print(sum);  //输出 30
}

//只有一个表达式,简写成如下形式,参数类型等依然是可以省略的
addFunction(num a, num b)=> a + b;

其中 => expr 语法为 {return expr;},此语法可称为箭头语法。

因为 Dart 中函数也是对象(Function)对象,所以可以将函数赋值给变量或作为参数进行传递。

main(){
  var iSum = addFunction;
  num sum = iSum(10, 20);
  print(sum);
}
     
addFunction(num a, num b)=> a + b;

函数类型也可作为参数和返回值类型,如下:

void main(){
  var res = printFunction(printInfo);
  print(res.runtimeType);   //输出  () => dynamic
}

printInfo() {
  print("这是一个函数");
}

Function printFunction(Function s) {
  s();    //执行函数 输出 这是一个函数
  return s;
}
2.可选参数函数

Dart 中,函数可以具有两种类型的参数:必须参数和可选参数。当两种类型的参数都存在时,应先列出必须参数,然后列出可选参数。可选参数又分为命名参数和位置参数。使用时,可以单独使用命名参数或位置参数,但是两者不能同时在一个函数参数中使用。

前面定义的函数中都为必须参数,在调用函数时必须传递。而可选参数是指,在进行函数调用时,可以传入此参数,也可以不传入此参数。

命名参数

命名参数是在定义函数时,将命名参数放在{}中,如下:

personInfo({String sex, int age}) {
    print("性别:$sex 年龄:$age");
}

调用方式为根据参数名传入参数,格式为:paramName:value ,如下:

main(){
  personInfo();   //不传参数 输出 性别:null 年龄:null
  personInfo(sex:"男"); //传入性别 输出 性别:男 年龄:null
  personInfo(age:20);   //传入年龄 输出 性别:null 年龄:20
  personInfo(sex:"男", age: 20); //传入性别 年龄 输出 性别:男 年龄:20
  personInfo(age: 20, sex:"男"); //传入性别 年龄 输出 性别:男 年龄:20 
}
     
personInfo({String sex, int age}) {
    print("性别:$sex 年龄:$age");
}

命名参数是根据指定的参数名称寻找对应的参数,确定其类型,所以传入的顺序可以不定。

虽然命名参数是可选参数,但是也可以强制提供该参数,使用关键字 @required 。如下:

import 'package:meta/meta.dart';

main(){
  personInfo(name: "张三");
  personInfo();   //此处调用会报警告
}

personInfo({String sex, int age, @required String name}) {
    print("性别:$sex 年龄:$age 姓名:$name");
} 

使用 @required 标注需要导入包 package:meta/meta.dartpackage 并不是 Dart SDK 默认提供的,需要添加依赖或直接安装到,这在后面会讲到。有兴趣的可以参考:https://pub.dev/packages/meta#-installing-tab-

根据官方文档的说明,使用 @required 注释的参数为必须提供的参数,这里也进行了测试,在 VSCode 环境下,.dart 文件运行的情况下,不提供也是可以的,但是会报警告信息,如下:

The parameter 'name' is required. .dart(missing_required_param)

并且当使用 @required 标注以后,在调用函数时,会自动带上所修饰的关键字。

位置参数

位置参数是表示某个位置的参数是可选的,需将参数放在 [] 中,如下:

main(){
  personInfo("张三");   //输出 姓名:张三 性别:null 年龄:null
  personInfo("张三", null, 20);  //输出 姓名:张三 性别:男 年龄:20
  personInfo("张三", "男", 20);  //输出 姓名:张三 性别:null 年龄:20
}

personInfo(String name, [String sex, int age]) {
    print("姓名:$name 性别:$sex 年龄:$age");
} 

sexage 为位置可选参数,name 为必须参数。当有多个位置参数时,如果想提供的参数不在位置参数的前面的位置,在 Dart 中并没有提供忽略参数的方法,可以将位置参数的排列顺序做改变或者提供 null 参数,并在函数体内做处理即可。

3. 可选参数的默认值

因为在调用函数时,默认参数不是必须提供,所以当调用者未提供指定的可选参数时,Dart 可以为可选参数设置一个默认值,当调用函数时,未提供指定参数,则使用默认值做为指定值。默认值必须是编译时常量,如果不提供默认值,默认值为 null

命名参数的默认值

main(){
  personInfo();                                         //输出 姓名:null 年龄:20
  personInfo(name: "张三");                    //输出 姓名:张三 年龄:20
  personInfo(name: "张三", age: 28); //输出 姓名:张三 年龄:28
}

personInfo({String name, int age = 20}) {
    print("姓名:$name 年龄:$age");
} 

位置参数的默认值

main(){
  personInfo();             //输出 姓名:null 年龄:20
  personInfo("张三");        //输出 姓名:张三 年龄:20
  personInfo("张三", 28);  //输出 姓名:张三 年龄:28
}

personInfo([String name, int age = 20]) {
    print("姓名:$name 年龄:$age");
} 

PS:也可使用 List , Map , Set 等作为默认值,如下:

personInfo({List a = const [], Map b = const {}, Set c = const {}}) {
   print("$a $b $c");
} 
4. 匿名函数

一般定义函数都是有函数名的,如上面所示的代码,但是并不是所有函数都有名字。Dart 中没有名字的函数称为匿名函数。匿名函数可以直接赋值给变量来通过变量进行函数调用,也可以创建自执行的函数和闭包。匿名函数也可以有参数或无参数。

var fun = (){
  print("匿名无参函数");
};

如上匿名函数如果定义在其他函数体内则相当于将匿名函数赋值给局部变量,定义在全局则相当于赋值给全局变量,如果如下匿名函数未赋值给变量,定义在其他函数体内不会出现问题,但是无法进行调用,定义在全局则会报错:

(){
   print("匿名函数");
};

但可以创建自执行匿名函数,如下:

main(){
  (){
    print("匿名函数");
   }();  //输出 匿名函数
}

带参数的自执行匿名函数:

main(){
  (int a){
    print("匿名函数 $a");
   }(10);  //输出 10
}

当在全局区定义匿名函数并赋值给一个变量时,会报警告,如下:

main(){
}

var fun = (){      //()处报警告
  print("匿名无参函数");
};

//警告内容如下
//The type of the function literal can't be inferred because the literal has a block as its body.
//Try adding an explicit type to the variable.dart(top_level_function_literal_block)

警告的意思是无法推断出变量的类型,此时可以做如下修改即可消除警告:

main(){
}

dynamic fun = (){
  print("匿名无参函数");
};

使用 dynamicObject 修饰变量。正常使用如下:

main(){
  var sum = addFunction(10, 20);
  print(sum);   //输出 30
}

dynamic addFunction = (num a, num b) => a + b; 

在函数体内定义匿名函数:

void main(){
  dynamic addFunction = (num a, num b) => a + b; 
  var sum = addFunction(10, 20);
  print(sum);
}

此时,只能在函数体内使用,使用前需要定义,注意顺序。

5. 闭包

闭包也是函数对象,无论变量还是函数等都有其使用范围,也就是作用域,当出了作用域后将无法继续使用对象或函数等。而闭包会对其使用的变量进行拷贝,即使出了其作用域依然可以被函数内部访问。

main(){
  var fun1 = addFunction(5);
  var result = fun1(10);
  print(result);

}

//闭包函数,函数内嵌套函数,并且内部函数作为外部函数的返回值
addFunction(num a) {
  return (num b) => a + b;
}

上述实现了闭包,如果在闭包函数内定义一个变量,相对于函数内部的函数来说,在外层函数定义的变量对于内部函数来说就类似全局的概念,内部函数会对外部变量做一次拷贝,所以对于闭包函数,变量是一直存在的。

二、类

Dart 是一种有类和基于 Mixin 的继承的面向对象的语言,一切皆对象。每个对象都是一个类的实例,所有类都继承自 Object 。Dart 只支持单继承,不支持多重继承,但是可以通过混合 Mixin 实现多重继承的特性。支持扩展方法,扩展方法可以在不更改类或创建子类的情况下向类中添加功能的方法。类中封装了属性和方法,属性用来存储类数据,方法用来描述类行为。

1. 定义类

Dart 中使用关键字 class 定义类,如下:

class Student {
  String name;
  int grade;
}

上述定义了一个学生类,并在其中定义了姓名和年级属性,姓名和年级属于实例属性(实例变量),可以在类内部使用,也可以将类实例化出的对象使用点语法来调用,未初始化的实例变量默认值为 null,如下:

main(){
  var student = new Student();
  student.name = "hike";
  student.grade = 1;
  print("姓名:${student.name} 班级:${student.grade}");  //输出 姓名:hike 班级:1
}

class Student {
  String name;
  int grade;
}

其中的 new 关键字是可以省略的。也可以使用级联运算符,如下:

main(){
  var student = Student()
  ..name = "hike"
  ..grade = 1;
  print("姓名:${student.name} 班级:${student.grade}"); //输出 姓名:hike 班级:1
}

class Student {
  String name;
  int grade;
}
2. 实例方法

实例方法与上述实例属性一样,在类内定义好的实例方法需要实例化的类来调用,方法就是函数,如下:

main(){
  var student = new Student();
  student.name = "hike";
  student.grade = 1;
  student.study();

  Student student1 = Student();
  student1.running("mach", 5);
  print("学生的名字为${student1.name} ${student1.grade}年级");
}

class Student {
  String name;
  int grade;

  void study() {
    print("${grade}年级的学生${name}在学习");
  }

  void running(String name, int grade) {
    this.name = name;
    this.grade = grade;
    print("${this.grade}年级的学生${this.name}在跑步");
  }
}

上面在学生类中定义了两个实例方法 studyrunningstudy 为无参数方法,running 为有参方法。在类内定义的方法可以直接使用类内定义的实例属性,但是如果方法的参数名称与类内的属性名称相同,在做赋值操作时,应在属性前通过 this 关键字使用点语法调用,this 代表当前类的实例对象,如方法running

3. 构造方法

类在进行实例化时候,会调用类的构造方法,这是大多数编程语言的特性,Dart 中也不列外。Dart 的类在实例化的时候也会调用构造方法,上述的代码都没有进行构造函数的定义,构造函数是一种特殊的函数,如果开发者没有提供构造函数(如上代码,都没有提供构造函数),编译器会提供默认的构造函数,也就是说无论开发者是否书写构造函数,在类的创建时都会调用构造函数,只是如果没有提供构造函数,则会调用默认的构造函数,默认构造函数也属于常规构造函数。构造函数参数也支持可选参数方式。

常规构造函数

默认构造,如下:

main(){
  var student = new Student();   //输出 这是默认的构造函数
}

class Student {
  String name;
  int grade;

  Student(){
    print("这是默认的构造函数");
  }

  void study() {
    print("${grade}年级的学生${name}在学习");
  }

  void running(String name, int grade) {
    this.name = name;
    this.grade = grade;
    print("${this.grade}年级的学生${this.name}在跑步");
  }

上述代码,我们只创建了 Student 类的对象,并没有调用任何方法,但是依然会打印函数 Student 中的内容,Student 就是默认的构造函数。构造函数的函数名必须和类名相同,所谓默认的构造函数就是与类名相同的无参函数,创建类的对象时会默认调用此函数。我们也可以改写默认的构造函数,给默认的构造函数添加参数,如下:

main(){
  var student = new Student("hike", 3);
  student.study();
  student.running();
}

class Student {
  String name;
  int grade;

  Student(String newName, int newGrade){
    name = newName;
    grade = newGrade;
  }

  void study() {
    print("${grade}年级的学生${name}在学习");
  }

  void running() {
    print("${this.grade}年级的学生${this.name}在跑步");
  }

上述代码,将默认的构造函数添加了两个参数,分别为:newNamenewGrade ,其实参数名和属性名可以完全相同,就如前面所写的 running 一样,只不过需要添加 this 关键字,指明所属对象。修改如下:

 Student(String name, int grade){
    this.name = name;
    this.grade = grade;
  }

在 Dart 中有一种更为便捷的实现方法,可以完全省略构造函数体内的赋值过程,修改如下:

Student(this.name, this.grade);

如此实现,同样是实现构造函数的赋值过程,提供构造函数的好处,可以在创建对象的时候就提供参数,省去了创建对象的赋值过程,当然也可以在构造函数内或非构造函数内对属性做默认赋值,但是如果不提供构造函数,赋的默认值就无法通过便捷的方式修改,不提供默认值则默认值为 null 。值得注意的是,一旦对默认的构造函数(与类同名无参的函数)做了修改,原本提供的默认构造函数(与类同名无参的函数)便无法继续使用,如下:

main(){
  // var student =  new Student();   //错误
  var student = new Student("hike", 3);
  student.study();
  student.running();
}

class Student {
  String name;
  int grade;

  Student(this.name, this.grade);

  void study() {
    print("${grade}年级的学生${name}在学习");
  }

  void running() {
    print("${this.grade}年级的学生${this.name}在跑步");
  }
}

Dart 中不支持函数重载,构造函数也是如此。所以如果想创建多个提供不同参数的构造函数便无法实现。但是 Dart 中提供了其他的方式实现同样的功能,就是命名构造函数。

命名构造函数

命名构造函数就是通过为构造函数起一个别名的方式显示构造函数的功能,实现方法如下:

main(){

  var student1 = new Student("hike", 3);
  student1.study();
  student1.running();

  Map studentMap = {
    "name" : "Mary",
    "grade" : 2
  };
  var student2 = new Student.fromMap(studentMap);
  student2.study();
  student2.running();

  var student3 = new Student.otherStudent(student1);
  student3.study();
  student3.running();
}

class Student {
  String name;
  int grade;

  Student(this.name, this.grade);

  Student.fromMap(Map studentJson) {
    name = studentJson["name"];
    grade = studentJson["grade"];
  }

  Student.otherStudent(Student stu){
    name = stu.name;
    grade = stu.grade;
  }

  void study() {
    print("${grade}年级的学生${name}在学习");
  }

  void running() {
    print("${this.grade}年级的学生${this.name}在跑步");
  }
}

通过以上可以看出,命名构造函数格式为 构造函数名.别名(参数列表) 。别名也称作标识符,只要不同就可以了,很简单,其他的语法规则都与以前介绍的相同。

构造函数初始化列表

构造函数的初始化列表用来在构造函数主体执行之前初始化实例变量,初始化列表可以进行赋值操作,也可以使用表达式,对于计算某个值的最终结果很方便。初始化列表使用 : 分隔。

main(){
  var student1 = new Student("hike", 3, 89, 99.4);
  student1.study();
  student1.running();
}

class Student {
  String name;
  int grade;
  double score;
  double English;
  double math;

  Student(this.name, this.grade, english, math): score = english + math {
    print("${grade}年级的${name}的英语和数学的总分为:$score");
  }

  void study() {
    print("${grade}年级的学生${name}在学习");
  }

  void running() {
    print("${this.grade}年级的学生${this.name}在跑步");
  }
}

代码中新增了总分数 score 、英语分数 english 和数学分数 math ,在构造函数中使用初始化列表在构造函数主体执行之前计算出了总分数。

初始化列表右侧(: 右侧)不能使用 this 关键字。在开发期间,可以在初始列表中加入 assert 来验证输入的正确性。

重定向构造函数

通常,在创建对象的时候会调用一个构造函数(无论是默认构造函数还是自定义的构造函数),在这过程中,调用哪个构造函数就执行哪个构造函数的函数体(函数内容)。在 Dart 中,可以定义重定向构造函数,定义重定向构造函数的目的在于,当调用重定向构造函数时,会调用重定向函数指定的构造函数方法,以实现某种目的。在定义Dart 中的重定向函数时,重定向函数主题必须为空(不能有函数体),目标函数放在 : 后面,使用如下:

main(){
  var student1 = new Student("hike", 3);
  student1.study();
  student1.running();

  var student2 = Student.formGradeOne("Lucy");
  student2.study();
}

class Student {
  String name;
  int grade;

  Student(this.name, this.grade);

  //重定向构造函数
  Student.formGradeOne(String name):this(name, 1);

  void study() {
    print("${grade}年级的学生${name}在学习");
  }

  void running() {
    print("${this.grade}年级的学生${this.name}在跑步");
  }
}

上述代码定义了重定向构造函数 Student.formGradeOne ,目标构造函数为 Student ,这个重定向构造函数实现了一个定义班级为1的学生,只需要提供学生的姓名就可以直接创建班级为1的学生,这样定义提供了一种便捷的创建方式,实际调用的构造函数为目标构造函数(Student)。

PS:重定向函数的参数名称不用与目标函数的参数名称相同,但类型必须对应。

常量构造函数

如果希望通过同一个类实例化出来的对象为同一对象(对于无参构造函数创建的对象为同一对象,对于有参构造函数,当传入相同的数据时创造出的为同一对象),就可以使用常量构造函数,Dart 中,使用 const 关键字修饰的构造函数为常量构造函数,在使用常量构造函数的类中,实例属性必须使用 final 修饰,且常量构造函数不能有函数体。非常量构造函数创建对象如下:

main(){
  var student1 = new Student("hike", 3);
  var student2 = new Student("hike", 3);
  bool isSame = identical(student1, student2);
  print(isSame);   //输出 false
}

class Student {
  String name;
  int grade;

  Student(this.name, this.grade);
}

上述为非常量构造函数创建的对象,identical 方法用来检测两个对象是否指向同一对象。虽然参数都相同,且都是同一类的实例化对象,但是他们并不是同一个对象。

使用常量构造函数方法如下:

main(){
  const student1 = Student("hike", 3);
  const student2 = Student("hike", 3);
  print(identical(student1, student2));   //输出 true
}

class Student {
  final String name;
  final int grade;

  const Student(this.name, this.grade);
}

通过常量构造函数实例化的对象,必须使用 const 做修饰,不能使用 new 关键字。

工厂构造函数

Dart 中支持工厂构造函数,工厂构造函数与普通构造函数的区别在于,工厂构造函数使用 factory 关键字修饰,并且有自己的返回值。以上使用的构造函数可以归纳为普通构造函数(默认构造与命名构造),在普通构造函数内不能有明确返回值,即便返回当前对象类型,其操作是由 Dart 来完成的。而工厂构造需要开发者手动指定返回值类型,可以返回当前对象或其他类型。如不添加任何返回值,则会报警告(VSCode开发环境)。使用工厂构造创建单利的方式如下:

main(){
  var bmwCar1 = new BmwCar(2);
  var bmwCar2 = new BmwCar(1);
  bool isSame = identical(bmwCar1, bmwCar2);
  print(isSame);                //输出 true
  print("${bmwCar1.carId}  ${bmwCar2.carId}");   //输出 2  2
}

class BmwCar {
  int carId;
  static BmwCar student;

  factory BmwCar(int carId) {
   if(student == null) {
     student = new BmwCar._fromCarId(carId);
   }
   return student;
 }

  BmwCar._fromCarId(this.carId);
}

上面的代码,无论传入的为何种参数,创建出的新对象都为同一对象。Dart 中下划线(_)开头的变量为私有变量,私有变量只能在定义它们的库中使用(一个 .dart 文件就是一个库文件),关于此会在后续的关于库的文章中详细介绍。此外,工厂方法也可以从一个缓存返回实例,官方的例子就是如此,可以参考。

4. 类属性(静态属性) 与 类方法(静态方法)

以上定义的属性和方法都是实例属性和实例方法,即必须通过类实例化以后的对象来进行调用。Dart 中也提供了类属性和类方法,即不用实例化类,直接通过类名就可以直接调用的属性和方法。Dart 中使用 static 来修饰类属性和类方法,如下:

main(){
  Student.name = "hike";
  Student.grade = 2;
  Student.study();
}

class Student {
  static String name;
  static int grade;
  
  static study(){
    print("${grade}班级的${name}在学习");
  }
}

类属性和类方法直接使用类来调用,不能使用实例化的对象来操作。因为类方法无法通过实例化的对象调用,所以
不能在类方法中使用 this 关键字。类属性(静态变量)在使用前不会被初始化。在类方法中无法访问非静态成员,非静态方法中可以访问静态成员。

您可以使用静态方法作为编译时常量。例如,您可以将静态方法作为参数传递给常量构造函数。

5. setters 与 getters

所有的实例变量都会生成一个隐式的 getter 方法,非最终实例变量也会生成隐式的 setter 方法。getter 方法用来获取属性,setter 方法用来设置属性。当我们使用实例变量通过点运算符调用属性时,调用的就是getter或setter方法。如果一个属性值并非最终变量,就可以通过setter方法来进行定义,在其他语言中,有些也叫计算属性。

main(){;
  var student = Student();
  student.calculate = 60;
  print(student.calculate);
}

class Student {
  int baseCredit;

  //获取总学分
  int get calculate {
    return baseCredit + 20;
  }

  //输入平时得分
  set calculate(int value) {
    baseCredit = value + 10;
  }
}

上述例子,设置了一个计算属性 (calculate) ,set 方法用来提供学生平时的表现得分,并在表现分基础上加10分,get 方法用来计算学生的最终得分,为在基础分基础上加20分为学生的最终得分。下面是一个更为直观的例子:

main(){
  var network = Network();
  network.strURL = "http://www.baidu.com";
  print(network.strURL);
}

class Network {
  String _strURL;

  String get strURL {
    return _strURL;
  }

  void set strURL(String urlString) {
    _strURL = urlString;
  }
}
6. 类的继承

继承是类的重要特性,子类可以通过继承的方式对父类进行扩展和使用父类中定义的属性和方法,也可以对父类中的方法进行重写以实现自己需要的功能。Dart 中使用 extends 关键字实现继承:

main(){;
  Student student = Student();
  student.name = "hike";
  student.age = 20;
  student.eat();   //输出  hike 在吃面包
  student.study(); //输出  hike 在学习
}

class Student extends Person {
  void study(){
    print("$name 在学习");
  }
}

//基类(父类)
class Person {
  String name;
  int age;

  void eat(){
    print("$name 在吃面包");
  }
}

这里定义了一个基类 Person ,并定义了姓名和年龄属性,还有一个吃的方法,学生也是人类,Person 类定义的属性和方法学生类也同样应该有,所以通过直接继承 Person 类的方式,就可以直接使用 Person 类中的属性和方法,Student 类也有自己的学习方法。有一点值得注意,构造方法是无法继承的,如果父类中存在非默认构造方法,子类在继承时必须使用调用父类构造方法,否则会报错。如下:

main(){;
  Student student = Student("hike", 20);
  student.eat();
  student.study();
}

class Student extends Person {
  //必须实现
  Student(String name, int age) : super(name, age);

  void study(){
    print("$name 在学习");
  }
}

//基类(父类)
class Person {
  String name;
  int age;

  //非默认构造函数
  Person(this.name, this.age);
  void eat(){
    print("$name 在吃面包");
  }
}

Student(String name, int age) : super(name, age); 调用父类构造方法的格式与重定向构造函数格式相同,只是将 this 关键字换成了 supersuper 除了用在此处之外,主要用于在子类中调用父类的方法,修改 study 方法如下:

void study(){
  super.eat();
  print("$name 在学习");
}

子类除了能调用父类方法外,还可以对父类方法进行重写,如下,重写 eat 方法:

main(){;
  Student student = Student("hike", 20);
  student.eat();    //输出 hike 在吃苹果
  student.study();  //输出 hike 在学习
}

class Student extends Person {
  //必须实现
  Student(String name, int age) : super(name, age);
  
  //重写父类方法
  @override
  void eat() {
    print("$name 在吃苹果");
  }

  void study(){
    print("$name 在学习");
  }
}

//基类(父类)
class Person {
  String name;
  int age;

  //非默认构造函数
  Person(this.name, this.age);
  void eat(){
    print("$name 在吃面包");
  }
}

通过 @override 标注方法为重写方法,此标注可以省略。此刻,输出的为在吃苹果,而并非父类的吃面包,证明本类已经覆盖了父类的方法实现。如果想在覆盖父类方法的同时,保留父类方法的实现,可以在本类的覆盖实现中通过 super 调用父类的实现,Studenteat 方法修改如下:

@override
void eat() {
  super.eat();
  print("$name 在吃苹果");
}

这样,父类与子类的实现会同时执行。

7. 抽象类 与 抽象方法

抽象类是无法被实例化的类(无法直接通过抽象类创建对象)。抽象类常用于定义通用接口,通用接口用来提供给符合条件的类使用。所谓接口就是抽象类中只定义不实现的方法,这些方法被称为抽象方法。在非抽象类中是不能只定义不实现方法主体功能的。Dart 中使用 abstract 关键字定义抽象类,如下:

abstract class asStudent {
  void study();
  void test();
}

上述代码定义了一个抽象学生类,类中定了 study()test() 两个方法,这两个方法就是抽象方法,抽象方法只能在抽象类中定义。类 asStudent 为抽象类。这个类不能直接实例化,只能通过继承或实现接口的方式使用。

接口实现方式通过关键字 implements 实现,如下:

main(){;
  Student student = Student();
  student.study();
  student.test();
}

class Student implements asStudent {

  @override
  void study(){
    print("学习");
  }

  @override
  void test() {
    print("考试");
  }
}

abstract class asStudent {
  void study();
  void test();
}

也可以同时实现多个接口抽象类,使用 , 分割即可。当一个类实现接口类时,类本身需要重写接口类中的所有声明的方法,否则会报错,实现多个接口,则需要将多个接口中声明的方法全部实现。

通过继承的方式实现,使用关键字 extends ,如下:

main(){;
  Student student = Student();
  student.study();
  student.test();
}

class Student extends asStudent {

  @override
  void study(){
    print("学习");
  }

  @override
  void test() {
    print("考试");
  }
}

abstract class asStudent {
  void study();
  void test();
}

此方式也同样需要重写父类中所有的方法。

8. Mixin

Dart 中不支持多重继承,即一个子类只能继承一个父类。如果想要实现多继承的特性,就需要使用 Mixin 的特性,使用关键字 with ,如下:

main(){;
  Student student = Student();
  student.name = "hike";
  student.eat();        //输出 hike 在吃面包
  student.drinking();   //输出 喝水
  student.study();          //输出 hike 在学习
}

class Student extends Person with Animal {

  void study(){
    print("$name 在学习");
  }
}

//基类(父类)
class Person {
  String name;
  int age;

  void eat(){
    print("$name 在吃面包");
  }
}

//动物类
class Animal {
  void drinking() {
    print("喝水");
  }
}

增添了动物类(Animal),并添加了一个喝水的方法,在 Student 继承的基础上使用 with 关键字后添加需要混合的类名,可以添加多个,使用逗号(,)进行分割,这样就可以使用新增添的类中的方法和属性。

也可以使用如下写法:

class resStudent = Student with Animal;

main(){;
  resStudent student = resStudent();
  student.name = "hike";
  student.eat();
  student.drinking();
  student.study();
}

class Student extends Person {

  void study(){
    print("$name 在学习");
  }
}

//基类(父类)
class Person {
  String name;
  int age;

  void eat(){
    print("$name 在吃面包");
  }
}

class Animal {
  void drinking() {
    print("喝水");
  }
}

需要注意的是,如果一个可做为 Mixin 类(使用在 with 后),不能有构造方法,即便重写默认的构造方法也不行。但是在不提供构造方法的前提下,可以创建该类的对象。也就是说虽然 Mixin 类虽然不能有构造函数,但是可以被实例化。如果不想作为 Mixin 类被实例化,可以使用 mixin 关键字替换 class 关键字进行定义,如下:

//动物类
mixin Animal {
  void drinking() {
    print("喝水");
  }
}

此时在创建 Animal 类的实例则会报错,并且使用 mixin 定义的类也不能被继承(使用 extends 继承)。mixin 定义的类本身可以继承其他类,此时使用 on 关键字继承,如下:

class Description {
  void des(){
    print("描述");
  }
}

//动物类
mixin Animal on Description {
  void drinking() {
    print("喝水");
  }
}
9. noSuchMethod

上面说过,在非抽象类中只声明不实现方法是不被允许的,会报错。当一个抽象类被继承或被实现接口后,也需要实现抽象类中的所有方法,否则也会报错。但是有时候在抽象类中定义的方法并不需要全部实现,此时,可以选择重写 noSuchMethod 方法。重写此方法后,在编译阶段就不会报错。而在运行阶段,如果调用了未实现的方法则会调用此方法,可以在此方法中做一些处理。

main(){;
  Student student = Student();
  student.study();     //输出 学习
  student.test();        //此行调用将执行 noSuchMethod 方法
}

class Student extends asStudent {
  @override
  void study(){
    print("学习");
  }

  @override
  noSuchMethod(Invocation invocation) {
    print("调用了未实现方法:${invocation.memberName}");  //输出 调用了未实现方法:Symbol("test")
    // return super.noSuchMethod(invocation);  //注释掉此行代码,否则依然会抛出异常
  }
}

abstract class asStudent {
  void study();
  void test();
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容