Dart
是面向对象的编程语言,Dart
中类的很多概念跟其他语言中类的很多概念基本一致,但是Dart
中的类加入一些独有的特性。
在 Dart 中所有对象都是某个类的实例,所有 类继承
了 Object
类。
如下示例中,p
为Person
类的实例对象,name
和age
为实例对象上的属性,run()
为实例对象上的方法。
void main() {
Person p = new Person();
p.name = 'Axe';
p.run();
}
class Person {
String? name;
int? age; // 表示可空类型
run() {
print('$name + $age');
}
}
// 输出: Axe + null
私有属性和方法在定义变量名时在前面加 _
。
私有属性及私有方法只能在类的内部访问和调用,不能通过实例对象访问和调用。
属性及方法私有化的主要目的是为了不让通过实例对象对类的属性值修改,或通过实例对象调用类里面的方法。
如上一个示例中可以使用p.name = 'Axe'
直接修改Person
类中name属性的值,也可以通过实例对象p
调用Person
类中的run
方法。
同一文件内,实例对象是可以访问类中的私有属性及私有方法的。当其被抽离成一个文件时,私有属性的作用才生效。
// main.dart
import 'person.dart';
void main() {
Person p = new Person();
p.name = 'Tomy'; // 此行报错:The setter 'name' isn't defined for the class 'Person'.
p.age = 18;
p.run();
}
// person.dart
class Person {
String? _name; // 私有属性
int? age;
run() {
print('$_name + $age');
}
}
静态属性及静态方法不可以通过实例对象访问,只能通过类名访问。
class Person {
static String name = 'Tomy';
static void show() {
print('show' + ' ' + '$name');
}
}
main() {
print(Person.name); // Tomy
Person.show(); // show Tomy
Person p = new Person();
print(p.name); // 报错:The getter 'name' isn't defined for the class 'Person'.
}
静态属性不能被构造函数初始化且不能定义为私有属性,即使在变量名前加_
在外部依然可以访问。静态属性在内存中独一份,只要有一处修改静态属性name
的值为Tomy
,则所有Person.name
的值都是Tomy
。
静态方法中可以访问静态属性,但是不可以访问实例属性及私有属性。实例方法中既可以访问静态属性也可以访问实例属性。
常量属性(const关键字声明的属性)必须为静态属性(必须使用static修饰)。
class Person {
static String name = 'Tomy';
int age = 18;
String? hobby;
// 实例方法中既可以访问静态属性也可以访问实例属性。
say() {
print(name);
print(age);
print(hobby);
};
static void show() {
// 静态方法中访问实例属性和私有属性;
// 提示:Instance members can't be accessed from a static method
print(age); // Error: Undefined name 'age'.
print(hobby); // Error: Undefined name 'hobby'.
}
}
main() {
Person.name = "Jerry";
print(Person.name);
Person.show();
Person p = Person();
p.say();
}
Dart
中构造函数的函数名与类名相同且不能被显式调用,没有返回值类型、也没有返回值,构造函数在实例对象被创建时自动执行。
当用户没有显式的去定义构造函数时, 编译器会为类生成一个默认的构造函数, 称为 “默认构造函数”, 默认构造函数不能完成对类数据成员(属性和方法)的初始化, 只能给实例对象创建一标识符, 并为实例对象中的数据成员开辟一定的内存空间。
调用类的构造函数,new 可以省略。
import 'dart:math';
// 定义类
class Point {
num x = 0, y = 0;
// Point(this.x, this.y); // 构造器
// 或者
Point(x, y) {
this.x = x;
this.y = y;
print('这是构造函数,在实例化时触发');
}
// 实例方法
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
main() {
// 调用类的构造函数,new 可以省略
Point p1 = new Point(1, 2); // 这是构造函数,在实例化时触发
Point p2 = Point(3, 4); // 这是构造函数,在实例化时触发
// 调用类方法
print(p1.distanceTo(p2)); // 2.8284271247461903
}
构造函数的主要作用:
构造函数主要用来在创建实例对象时完成对对象属性的一些初始化等操作, 当创建实例对象时, 实例对象会自动调用它的构造函数。
一般来说, 构造函数有以下三个方面的作用:
构造函数分为:无参构造和有参构造,我们主要学习有参构造。
void main() {
Person p = new Person('Tomy', 18);
p.run();
}
class Person {
String? _name;
int? _age;
// 重写构造函数
Person(String name, int age) {
_name = name;
_age = age;
}
run() {
print('$_name + $_age');
}
}
构造函数形参实参一样的情况下,dart可以简化如下:
void main() {
Person p = new Person('Tomy', 20);
p.run();
}
class Person {
String name;
int age;
// 构造函数
Person(this.name, this.age);
run() {
print('$name + $age');
}
}
// 输出 Tomy + 20
如果构造函数的参数为可选参数则所有 可选参数都必须有默认值,或者标识其为必传。
void main() {
Person p = Person(nameValue: 'Tomy', ageValue: 18);
p.run();
}
class Person {
String? name;
int? age;
// 构造函数
Person({String nameValue = '', required int ageValue}) {
name = nameValue;
age = ageValue;
}
run() {
print('$name + $age');
}
}
代码越来越多导致维护性越来越差,所以我们需要把类抽离成文件,在需要的地方使用
import
导入库。
命名构造函数:dart提供的一个新概念,即可以指定一个指定名称的函数作为构造,使用命名构造函数可以为一个类实现多个构造函数, 或者使用命名构造函数使代码语义化,之前使用的 DateTime.now() 也是命名构造函数。使用如下:
void main() {
Person p = Person('Tomy', 18);
p.run();
// 使用命名构造函数
Person p2 = Person.init('Jerry', 12);
p2.run();
}
class Person {
String name;
int age;
// 构造函数
Person(this.name, this.age);
// 命名构造函数
Person.init(this.name, this.age);
// 重定向构造函数
Person.redirctor(num x) : this(name, 0);
run() {
print('$name + $age');
}
}
有时候构造函数的目的只是重定向到该类的另一个构造函数。重定向构造函数没有函数体,使用冒号:
分隔。
void main() {
Person p2 = Person.init('Jerry', 12);
p2.run();
Person Re = Person.redirctor('Tony', 15);
Re.run();
}
class Person {
String name;
int age;
// 构造函数
Person(this.name, this.age);
// 命名构造函数
Person.init(this.name, this.age);
// 重定向构造函数
Person.redirctor(name, age) : this.init(name, 0);
run() {
print('$name + $age');
}
}
初始化列表会在构造函数方法体执行之前执行,多个初始化列表表达式之间使用,
分割。
初始化列表主要作用:对final
修饰的实例对象属性赋值和对构造函数的形参进行校验。
初始化列表常用于设置final修饰的属性的值。
初始化列表中可以使用assert进行判断参数。
先按顺序执行初始化列表中的内容,然后再执行构造函数方法体中的内容。
void main() {
Person p = Person({'name': 'Tom', 'age': 18, 'height': 178});
p.run();
}
class Person {
String name;
int age;
int height;
// 在构造函数运行之前初始化实例变量
Person(info): name = info['name'],age = info['age'],height = info['height'] {
print(height > 100);
}
run() {
print('$name + $age + $height');
}
}
如果类中所有属性都必须使用final
修饰,则可以使用const
修饰构造函数,此时该构造函数为一个常量构造函数。其内部属性的值在构造函数初始化完成之后,所有属性的值是不允许被修改的。
常量构造函数的作用:多次调用常量构造函数时,如果传递一样的实参将获取同一实例对象,从而节省内存开销和运行效率。
常量构造函数,只能是类默认的构造函数且必须有参数,所有的属性都必须使用final修饰。
void main() {
Person p = const Person('Tom', 18);
Person p1 = const Person('Jerry', 20);
Person p2 = const Person('Jerry', 20);
print(p1 == p2); // true
p.run();
p1.run();
}
class Person {
final String name;
final int age;
const Person(this.name, this.age);
run() {
print('$name + $age');
}
}
单例模式的定义很简单,一个类只允许创建一个对象/实例,那么这个类就是一个单例类,这种设计模式就叫做单例设计模式,简称单例模式。
常量构造函数的弊端就是,构造函数传入的实参必须是一样的,如果多个实参中有一个实参的值不一样,最终就得不到相同的实例对象。
工厂构造函数必须用
factory
修饰,否则,不能return
。
void main() {
Person p = Person();
Person p2 = Person();
print(p == p1);
}
class Person {
Person.init();
static Person? _instance;
factory Person() => _instance ??= Person.init();
}
// 或者
class Person1 {
Person1.init();
static final Person1 _instance = Person1.init();
factory Person1() {
return _instance;
}
}
Dart
中类的继承会继承除了构造方法以外的所有属性和方法。
void main() {
Student student = Student('张三', 18, 175);
student.getInfo();
}
class Person {
String? name;
int? age;
int? _height;
bool isAdult() => age! >= 18;
Person(this.name, this.age, this._height);
getInfo() {
print('''
姓名:$name
年龄:$age
身高:$_height
''');
}
}
class Student extends Person {
String? className;
String? gender;
Student(String? name, int? age, int? height) : super(name, age, height);
@override
// 重写属性
bool isAdult() => age! > 17;
@override
getInfo() {
// ignore: todo
// TODO: implement getInfo
return super.getInfo();
}
}
// 输出
// 姓名:张三
// 年龄:18
// 身高:175
抽象类不能被实例化,如果继承一个抽象类,用extends
关键字,如需继承多个抽象类需要使用 implements
关键字。
implements
关键字既可以继承抽象类也可以继承普通类,在继承了普通类之后也需要实现普通类中的所有属性及方法。
// 抽象类
abstract class Introduce {
// 抽象方法:只能放在抽象类中,由子类去实现
String introduceSelf();
}
class Person {
String? name;
int? age;
int? height;
Person(this.name, this.age, this.height);
}
class Student implements Person, Introduce {
// 实现抽象类中的属性和方法
@override
String? name;
@override
int? age;
@override
int? height;
Student.init(this.name, this.age, this.height);
// 实现抽象类方法
@override
String introduceSelf() {
print('''自我介绍:
姓名:$name
年龄: $age
身高:$height
''');
return '';
}
}
void main() {
Student student = Student.init('张三', 18, 178);
student.introduceSelf();
}
// 输出:
// 自我介绍:
// 姓名:张三
// 年龄: 18
// 身高:178
mixins的中文意思是混入,就是在类中混入其他功能。在Dart中可以使用mixins实现类似多继承的功能。
利用关键字“with”来使用mixins:
void main() {
BirdVodka bv = BirdVodka(10, 30);
print(bv.sum1()); // 40
bv.run(); // 给我半杯小鸟伏特加
}
class Class1 {
int? val1;
int? val2;
Class1(this.val1, this.val2);
int sum1() {
return val1! + val2!;
}
}
class Class2 {
run() {
print('给我半杯小鸟伏特加');
}
}
class BirdVodka extends Class1 with Class2 {
BirdVodka(int val1, int val2) : super(val1, val2);
}
可以通过 operator
关键字,定义复写的操作运算符。
复写操作符需要在类中定义。
返回类型 operator 操作符(参数1, 参数2, ...) {
// code
...
return ...
}
void main() {
Num no1 = Num(20);
Num no2 = Num(18);
print(no1 > no2);
}
class Num {
int? number;
Num(this.number);
bool operator >(Num other) => number! > other.number!;
}
可覆写的操作符:
< 、+、|、[]、>、/、^、[]=、 <=、~/、&、~、>=、*、<<、==、-、%、>>