Dart学习笔记(五):类

栏目: 后端 · 前端 · 发布时间: 5年前

内容简介:Dart是面向对象的语言,所有对象都是一个类的实例,所有类都继承自类的实例化,可以使用如果要访问对象的成员(

Dart是面向对象的语言,所有对象都是一个类的实例,所有类都继承自 Object 。此外, Dart 支持基于 mixin 的继承机制,这意味着,每个类(除了 Object )都只有一个父类,一个类的代码可以在其他多个类继承中重复使用。

一、类的实例化

类的实例化,可以使用 new 关键字,后面可以接上 构造函数 (如 ClassName 或者 ClassName.identifier ),如:

var jsonData = JSON.decode('{"x":1, "y":2}');
// 直接接 ClassName
var p1 = new Point(2, 2);
// 接 ClassName.identifier
var p2 = new Point.fromJson(jsonData);

如果要访问对象的成员( 属性 或者 方法 ),可以使用 . 语法:

var distance = p1.distanceTo(p2);

还可以使用 ?. 来避免左边对象为 null 时抛出异常:

p?.y = 4; // 如果p为非null,才会执行 p.y = 4;

有些类提供了 常量构造函数 ,可以创造 编译时常量 ,那么这种构造函数的实例化不采用 new 关键字,而是采用 const 关键字,如下:

var p = const ImmutablePoint(2, 2);

两个一样的编译时常量是同一个对象,用 identical 测试返回 true

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

identical(a, b); // true

此外, runtimeType 可以获得一个示例的类型,该属性返回一个 Type 对象:

main() {
  int a = 1;
  print('type of a is ${a.runtimeType}');
}
// 输出:type of a is int

二、类的定义

使用 class 关键字可以声明一个类:

class Foo {
}

1)实例变量

属于类的实例的变量称为 实例变量实例变量 使用 var propertyName; 或者 type propertyName 声明在 class 体里,如果一个 实例变量 没有赋予初始值,则它的初始值是 null ,如:

class Point {
    num x; // 初始值 null
    num y; // 初始值 null
    num z;
}

每个实例变量都会隐含地生成一个 getter 方法,对于非 final 的变量还会隐含地生成一个 setter ,所以可以:

var point = new Point();
point.x = 4; // 调用了 setter 方法来设置变量值
point.y; // 调用了 getter 方法来获取变量值

注意:如果在定义实例变量时初始化了 实例变量 ,那么实例变量的值是在实例创建时、构造函数和初始化参数列表 执行前 初始化的

2)构造函数

class 中命名和 类名 一致的那个函数是 构造函数 ,构造函数会在 被实例化时调用,如:

class Point {
    num x;
    num y;
    Point(num x, num y) {
        this.x = x;
        this.y = y;
    }
}

其中 this 关键字指向当前的实例。不过,只有当名字冲突时才使用 thisDart 中在类里是可以忽略 this 的,也就是说,我们可以这么写:

class Point {
    num x;
    num y;
    Point(num _x, num _y) {
        x = _x;
        y = _y;
    }
}

或者,也可以直接在构造函数中使用 初始化赋值 的语法糖:

class Point {
    num x;
    num y;
    Point(this.x, this.y);
}

1、默认构造函数

如果在一个类中没有定义构造函数,则会有个默认的构造函数。默认的构造函数无参数,且会调用没有参数的构造函数。

2、构造函数不会继承

子类 不会继承 父类 的构造函数(除非是无名无参的构造函数)

3、命名构造函数

使用命名构造函数可以实现为一个类指定 多个构造函数 ,也可以通过这来更清晰地表达意图,如:

class Point {
    num x;
    num y;
    Point(this.x, this.y);
    Point.fromJson(Map json) {
        x = json['x'];
        y = json['y'];
    }
}

4、调用父类的构造函数

默认情况下,子类的构造函数会自动调用父类的 无名无参的默认构造函数 ,父类的构造函数在子类构造函数的函数体开头位置调用。但是如果提供了 初始化参数列表 ,则初始化参数列表会在 父类构造函数 之前执行,也就是说,执行顺序如下:

  • 初始化参数列表
  • 父类无名构造函数
  • 子类无名构造函数

如果超类中没有 无名无参 构造函数,那么就需要手动调用了,调用方法为在 构造函数 之后使用 : 调用,如:

class Point {
  num x;
  num y;
  Point(this.x, this.y);
}

class Point3D extends Point {
  num z;
  Point3D(num x, num y, num z): super(x, y) {
    this.z = z;
  }
}

由于 父类构造函数 是在 子类构造函数 执行前执行的,所以参数可以是一个表达式或者一个方法调用,如:

