六、类和对象
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];
}