zoukankan      html  css  js  c++  java
  • flutter--Dart基础语法(三)类和对象、泛型、库

    一、前言

    Flutter 是 Google 开源的 UI 工具包,帮助开发者通过一套代码库高效构建多平台精美应用,Flutter 开源、免费,拥有宽松的开源协议,支持移动、Web、桌面和嵌入式平台。

    Flutter是使用Dart语言开发的跨平台移动UI框架,通过自建绘制引擎,能高性能、高保真地进行Android和IOS开发。Flutter采用Dart语言进行开发,而并非Java,Javascript这类热门语言,这是Flutter团队对当前热门的10多种语言慎重评估后的选择。因为Dart囊括了多数编程语言的优点,它更符合Flutter构建界面的方式。

    本文主要就是简单梳理一下Dart语言的一些基础知识和语法。关于编程语言的基本语法无外乎那么些内容,注释、变量、数据类型、运算符、流程控制、函数、类、异常、文件、异步、常用库等内容,相信大部分读者都是有一定编程基础的,所以本文就简单地进行一个梳理,不做详细的讲解。大家也可以参考 Dart编程语言中文网

    上一篇文章主要是写了Dart语言的流程控制、函数和异常处理,本文将接着上一篇文章继续往后写,本文将主要介绍Dart语言的类和对象、泛型以及库的使用。

    二、类和对象

    Dart 是一种基于类和 mixin 继承机制的面向对象的语言。 每个对象都是一个类的实例,所有的类都继承于 Object。面向对象中非常重要的概念就是类,类产生了对象。接下来我们就具体来学习类和对象,但是Dart对类进行了很多其他语言没有的特性,所以,这里我会花比较长的篇幅来讲解。

    2.1  类的定义

    在Dart中,定义类用class关键字。类通常有两部分组成:成员(member)和方法(method)。定义类的伪代码如下:

    class 类名 {
      类型 成员名;
      返回值类型 方法名(参数列表) {
        方法体
      }
    }

    编写一个简单的Person类:

    • 这里有一个注意点: 我们在方法中使用属性(成员/实例变量)时,并没有加this
    • Dart的开发风格中,在方法中通常使用属性时,会省略this,但是有命名冲突时,this不能省略
    class Person {
      String name;
    
      eat() {
        print('$name在吃东西');
      }
    }

    我们来使用这个类,创建对应的对象:

    • 注意:从Dart2开始,new关键字可以省略。
    main(List<String> args) {
      // 1.创建类的对象
      var p = new Person(); // 直接使用Person()也可以创建
    
      // 2.给对象的属性赋值
      p.name = 'why';
    
      // 3.调用对象的方法
      p.eat();
    } 

    2.2 构造方法

    Dart语言中构造方法分为普通构造方法、命名构造方法、重定向构造方法、常量构造方法、工厂构造方法以及初始化列表等多种。下面我们就一一给大家简单解释一下其中的区别。

    2.2.1 普通构造方法

    我们知道, 当通过类创建一个对象时,会调用这个类的构造方法。

    • 当类中没有明确指定构造方法时,将默认拥有一个无参的构造方法
    • 前面的Person中我们就是在调用这个构造方法。

    我们也可以根据自己的需求,定义自己的构造方法:

    • 注意一当有了自己的构造方法时,默认的构造方法将会失效,不能使用
      • 当然,你可能希望明确的写一个默认的构造方法,但是会和我们自定义的构造方法冲突;
      • 这是因为Dart本身不支持函数的重载(名称相同, 参数不同的方式)。
    • 注意二:这里我还实现了toString方法
    class Person {
      String name;
      int age;
    
      Person(String name, int age) {
        this.name = name;
        this.age = age;
      }
    
      @override
      String toString() {
        return 'name=$name age=$age';
      }
    }

    另外,在实现构造方法时,通常做的事情就是通过 参数属性 赋值。为了简化这一过程, Dart提供了一种更加简洁的语法糖形式。上面的构造方法可以优化成下面的写法:

    Person(String name, int age) {
        this.name = name;
        this.age = age;
      }
      // 等同于
      Person(this.name, this.age);

    2.2.2 命名构造方法

    但是在开发中, 我们确实希望实现更多的构造方法,怎么办呢?因为不支持方法(函数)的重载,所以我们没办法创建相同名称的构造方法。因此,我们需要使用命名构造方法:

    class Person {
      String name;
      int age;
    
      Person() {
        name = '';
        age = 0;
      }
        // 命名构造方法
      Person.withArgments(String name, int age) {
        this.name = name;
        this.age = age;
      }
    
      @override
      String toString() {
        return 'name=$name age=$age';
      }
    }
    
    // 创建对象
    var p1 = new Person();
    print(p1);
    var p2 = new Person.withArgments('why', 18);
    print(p2);

    在之后的开发中, 我们也可以利用命名构造方法,提供更加便捷的创建对象方式。比如开发中,我们需要经常将一个Map转成对象,可以提供如下的构造方法

      // 新的构造方法
      Person.fromMap(Map<String, Object> map) {
        this.name = map['name'];
        this.age = map['age'];
      }
    
      // 通过上面的构造方法创建对象
      var p3 = new Person.fromMap({'name': 'kobe', 'age': 30});
      print(p3);

    2.2.3 初始化列表

    我们来重新定义一个类Point, 传入x/y,可以得到它们的距离distance:

    class Point {
      final num x;
      final num y;
      final num distance;
    
      // 错误写法
      // Point(this.x, this.y) {
      //   distance = sqrt(x * x + y * y);
      // }
    
      // 正确的写法
      Point(this.x, this.y) : distance = sqrt(x * x + y * y);
    }

    上面这种初始化变量的方法, 我们称之为初始化列表(Initializer list)

    2.2.4 重定向构造方法

    在某些情况下, 我们希望在一个构造方法中去调用另外一个构造方法, 这个时候可以使用重定向构造方法

    • 在一个构造函数中,去调用另外一个构造函数(注意:是在冒号后面使用this调用)
    class Person {
      String name;
      int age;
    
      Person(this.name, this.age);
    
      Person.fromName(String name) : this(name, 0);
    }

    2.2.5 常量构造方法

    在某些情况下,传入相同值时,我们希望返回同一个对象,这个时候,可以使用常量构造方法.

    默认情况下,创建对象时,即使传入相同的参数,创建出来的也不是同一个对象,看下面代码:

    • 这里我们使用identical(对象1, 对象2)函数来判断两个对象是否是同一个对象:
    main(List<String> args) {
      var p1 = Person('why');
      var p2 = Person('why');
      print(identical(p1, p2)); // false
    }
    
    class Person {
      String name;
    
      Person(this.name);
    }

    但是, 如果将构造方法前加const进行修饰,那么可以保证同一个参数,创建出来的对象是相同的

    • 这样的构造方法就称之为常量构造方法
    main(List<String> args) {
      var p1 = const Person('zhangsan');
      var p2 = const Person('zhangsan');
      const p3 = Person('zhangsan');
      var p4 = Person('zhangsan');
      var p5 = Person('lisi');
    
      print(identical(p1,p2)); //true
      print(identical(p1,p3)); //true
      print(identical(p1,p4)); //false
      print(identical(p1,p5)); //false
    
    }
    
    class Person {
      final String name;
    
      const Person(this.name);
    } 

    常量构造方法有一些注意点:

    • 注意一:拥有常量构造方法的类中,所有的成员变量必须是final修饰.
    • 注意二: 为了可以通过常量构造方法,创建出相同的对象,不再使用 new关键字,而是使用const关键字
      • 如果是将结果赋值给const修饰的标识符时,const可以省略.

    2.2.6 工厂构造方法

    Dart提供了factory关键字, 用于通过工厂去获取对象

    main(List<String> args) {
      var p1 = Person('why');
      var p2 = Person('why');
      print(identical(p1, p2)); // true
    }
    
    class Person {
      String name;
    
      static final Map<String, Person> _cache = <String, Person>{};
    
      factory Person(String name) {
        if (_cache.containsKey(name)) {
          return _cache[name];
        } else {
          final p = Person._internal(name);
          _cache[name] = p;
          return p;
        }
      }
    
      Person._internal(this.name);
    }

    2.3 setter和getter

    默认情况下,Dart中类定义的属性是可以直接被外界访问的。但是某些情况下,我们希望监控这个类的属性被访问的过程,这个时候就可以使用setter和getter

    main(List<String> args) {
      final d = Dog("黄色");
      d.setColor = "黑色";
      print(d.getColor);
    }
    
    class Dog {
      String color;
    
      String get getColor {
        return color;
      }
      set setColor(String color) {
        this.color = color;
      }
    
      Dog(this.color);
    }

    2.4 类的继承

    面向对象的其中一大特性就是继承,继承不仅仅可以减少我们的代码量,也是多态的使用前提。Dart中的继承使用extends关键字,子类中使用super来访问父类。父类中的所有成员变量和方法都会被继承,但是构造方法除外

    main(List<String> args) {
      var p = new Person();
      p.age = 18;
      p.run();
      print(p.age);
    }
    
    class Animal {
      int age;
    
      run() {
        print('在奔跑ing');
      }
    }
    
    class Person extends Animal {
    
    }

    子类可以拥有自己的成员变量, 并且可以对父类的方法进行重写

    class Person extends Animal {
      String name;
    
      @override
      run() {
        print('$name在奔跑ing');
      }
    }

    子类中可以调用父类的构造方法,对某些属性进行初始化:

    • 子类的构造方法在执行前,将隐含调用父类的无参默认构造方法(没有参数且与类同名的构造方法)。
    • 如果父类没有无参默认构造方法,则子类的构造方法必须在初始化列表中通过super显式调用父类的某个构造方法
    class Animal {
      int age;
    
      Animal(this.age);
    
      run() {
        print('在奔跑ing');
      }
    }
    
    class Person extends Animal {
      String name;
    
      Person(String name, int age) : name=name, super(age);
    
      @override
      run() {
        print('$name在奔跑ing');
      }
    
      @override
      String toString() {
        return 'name=$name, age=$age';
      }
    }

    2.5 抽象类

    我们知道,继承是多态使用的前提。所以在定义很多通用的 调用接口 时, 我们通常会让调用者传入父类,通过多态来实现更加灵活的调用方式。但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,我们可以定义为抽象方法

    什么是 抽象方法? 在Dart中没有具体实现的方法(没有方法体),就是抽象方法。

    • 抽象方法,必须存在于抽象类中。
    • 抽象类是使用abstract声明的类。

    下面的代码中, Shape类就是一个抽象类, 其中包含一个抽象方法.

    abstract class Shape {
      getArea();
    }
    
    class Circle extends Shape {
      double r;
    
      Circle(this.r);
    
      @override
      getArea() {
        return r * r * 3.14;
      }
    }
    
    class Reactangle extends Shape {
      double w;
      double h;
    
      Reactangle(this.w, this.h);
    
      @override
      getArea() {
        return w * h;
      }
    }

    注意事项:

    • 注意一:抽象类不能实例化.
    • 注意二:抽象类中的抽象方法必须被子类实现, 抽象类中的已经被实现方法, 可以不被子类重写.

    2.6 隐式接口

    Dart中的接口比较特殊, 没有一个专门的关键字来声明接口。默认情况下,定义的每个类都相当于默认也声明了一个接口,可以由其他的类来实现(因为Dart不支持多继承)。在开发中,我们通常将用于给别人实现的类声明为抽象类:

    abstract class Runner {
      run();
    }
    
    abstract class Flyer {
      fly();
    }
    
    class SuperMan implements Runner, Flyer {
      @override
      run() {
        print('超人在奔跑');
      }
    
      @override
      fly() {
        print('超人在飞');
      }
    }

    2.7  Mixin混入

    在通过implements实现某个类时,类中所有的方法都必须被重新实现 (无论这个类原来是否已经实现过该方法)

    但是某些情况下,一个类可能希望直接复用之前类的原有实现方案,怎么做呢?

    • 使用继承吗?但是Dart只支持单继承,那么意味着你只能复用一个类的实现。

    Dart提供了另外一种方案: Mixin混入的方式

    • 除了可以通过class定义类之外,也可以通过mixin关键字来定义一个类。
    • 只是通过mixin定义的类用于被其他类混入使用,通过with关键字来进行混入。
    main(List<String> args) {
      var superMan = SuperMain();
      superMan.run();
      superMan.fly();
    }
    
    mixin Runner {
      run() {
        print('在奔跑');
      }
    }
    
    mixin Flyer {
      fly() {
        print('在飞翔');
      }
    }
    
    // implements的方式要求必须对其中的方法进行重新实现
    // class SuperMan implements Runner, Flyer {}
    
    class SuperMain with Runner, Flyer {
    
    } 

    2.8 类成员和方法

    前面我们在类中定义的成员和方法都属于对象级别的, 在开发中, 我们有时候也需要定义类级别的成员和方法。在Dart中我们使用static关键字来定义,需要注意的是,类方法和类成员只能通过类名进行访问,不能通过对象名进行访问

    main(List<String> args) {
      var stu = Student();
      stu.name = 'why';
      stu.sno = 110;
      stu.study();
    
      Student.time = '早上8点';
      // stu.time = '早上9点'; 错误做法, 实例对象不能访问类成员
      Student.attendClass();
      // stu.attendClass(); 错误做法, 实现对象不能访问类方法
    }
    
    class Student {
      String name;
      int sno;
    
      static String time;
    
      study() {
        print('$name在学习');
      }
    
      static attendClass() {
        print('去上课');
      }
    }

    三、 枚举类型

    枚举在开发中也非常常见, 枚举也是一种特殊的类, 通常用于表示固定数量的常量值

    3.1 枚举的定义

    枚举使用enum关键字来进行定义:

    main(List<String> args) {
      print(Colors.red);
    }
    
    enum Colors {
      red,
      green,
      blue
    }

    3.2 枚举的属性

    枚举类型中有两个比较常见的属性:

    • index: 用于表示每个枚举常量的索引, 从0开始.
    • values: 包含每个枚举值的List.
    main(List<String> args) {
      print(Colors.red.index);
      print(Colors.green.index);
      print(Colors.blue.index);
    
      print(Colors.values);
    }
    
    enum Colors {
      red,
      green,
      blue
    }

    枚举类型的注意事项:

    • 注意一: 您不能子类化、混合或实现枚举。
    • 注意二: 不能显式实例化一个枚举

    四、 泛型

    泛型的定义主要有以下两种:
    1. 在程序编码中一些包含类型参数的类型,也就是说泛型的参数只可以代表类,不能代表个别对象。(这是当今较常见的定义)
    2. 在程序编码中一些包含参数的类。其参数可以代表类或对象等等。(人们大多把这称作模板)不论使用哪个定义,泛型的参数在真正使用泛型时都必须作出指明。
    一些强类型编程语言支持泛型,其主要目的是加强类型安全及减少类转换的次数,但一些支持泛型的编程语言只能达到部分目的。在Dart的 API 文档中你会发现基础数组类型 List 的实际类型是 List<E> 。 <…> 符号将 List 标记为 泛型 (或 参数化) 类型。 这种类型具有形式化的参数。 通常情况下,使用一个字母来代表类型参数, 例如 E, T, S, K, 和 V 等。

    4.1 为什么使用泛型?

    在类型安全上通常需要泛型支持, 它的好处不仅仅是保证代码的正常运行:

    • 正确指定泛型类型可以提高代码质量。
    • 使用泛型可以减少重复的代码。

    如果想让 List 仅仅支持字符串类型, 可以将其声明为 List<String> (读作“字符串类型的 list ”)。 那么,当一个非字符串被赋值给了这个 list 时,开发工具就能够检测到这样的做法可能存在错误。 例如:

    var names = List<String>();
    names.addAll(['Seth', 'Kathy', 'Lars']);
    names.add(42); // 错误

    另外一个使用泛型的原因是减少重复的代码。 泛型可以在多种类型之间定义同一个实现, 同时还可以继续使用检查模式和静态分析工具提供的代码分析功能。

    // 假设你创建了一个用于缓存对象的接口:
    abstract class ObjectCache {
      Object getByKey(String key);
      void setByKey(String key, Object value);
    }
    
    // 后来发现需要一个相同功能的字符串类型接口,因此又创建了另一个接口:
    abstract class StringCache {
      String getByKey(String key);
      void setByKey(String key, String value);
    }
    
    // 后来,又发现需要一个相同功能的数字类型接口 … 这里你应该明白了。
    
    // 泛型可以省去创建所有这些接口的麻烦。 通过创建一个带有泛型参数的接口,来代替上述接口:
    abstract class Cache<T> {
      T getByKey(String key);
      void setByKey(String key, T value);
    }

    在上面的代码中,T 是一个备用类型。 这是一个类型占位符,在开发者调用该接口的时候会指定具体类型。

    4.2 List、Set、Map中泛型的使用

    4.2.1 字面量中的泛型

    List , Set 和 Map 字面量也是可以参数化的。 参数化字面量和之前的字面量定义类似, 对于 List 或 Set 只需要在声明语句前加 <type> 前缀, 对于 Map 只需要在声明语句前加 <keyTypevalueType> 前缀, 下面是参数化字面量的示例:

    var names = <String>['Seth', 'Kathy', 'Lars'];
    var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
    var pages = <String, String>{
      'index.html': 'Homepage',
      'robots.txt': 'Hints for web robots',
      'humans.txt': 'We are people, not machines'
    };

    4.2.2 使用泛型类型的构造函数

    在调用构造函数的时,在类名字后面使用尖括号(<...>)来指定泛型类型。 例如:

    // 创建一个元素为字符串的Set集合
    var nameSet = Set<String>.from(names);
    
    // 下面代码创建了一个 key 为 integer, value 为 View 的 map 对象:
    var views = Map<int, View>();

    4.2.3 运行时中的泛型集合

    Dart 中泛型类型是 固化的,也就是说它们在运行时是携带着类型信息的。 例如, 在运行时检测集合的类型:

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

    提示: 相反,Java中的泛型会被 擦除 ,也就是说在运行时泛型类型参数的信息是不存在的。 在Java中,可以测试对象是否为 List 类型, 但无法测试它是否为 List<String> 。

    4.3 创建类时限制泛型类型

    使用泛型类型的时候, 可以使用 extends 实现参数类型的限制。

    class Foo<T extends SomeBaseClass> {
      // Implementation goes here...
      String toString() => "Instance of 'Foo<$T>'";
    }
    
    class Extender extends SomeBaseClass {...}
    
    // 可以使用 SomeBaseClass 或其任意子类作为通用参数:
    var someBaseClassFoo = Foo<SomeBaseClass>();
    var extenderFoo = Foo<Extender>();
    
    // 也可以不指定泛型参数:
    var foo = Foo();
    print(foo); // Instance of 'Foo<SomeBaseClass>'
    
    // 指定任何非 SomeBaseClass 类型会导致错误:
    var foo = Foo<Object>();

    4.4 使用泛型函数

    最初,Dart 的泛型只能用于类。 新语法_泛型方法_,允许在方法和函数上使用类型参数

    T first<T>(List<T> ts) {
      // Do some initial work or error checking, then...
      T tmp = ts[0];
      // Do some additional checking or processing...
      return tmp;
    }

    这里的 first (<T>) 泛型可以在如下地方使用参数 T :

    • 函数的返回值类型 (T).
    • 参数的类型 (List<T>).
    • 局部变量的类型 (T tmp).

    五 库的使用

    在Dart中,你可以导入一个库来使用它所提供的功能。库的使用可以使代码的重用性得到提高,并且可以更好的组合代码。Dart中任何一个dart文件都是一个库,即使你没有用关键字library声明。

    5.1 库的导入

    import语句用来导入一个库,后面跟一个字符串形式的Uri来指定表示要引用的库,语法如下:

    import '库所在的uri'

    5.1.1 常见的库URI有三种不同的形式

    • 来自dart标准版,比如dart:io、dart:html、dart:math、dart:core(但是这个可以省略)
      //dart:前缀表示Dart的标准库,如dart:io、dart:html、dart:math
      import 'dart:io';
    • 使用相对路径导入的库,通常指自己项目中定义的其他dart文件
      //当然,你也可以用相对路径或绝对路径的dart文件来引用
      import 'lib/student/student.dart';
    • Pub包管理工具管理的一些库,包括自己的配置以及一些第三方的库,通常使用前缀package
      //Pub包管理系统中有很多功能强大、实用的库,可以使用前缀 package:
      import 'package:flutter/material.dart';

    5.1.2 库文件中内容的显示和隐藏

    如果希望只导入库中某些内容,或者刻意隐藏库里面某些内容,可以使用showhide关键字

    • show关键字:可以显示某个成员(屏蔽其他)
    • hide关键字:可以隐藏某个成员(显示其他)
    // 只显示Student, Person,其他的都屏蔽
    import 'lib/student/student.dart' show Student, Person;
    
    // 只屏蔽Person,其他的都显示
    import 'lib/student/student.dart' hide Person;

    5.1.3 库中内容和当前文件中的名字冲突

    当各个库有命名冲突的时候,可以使用as关键字来使用命名空间

    import 'lib/student/student.dart' as Stu;
    
    Stu.Student s = new Stu.Student();

    5.2 库的定义

    5.2.1 library关键字

    通常在定义库时,我们可以使用 library 关键字给库起一个名字。但目前我发现,库的名字并不影响导入,因为import语句用的是字符串URI

    library math;

    5.2.2 part关键字

    在开发中,如果一个库文件太大,将所有内容保存到一个文件夹是不太合理的,我们有可能希望将这个库进行拆分,这个时候就可以使用part关键字了。不过官方已经不建议使用这种方式了:

    image-20190911173722226

    // mathUtils.dart文件
    part of "utils.dart";
    
    int sum(int num1, int num2) {
      return num1 + num2;
    }
    
    // dateUtils.dart文件
    part of "utils.dart";
    
    String dateFormat(DateTime date) {
      return "2020-12-12";
    }
    
    // utils.dart文件
    part "mathUtils.dart";
    part "dateUtils.dart";
    
    // test_libary.dart文件
    import "lib/utils.dart";
    
    main(List<String> args) {
      print(sum(10, 20));
      print(dateFormat(DateTime.now()));
    }

    5.2.3 export关键字

    官方不推荐使用part关键字,那如果库非常大,如何进行管理呢?

    • 将每一个dart文件作为库文件,使用export关键字在某个库文件中单独导入
    // mathUtils.dart文件
    int sum(int num1, int num2) {
      return num1 + num2;
    }
    
    // dateUtils.dart文件
    String dateFormat(DateTime date) {
      return "2020-12-12";
    }
    
    // utils.dart文件
    library utils;
    
    export "mathUtils.dart";
    export "dateUtils.dart";
    
    // test_libary.dart文件
    
    import "lib/utils.dart";
    
    main(List<String> args) {
      print(sum(10, 20));
      print(dateFormat(DateTime.now()));
    }

    最后,也可以通过Pub管理自己的库自己的库,在项目开发中个人觉得不是非常有必要,所以暂时不讲解这种方式。

  • 相关阅读:
    CDH 2、Cloudera Manager的安装
    204 01 Android 零基础入门 03 Java常用工具类 04 Java集合 04 Map集合 01 Map概述
    203 01 Android 零基础入门 03 Java常用工具类 04 Java集合 03 Set集合 07 删除宠物猫信息数据(引入泛型知识点)
    202 01 Android 零基础入门 03 Java常用工具类 04 Java集合 03 Set集合 06 查找宠物猫信息数据
    201 01 Android 零基础入门 03 Java常用工具类 04 Java集合 03 Set集合 05 添加重复的宠物猫信息数据
    200 01 Android 零基础入门 03 Java常用工具类 04 Java集合 03 Set集合 04 添加和显式宠物猫信息
    199 01 Android 零基础入门 03 Java常用工具类 04 Java集合 03 Set集合 03 宠物猫信息管理概述
    198 01 Android 零基础入门 03 Java常用工具类 04 Java集合 03 Set集合 02 案例:在集合中插入字符串
    197 01 Android 零基础入门 03 Java常用工具类 04 Java集合 03 Set集合 01 Set概述
    196 01 Android 零基础入门 03 Java常用工具类 04 Java集合 02 List集合 05 案例:公告的删除和修改
  • 原文地址:https://www.cnblogs.com/mukekeheart/p/14362563.html
Copyright © 2011-2022 走看看