class A {
    A(String str) {
        print(str);
    }
}
class B extends A {
    B(): super(getDefaultData());
    static getDefaultData() {
        return 'Hello, world';
    }
}

注意:如果是一个类中的 方法 调用,那么只能使用静态方法

5、初始化列表

初始化列表除了可以调用父类构造函数,还可以初始化实例参数,采用 , 分隔表达式,如:

class Point {
    num x;
    num y;
    Point(this.x, this.y);
    Point.fromJson(Map jsonMap):
        x = jsonMap['x'],
        y = jsonMap['y'] {
        print('($x, $y)');
    }
}

需要注意的是:初始化表达式 = 右边的部分不能访问 this ,此外,对于 final 变量的值,可以在初始化列表中指定,如:

import 'dart:math';

class Point {
    final num x;
    final num y;
    final num distanceFromOrigin;
    Point(x, y):
        x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

6、重定向构造函数

有时候一个 构造函数 会调用类中的其他构造函数,这种构造函数称之为 重定向构造函数 ,如下:

class Point {
    num x;
    num y;
    Point(this.x, this.y);
    Point.alongXAxis(num x): this(x, 0);
}

7、常量构造函数

如果类提供的是状态不变的对象,那么可以把这些对象定义为 编译时常量 ,实现这种功能可以定义 const 构造函数,且声明所有类的变量为 final ,如:

class ImmutablePoint {
    final num x;
    final num y;
    const ImmutablePoint(this.x, this.y);
    static final ImmutablePoint origin = const Immutable(0, 0);
}

8、工厂方法构造函数

如果一个构造函数并不总是返回一个新的对象,那么可以在构造函数前面加上 factory 关键字,来表示它是一个 工厂方法构造函数 。但是需要注意的是,工厂构造函数中不能访问 this ,如下:

class Logger {
    final String name;
    bool mute = false;
    // 缓存实例
    static final Map<String, Logger> _cache = <String, Logger>{};

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

    Logger._internal(this.name);

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

那么,在使用 new 关键词实例化 Logger 时,每次都会调用 factory Logger(String name) 这个构造函数:

var logger1 = new Logger('UI');
var logger2 = new Logger('UI');
identical(logger1, logger2); // true

3)实例方法

实例方法可以访问 thisDart 中实例方法的声明如下:

class A {
    methodName() {
        // ...
    }
}

4)setter/getter

在一个方法前面加上 settergetter 关键字,那么这个方法就成为了 settergetter ,就可以对 instance.methodName 进行赋值或者取值操作,而无需使用 () 来调用,如:

class Person {
    String firstName;
    String lastName;
    Person(this.firstName, this.lastName);

    String get fullName {
        return '$firstName $lastName';
    }
}
final me = new Person('Ruphi', 'Lau');
print(me.fullName); // 输出:Ruphi Lau

注意, getter 里不能带 () ,即 get name() 是错误的, get name 才是正确的,而 setter 里则可以接受一个参数,即为赋值传入的值: set name(String name)

三、操作符重写

Dart 中支持操作符重写,可被重写的操作符有:

  • 比较运算符: ><<=>===
  • 算数运算符: +-*/%~/
  • 位运算符: |&^~<<>>
  • 方括号运算符: [][]=

重写运算符的语法为使用 operator 关键字紧接运算符,以下例子为实现 向量 的运算:

class Vector {
    final int x;
    final int y;
    const Vector(this.x, this.y);

    Vector operator +(Vector v) {
        return new Vector(x + v.x, y + v.y);
    }

    Vector operator -(Vector v) {
        return new Vector(x - v.x, y - v.y);
    }
}

main() {
    final v = new Vector(2, 3);
    final w = new Vector(2, 2);
    
    final addRes = v + w;
    print('(${addRes.x}, ${addRes.y})'); // 输出:(4, 5)
    
    final minusRes = v - w;
    print('(${minusRes.x}, ${minusRes.y})'); // 输出:(0, 1)
}

四、抽象类

1)抽象类的定义

不能被实例化的类是抽象类,抽象类通常用来定义接口及 部分实现 。如果抽象类要被实例化,则需要定义一个 工厂构造函数 。抽象类的声明使用 abstract 修饰符:

abstract class AbstractContainer {
    // ...
}

2)抽象函数

抽象函数是之定义函数接口但是没有实现(方法体)的函数,抽象函数由子类实现,调用一个未实现的抽象函数会导致运行时异常。如下:

abstract class Doer {
    void doSomething(); // 抽象函数,没有方法体
}
class EffectiveDoer extends Doer {
    void doSomething() {
        // 在子类中实现
    }
}

3)隐式接口

