Flutter学习 -- Dart语言二

46 阅读2分钟

六、类和对象

6.1 定义

成员(member)和方法(method)

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

  eat() {
    print('$name在吃东西');
  }
 }

main(List<String> args) {
  // 1.创建类的对象
  var p = Person(); // 直接使用new Person()也可以创建

  // 2.给对象的属性赋值
  p.name = 'why';

  // 3.调用对象的方法
  p.eat();
}

6.2 构造方法
6.2.1 普通构造函数

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

  • 当类中没有明确指定构造方法时,将默认拥有一个无参的构造方法
  • 当有了自己的构造方法时,默认的构造方法将会失效,不能使用
class Person {
  late String name;
  late int age;

  // 等同于 Person(this.name, this.age);
  Person(String name, int age) {
    this.name = name;
    this.age = age;
  }
  
  @override
  String toString() {
    return 'name=$name age=$age';
  }
}
main(List<String> args) {
  // 1.创建类的对象
  var p = Person('why', 18); // 直接使用new Person()也可以创建
  print(p.toString());
}

6.2.2 命名构造方法

因为不支持方法(函数)的重载,所以我们没办法创建相同名称的构造方法。

我们需要使用命名构造方法:

class Person {
  late String name;
  late 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';
  }
}

main(List<String> args) {
  var p1 = new Person();
  print(p1);
  var p2 = new Person.withArgments('why', 18);
  print(p2);
}

6.2.3 初始化列表

在构造函数体执行之前初始化实例变量,每个实例变量之间使用逗号分隔

import 'dart:math';

class Point {
  final double x;
  final double y;
  final double distanceFromOrigin;

  Point(double x, double y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);

 // 等同于 Point(this.x, this.y) : distanceFromOrigin = sqrt(x * x + y * y);
}

void main() {
  var p = Point(2, 3);
  print(p.distanceFromOrigin);
}

6.2.4 重定向构造方法

在一个构造方法中去调用另外一个构造方法, 这个时候可以使用重定向构造方法(注:在冒号后面使用this调用):

class Person {
  String name;
  int age;

  Person(this.name, this.age);
  Person.fromName(String name) : this(name, 0);

  @override
  String toString() {
    return 'name=$name age=$age';
  }
}

void main() {
  var p = Person('why', 3);
  print(p.toString()); //name=why age=3
  var p1 = Person.fromName('why');
  print(p1.toString()); // name=why age=0
}

6.2.5 常量构造方法

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

main(List<String> args) {
  var p1 = const Person('why');
  var p2 = const Person('why');
  print(identical(p1, p2)); // true
}

class Person {
  final String name;

  const Person(this.name);
}

注:

  • 拥有常量构造方法的类中,所有的成员变量必须是final修饰的。
  • 为了可以通过常量构造方法,创建出相同的对象,不再使用 new关键字,而是使用const关键字。
6.2.6. 工厂构造方法

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

main(List<String> args) {
  var p1 = Person('why');
  var pMap = {'name': 'why'};
  var p2 = Person.fromJson(pMap);
  var p3 = Person('why');
  print(identical(p1, p2)); // true
  print(identical(p1, p3)); // true
}

class Person {
  String name;
  bool mute = false;

  static final Map<String, Person> _cache = <String, Person>{};

  factory Person(String name) {
    return _cache.putIfAbsent(name, () => Person._internal(name));
  }

  factory Person.fromJson(Map<String, Object> json) {
    return Person(json['name'].toString());
  }

  Person._internal(this.name);
}
6.3 setter和getter

默认情况下,Dart中类定义的属性是可以直接被外界访问的。Getter 和 Setter 是一对用来读写对象属性的特殊方法。

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);
}

6.4 类的继承

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

main(List<String> args) {
  // 父类调用
  var p = new Person();
  p.name = '小明';
  p.age = 18;
  p.run();
  print(p.age);

  // 子类调用
  var a = new Animal();
  a.age = 10;
  a.run();
  print(a.age);
}

class Animal {
  late int age;

  run() {
    print('在奔跑ing');
  }
}

class Person extends Animal {
  late String name;

  @override
  run() {
    print('$name在奔跑ing');
  }
}

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

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

  • 子类的构造方法在执行前,将隐含调用父类的无参默认构造方法(没有参数且与类同名的构造方法)。
  • 如果父类没有无参默认构造方法,则子类的构造方法必须在初始化列表中通过super显式调用父类的某个构造方法
