Flutter开发指南之理论篇:Dart语法03(类,泛型)

总目录

Flutter开发指南之理论篇:Dart语法01(数据类型,变量,函数)
Flutter开发指南之理论篇:Dart语法02(运算符,循环,异常)
Flutter开发指南之理论篇:Dart语法03(类,泛型)
Flutter开发指南之理论篇:Dart语法04(库,异步,正则表达式)


 Dart是一门面向对象语言,它针对web 和移动设备开发进行了优化,主要特点为:

  • 一切皆对象!无论是数字,函数还是null,所有对象继承自Object类;
  • 声明一个变量时可以不指定具体类型,Dart可以自动推断类型;
  • Dart支持顶层函数,函数是一等对象,且函数可作为参数传递;
  • Dart使用_开头表示私有属性,没有关键字publicprotectedprivate

1. 类

1.1 类的定义

 Dart是一种基于类mixin继承机制的面向对象的语言,每个对象都是一个类的实例,所有的类都继承于 Object。基于Mixin继承意味着每个类(除Object外)都只有一个超类,一个类中的代码可以在其他多个继承类中重复使用。定义一个类大体形式如下:

class 类名 {
    // 成员变量
    类型 变量名;

    // 构造函数(方法)
    类名([参数1,..]) {
        
    }
    
    // 成员函数(方法)
    类型 方法名([参数1,...]) {
        
    }
}

 从类定义可知,Dart中的类创建形式同Java一致。

1.1.1 构造函数

 构造函数主要用于创建一个类的对象(实例),通常使用new关键字实现,但是在Dart中该关键字可以省略。在没有声明构造函数的情况下,Dart会提供一个默认的构造函数,并且默认构造函数没有参数,会自动调用父类的无参构造函数。需要注意的是,子类不会继承父类的构造函数。举个例子:


class PersonalInfoBean {
    // 成员变量
    // 当声明时没有初始化,Dart会自动设置其初始值为null
    String name;
    num age;  
    var sex = 'male';
    
    // 构造函数(带参数)
    // this关键字可省略
    PersonalInfoBean(String name, num age, var sex) {
        this.name = name;
        this.age = age;
        this.sex = sex
    }
    
    // 【dart语法糖】
    // 精简构造函数,因此上述构造函数等价于
    // PersonalInfoBean(this.name, this.age, this.sex);
}

 需要注意的是,dart中所有实例变量都生成隐式getter方法,非final的实例变量同样会生成隐式setter方法。因此,在调用类对象的成员变量时,最终会调用变量的getter或setter方法获取更新值。特别的,dart支持使用使用 ?. 来代替.访问类对象的成员,可以避免因为左边对象可能为null,进而导致的异常。

// 使用无参构造函数
val personalInfo = PersonalInfoBean();

print('name = ${personalInfo.name}'); // 打印 "name = null"
print('name = ${personalInfo.sex}'); // 打印 "name = male"

personalInfo.name = 'jiangdg';
print('name = ${personalInfo.name}'); // 打印 "name = jiangdg"

// 使用有参构造函数
val personalInfo2 = PersonalInfoBean('JJ', 45, 'female');
print('name = ${personalInfo.name}'); // 打印 "name = JJ"
print('name = ${personalInfo.sex}'); // 打印 "name = 45"

 除了常规方式,Dart还支持几种特殊的构造函数:

  • 命名构造函数

 使用命名构造函数可为一个类实现多个构造函数,也可以使用命名构造函数来更清晰的表明函数意图。注意,由于构造函数不能被继承,这就意味着命名构造函数不会被子类继承,如果希望使用父类中定义的命名构造函数创建子类,就必须在子类中实现(重写)该构造函数。示例如下:

class Point {
  // dart使用_表示私有属性
  num _x, _y;

  Point(this.x, this.y);

  // 命名构造函数
  Point.origin() {
    x = 0;
    y = 0;
  }
}