每个类都隐式地定义了一个包含所有实例成员的接口,并且这个类实现了该接口。 Dart 中并没有直接提供 interface 这样子的关键字,因此定义 interface 应该通过定义一个类实现,如果只想支持某个类的接口但是不想继承它的实现,那么使用 implements 关键字即可,如下:

class Person {
    final name;
    Person(this.name); // 构造函数不会创建接口
    String greet(who) => 'Hello, $who. I am $name'; // 包含了 greet 的实现
}

class Stark implements Person {
    final name = 'Tony Stark';
    String greet(who) => '$who, I am, I am $name';
}

main() {
    final ironMan = new Stark();
    print(
      ironMan.greet('Thanos')
    );
}

接口是可以 多实现 的,如下:

class TonyStart implements American, Scientist, Richman, Playboy {
    // ...
}

五、类的继承

类的继承,采用 extends 关键字,而 子类 中可以使用 supper 来引用 父类 ,如下:

class TV {
    void turnOn() {
        _illuminateDisplay();
        _activateIrSensor();
    }
    // ...
}

class SmartTV extends TV {
    void turnOn() {
        super.turnOn();
        _bootNetworkInterface();
        _initializeMemory();
        _upgradeApps();
    }
    // ...
}

子类可以覆写 实例函数gettersetter 。以下例子为覆写 noSuchMethod() 函数(这个函数为 Object 类中当调用了对象上不存在的函数所触发的):

class A {
    void noSuchMethod(Invocation mirror) {
        print('You tried to use a non-existent member: ${mirror.memberName}');
    }
}

此外,可以使用 @override 注解来表明是想要覆写超类的一个函数,如:

class A {
    @override
    void noSuchMethod(Invocation mirror) {
        // ...
    }
}

六、枚举类型

枚举类型是一种特殊的 ,用来表示枚举,使用 enum 关键字可以定义枚举:

enum Color {
    RED,
    GREEN,
    BLUE
}

枚举类型中的每个值都有一个 index getter ,返回枚举值在定义中出现的位置(从0开始):

Color.RED;   // 0
Color.GREEN; // 1
Color.BLUE;  // 2

可以使用 values 来获得所有的枚举值,如:

List<Color> colors = Color.values;

若是在 switch 语句中使用枚举,那么需要处理枚举类型的所有值,或者定义一个 default 分支,否则会导致抛出一个警告:

Color someColor = Color.RED;
switch (someColor) {
    case Color.RED:
        // ...
        break;
    case Color.GREEN:
        // ...
        break;
    // 会报错,因为没有对 Color.BLUE 进行处理
}

Dart中的枚举类型,有如下的限制:

  • 无法继承枚举类型,无法使用mixin,无法实例化枚举
  • 无法显示地初始化一个枚举类型

七、Mixins

Mixins 是一种在多类继承中重用一个类代码的手段,可以为类添加新的功能。使用 Mixins 的方法为使用 with 关键字,如下:

class Person {
    final name;
    Person(this.name);
}
class Program {
    program() => print('Program');
}
class Reading {
    reading() => print('Reading');
}
class Tom extends Person with Program, Reading {
    Tom(): super('Tom') {
        print('$name can:');
        program();
        reading();
    }
}

main() {
    new Tom();
}

以上代码输出:

Tom can:
Program
Reading

如果一个类继承 Object ,但是该类没有构造函数,那么就不能调用 super ,这个类就是一个 mixin ,如:

abstract class 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');
        }
    }
}

Dart 1.13 开始,Dart中的 Mixins 以下限制不再那么严格了:

  • Mixins 可以调用其他类,不再限制为继承 Object
  • Mixin 可以调用 super()

八、静态变量与静态函数

可以使用 static 关键字来定义 静态变量静态函数 ,他们属于类自身,不属于任意一个实例。如下:

class Chinese {
    static const from = 'China';
    static whereAreYouFrom() {
        print('I am from $from');
    }
}

需要注意的是:

  • 静态方法 由于属于 ,但是 this 代指的是实例对象,所以静态方法不能访问 this
  • 静态方法 可以访问 其他静态方法静态变量

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据驱动:从方法到实践

数据驱动:从方法到实践

桑文锋 / 电子工业出版社 / 2018-3 / 49

本书是从理论到实践的全面且细致的企业数据驱动指南,从作者的百度大数据工作说起,完整还原其从零到一构建百度用户行为大数据处理平台经历。详解大数据本质、理念与现状,围绕数据驱动四环节——采集、建模、分析、指标,深入浅出地讲述企业如何将数据驱动方案落地,并指出数据驱动的价值在于“数据驱动决策”、“数据驱动产品智能”。最后通过互联网金融、电子商务、企业服务、零售四大行业实践,从需求梳理、事件指标设计、数据......一起来看看 《数据驱动:从方法到实践》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试