main(List<String> args) {
  // 父类调用
  var p = new Person('小明', 18);
  p.run();
  print(p.age);

  // 子类调用
  var a = new Animal(10);
  a.run();
  print(a.age);
}

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';
  }
}

6.5 抽象类

使用关键字 abstract 标识类可以让该类成为 抽象类,抽象类将无法被实例化 。父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,,我们可以定义为抽象方法

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

main(List<String> args) {
  // 父类调用
  var p = new Circle(3);
  print(p.getArea());

  // 子类调用
  var r = new Reactangle(10, 20);
  print(r.getArea());
}

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;
  }
}

6.6 抽象类

如果想要创建一个 A 类支持调用 B 类的 API 且不想继承 B 类,则可以实现 B 类的接口。 一个类可以通过关键字 implements 来实现一个或多个接口并实现每个接口定义的 API:

class Person {
  final String _name;

  Person(this._name);

  String greet(String who) => 'Hello, $who. I am $_name.';
}

class Impostor implements Person {
  String get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

6.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 {
  @override
  fly() {
    print('超人在飞');
  }
}

6.8 类变量和方法

使用static可以声明类变量和方法。前面我们在类中定义的成员和方法都属于对象级别的, 在开发中, 我们有时候也需要定义类级别的成员和方法。

class Student {
  late String name;
  late int sno;

  static late String time;

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

  static attendClass() {
    print('去上课');
  }
}

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(); 错误做法, 实现对象不能‍访问类方法
}
6.9 枚举类型
6.9.1 定义
main(List<String> args) {
  print(Colors.red);
}

enum Colors { red, green, blue }

6.9.2 枚举的属性
main(List<String> args) {
  print(Colors.red.index);
  print(Colors.green.index);
  print(Colors.blue.index);

  print(Colors.values);
}

enum Colors { red, green, blue }

七、泛型

7.1 List和Map的泛型

List使用时的泛型写法:

  // 创建List的方式
  var names1 = ['why', 'kobe', 'james', 111];
  print(names1.runtimeType); // List<Object>

  // 限制类型 报错
  var names2 = <String>['why', 'kobe', 'james', 111]; // 最后一个报错
  List<String> names3 = ['why', 'kobe', 'james', 111]; // 最后一个报错

Map使用时的泛型写法:

  // 创建Map的方式
  var infos1 = {1: 'one', 'name': 'why', 'age': 18};
  print(infos1.runtimeType); // _Map<Object, Object>

  // 对类型进行显示 报错
  Map<String, String> infos2 = {'name': 'why', 'age': 18}; // 18不能放在value中
  var infos3 = <String, String>{'name': 'why', 'age': 18}; // 18不能放在value中
7.2 类定义的泛型

如果我们需要定义一个类, 用于存储位置信息Location, 但是并不确定使用者希望使用的是int类型,还是double类型,  甚至是一个字符串, 这个时候如何定义呢?

  • 一种方案是使用Object类型, 但是在之后使用时, 非常不方便
  • 另一种方案就是使用泛型.

Location类的定义: Object方式

main(List<String> args) {
  Location l1 = Location(10, 20);
  print(l1.x.runtimeType); // int

  Location l3 = Location('aaa', 'bbb');
  print(l3.x.runtimeType); // String
}

class Location {
  Object x;
  Object y;

  Location(this.x, this.y);
}

Location类的定义: 泛型方式

main(List<String> args) {
  Location l2 = Location<int>(10, 20);
  print(l2.x.runtimeType); // int

  Location l3 = Location<String>('aaa', 'bbb');
  print(l3.x.runtimeType); // String
}

class Location<T> {
  T x;
  T y;

  Location(this.x, this.y);
}

如果我们希望类型只能是num类型

main(List<String> args) {
  Location l2 = Location<int>(10, 20);
  print(l2.x.runtimeType);

  // 错误的写法, 类型必须继承自num
  Location l3 = Location<String>('aaa', 'bbb');
  print(l3.x.runtimeType);
}

class Location<T extends num> {
  T x;
  T y;

  Location(this.x, this.y);
}

7.3 泛型方法的定义

方法和参数也可以使用类型参数

main(List<String> args) {
  var names = ['why', 'kobe'];
  var first = getFirst(names);
  print('$first ${first.runtimeType}'); // why String
}

T getFirst<T>(List<T> ts) {
  return ts[0];
}

参考文档: Dart文档 课程学习