Dart学习笔记(五):类

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

内容简介: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
  • 静态方法 可以访问 其他静态方法静态变量

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

查看所有标签

猜你喜欢:

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

PHP and MySQL Web Development

PHP and MySQL Web Development

Luke Welling、Laura Thomson / Sams / July 25, 2007 / $49.99

Book Description PHP and MySQL Web Development teaches you to develop dynamic, secure, commerical Web sites. Using the same accessible, popular teaching style of the three previous editions, this b......一起来看看 《PHP and MySQL Web Development》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具