// 使用命名构造函数
// 实例化一个对象
val originPoint = Point.origin();
print('x = ${originPoint.x}'); // 打印 "x = 0"
print('y = ${originPoint.y}'); // 打印 "y = 0"
  • 重定向构造函数

 重定向构造函数实际上就是在一个构造函数中调用同一个类的其他构造函数,但是与Java不同的是,Dart的重定向构造函数的函数体为空,且构造函数的调用在冒号(:)之后`。示例如下:

class Point {
  num x, y;

  // 类的主构造函数。
  Point(this.x, this.y);

  // 重定向构造函数
  // 使命名构造函数alongXAxis指向主构造函数
  Point.alongXAxis(num x) : this(x, 0);
}
  • 常量构造函数

 如果该类生成的对象是固定不变的,那么就可以把这些对象定义为编译时常量。为此,需要定义一个 (1)const 构造函数, 并且(2)声明所有实例变量(成员变量)为 final。示例如下:

class ImmutablePoint { 
  // 成员变量均需要使用final标识
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final num x, y;
  // const构造函数
  const ImmutablePoint(this.x, this.y);
}

 需要注意的是,使用常量构造函数创建的编译时常量实例,需要在构造函数名之前加const关键字,如果缺少const,那么创建的就不是一个编译时常量实例。示例如下:

// a,b是同一个实例(编译时常量)
// 即a=b
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

// a,b不是同一个实例,即a!=b
// a是常量对象,b时非常量对象
var a = const ImmutablePoint(1, 1);
var b = ImmutablePoint(1, 1);

 特别地,在常量上下文中,构造函数或字面量前的const可以省略。

const a = const ImmutablePoint(1, 1);
// 等价于
// const a = ImmutablePoint(1, 1);

const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
// 等价于
// const pointAndLine = {
//  'point': [ImmutablePoint(0, 0)],
//  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
// };
  • 工厂构造函数

 当执行构造函数并不总是创建这个类的一个新实例时,则使用factory关键字。也就是说,通过工厂构造函数创建的实例始终都是同一个,类似于Java中的单例模式。下面的例子演示了从缓存中返回对象的工厂构造函数,即不同类型的日志使用对应且唯一的Logger实例:

class Logger {
  final String name;
  bool mute = false;

  // 从命名的 _ 可以知,
  // _cache 是私有属性。
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

 需要注意的是,工厂构造函数中无法访问this。工厂构造函数的调用方式与其他构造函数一样:

var logger = Logger('UI');
logger.log('Button clicked');

1.1.2 成员方法

  • Getter和Setter方法

 Dart类的成员方法定义和使用与Java类似,比如访问this和成员变量等,就无须过多解释,这里着重解释下GetterSetter方法。前面我们说到,每个实例变量都有一个隐式Getter方法,非final的实例变量还有一个Setter方法,这两个方法分别实现了实例变量值的获取和更新。特别地,Dart还提供了getset关键字实现Getter和Setter,以便为实例创建额外的属性。示例如下:

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // 定义两个计算属性: right 和 bottom。
  num get right => left + width;
  set right(num value) => left = value - width;
  
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}
  • 抽象方法

 抽象方法只存在于抽象类中,它的定义很简单,就是使用;来代替函数体。

abstact class AbstractAnimal {
    ...
    // 抽象方法
    void skills();
}

 抽象方法不能被直接调用,且可以在子类中实现。

class Dog extends AbstractAnimal {
    
    // 重写skills
    void skills() {
        print('Dog can fly')
    }
}
  • 静态方法

 与Java一样,Dart使用static关键字声明类的静态方法或变量。

mport 'dart:math';

class Point {
  // 静态变量
  static const initialCapacity = 16;
  num x, y;
  Point(this.x, this.y);
  
  // 静态方法
  static num distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

1.2 抽象类与隐式接口

1.2.1 抽象类

 与Java一样,Dart也使用abstract修饰符来定义一个抽象类,它包含了抽象方法和非抽象方法(即有具体实现的成员方法),通常抽象类不能实例化。如果希望抽象类能够被实例化,那么可以通过定义一个工厂构造函数来实现。示例如下:

// 这个类被定义为抽象类,
// 所以不能被实例化。
abstract class AbstractAnimal {
  // 定义构造函数,字段,方法...

  void skills(); // 抽象方法。
}

1.2.2 隐式接口

 与Java使用interface定义一个接口不同,Dart中没有真正意义上的接口,即不能使用interface显示地定义一个接口,如果我们获得多重继承的效果,Dart是推荐使用Mixin方式。但是,Dart却又有一个隐式接口的概念,所谓隐式接口,是指当我们在定义一个类时,Dart会为这个类自动创建一个与其同名且拥有所有成员(变量和方法)的接口。当其他类需要实现这个接口时,就必须使用implement关键字,并重写该接口的所有成员。举个例子:

class Vehicle{
  num speed  = 0  ;
  
  Vehicle(){
    print("super Constructor") ;
  }
  
  void run(){
    print("vehicle run") ;
  }
  
  void printSpeed(){
    print("spped = > $spped") ;
  }
}

 除了上述的Vehicle类,Dart还自动生成一个名为Vehicle的接口,继承方式如下:

class HandsomeBoy implements Vehicle{
  @override
  void run() {
    print("HandsomeBoy running");
  }
  @override
  void printspped() {
    print("HandsomeBoy spped = $spped") ;
  }
  //覆盖(实现)成员变量
  @override
  num spped;
}

1.3 继承与Mixin

1.3.1 继承

 与Java一样,Dart也是使用extends关键字来创建子类,使用super关键字来引用父类,并且使用@override注解指出想要重写的类成员(变量和方法)。示例如下:

// 父类
class BaseUtil {
    void logMsg(var msg) {
        print('I am father, msg=$msg');
    }
}

// 子类
class LogUtil extend BaseUtil {
    
    @override
    void logMsg(var msg) {
        super.logMsg(msg);
        print('I am child, msg=$msg');
    }
}

 特别地,Dart支持在任何类中,通过重写noSuchMethod()方法,来实现检测和应对处理调用不存在的方法或实例变量情况,

class A {
  // 如果不重写 noSuchMethod,访问
  // 不存在的实例变量时会导致 NoSuchMethodError 错误。
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

1.3.2 Mixin

Mixin是面向对象程序设计语言中的类,提供了方法的实现,mixin为使用它的class提供额外的功能,但自身却不单独使用(注:不能单独实例化对象,属于抽象类),其他类可以访问mixin类的方法而不必成为其子类。因此,Mixin类通常作为功能模块使用,在需要该功能时“混入”,而且不会使类的关系变得复杂。使用者与Mixin不是“is-a”的关系,而是「-able」关系。Mixin有利于代码复用又避免了多继承的复杂,使用Mixin享有单一继承的单纯性和多重继承的共有性。Mixin与Java中的接口(interface)功能相似,它们相同的地方是都可以多继承,不同的地方在于mixin是带实现的。

  • 定义Mixin类

 Dart中,Mixin类在定义类时使用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');
    }
  }
}

 同时,Mixin类可以使用关键字on来指定父类类型,便于调用调用自身没有定义的方法。

mixin MusicalPerformer on Musician {
  // ···
}
  • 使用Mixin类

 Dart中,通过关键字with来使用一个或多个Mixin类。

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

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

1.4 枚举类

 枚举类型是一种特殊的类,用于表示数量固定的常量值。Dart中使用enum关键字定义一个枚举类型,枚举中的每个值都有一个 index getter 方法, 该方法返回值所在枚举类型定义中的位置(从 0 开始)。示例如下:

enum Color {
    red, 
    green,
    blue
}

 使用枚举Color:

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('color is Red, index = ${Color.red.index}!');
    break;
  case Color.green:
    print('color is Green, index = ${Color.green.index}!');
    break;
  default: // 没有这个,会看到一个警告。
    print(aColor); // 'Color.blue'
}

 枚举类型具有以下限制:

  • 枚举不能被子类化,混合或实现。
  • 枚举不能被显式实例化。

2. 泛型

 与Java一样,Dart也支持泛型,它们的用法基本差不多。所谓泛型,是指使用<...>符号来泛指参数的类型,注意是泛指,也就是说这个参数的真实类型会随着<...>中的类型变化而变化,比如List/Map类等在定义时就使用了泛型,即List<E>/Map<K, V>,这就意味着List表示字符串类型列表,而List表示整型类型列表。通常情况下,<...>中的...使用一个字母代表参数类型为泛型,比如E,T,S,K和V等,它们也被成为占位符

 泛型优点:

  • 有助于确保类型安全,提高代码质量;
  • 减少重复的代码,使代码更加紧凑;

2.1 泛型类

 泛型类定义:

class 类名<T [extends SomeBaseClass]> {
    
    T 方法名(T 参数,...) {
        T 参数名;
        ...
        return T;
    }
}

 从泛型类定义可知,T表示泛型类型,使用泛型时,可以使用extends实现参数类型的限制,比如自定义类SomeBaseClass,那么占位符T的类型必须只能为SomeBaseClass类及其子类。当然,extends关键字是可选的,如果不指定,T可以为任何类型。需要注意的是,在调用泛型类构造函数的时以实例化一个对象时,需要在在类名字后面使用尖括号<...>来指定泛型类型。另外,在类中如果方法使用了占位符T,那么该方法又可称为泛型方法,它的定义形式与泛型函数一致。

案例:List源码

abstract class List<E> implements EfficientLengthIterable<E> {
    // 添加一个类型为E的元素
    void add(E value);
    // 删除列表下标为index类型为E的元素
    // 然后将其返回
    E removeAt(int index);
    ...
}

 从List源码可知,它就是一个泛型类,当我们需要不同类型的列表时,在定义List时向<>内指定具体的类型即可,而不需要去创建各种逻辑相同,仅是类型不同的类。因此,泛型的出现,使得减少重复的代码,使代码更加紧凑。比如,实例化一个List:

// 定义一个字符串类型的列表
// 需要指定<...>中的类型
var names = List<String>();
names.add(‘jiangdg’);
names.add(188);     // 报错,List列表元素只能为String类型

// 定义一个自定义类型的列表
var names = List<PersonInfoBean>();
names.add(PersonInfoBean('jiangdg', 18, 'male'));

2.2 泛型函数

 泛型函数格式:

T 函数名<T> (T 参数,..) {

    T tmp = value;
    
    return tmp;
}

 从泛型函数定义可以看出,可以在下列地方使用泛型T,即:

  • 函数的返回值类型;
  • 参数类型,包括元素类型是T的集合,如List;
  • 局部变了的类型。

案例:HashMap源码

abstract class HashMap<K, V> implements Map<K, V> {
  // 由other创建另一个HashMap
  factory HashMap.from(Map other) {
    Map<K, V> result = HashMap<K, V>();
    other.forEach((k, v) {
      result[k] = v;
    });
    return result;
  }
  
  // 删除一条元素
  V remove(Object key);
  
  // 迭代遍历
  void forEach(void f(K key, V value));
  ...
}

 HashMap是一个key-value集合,因此,K表示key的类型,V表示value的类型。当我们在定义一个HashMap实例时,需要指定K/V的具体类型,如:

// K为Int类型
// V为String类型
var historyMap = HashMap<Int, String>();

// K为String类型
// V为自定义类PersonalInfoBean类型
var personInfoMap = HashMap<String, PersonalInfoBean>();

// 插入一条元素
personInfoMap['jiangdg'] = PersonalInfoBean('jiangdg', 18, 'male');

// 移除一条元素
personInfoMap.remove('jiangdg');
personInfoMap.remove(18); // 报错,Map的Key类型为String类型

参考文献:

1. Dart编程语言中文网

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:白松林 返回首页