【dart学习】-- Dart之类和对象
一,概述
类(Class)是面向对象程序设计,实现信息封装的基础。类是一种用户定义的类型。每个类包含数据说明和一组操作数据或传递消息的函数。类的实例称为对象。
Dart的类与其它语言都有很大的区别,比如在dart的类中可以有无数个构造函数,可以重写类中的操作符,有默认的构造函数,由于dart没有接口,所以dart的类也是接口,因此你可以将类作为接口来重新实现。
Dart是一门使用类和单继承的面向对象语言所有的对象都是类的实例,并且所有的类都是Object
的子类。
二,类定义
- 类的定义用
class
关键字 - 如果未显式定义构造函数,会默认一个空的构造函数
-
类首字母必须大写
- 使用
new
关键字和构造函数
来创建对象 -
class Person { //未定义父类的时候,默认继承自Object num x; num y; num z; } void main(List<String> args){ var person = new Person();//调用默认的构造函数 person.x = 10; //使用点(
.
)引用实例变量或方法 person.y = 11; person?.z = 12; //如果p不为空,设置它的变量y的值为4 print(person.x); print(person.y); print(person.z); }结果:
10 11 12
三, 实例变量
- 声明实例变量时,所有未初始化的实例变量的值为null
- 对象的成员包括函数和数据(分别是方法和实例变量)。使用点(
.
)引用实例变量或方法; - 使用
?.
来确认前操作数不为空, 常用来替代.
, 避免左边操作数为null引发异常 -
void main(){ var point = new Point(); point.x = 4; //使用点(
.
)引用实例变量或方法
point?.y = 5;//如果p不为空,设置它的变量y的值为5
print(point.x); print(point.y);
}class Point {
int x; // null
int y; // null
int z = 0; // 0
}
四,构造函数
- 如果你没有声明构造函数,默认有构造函数,默认构造函数没有参数,调用父类的无参构造函数。子类不能继承父类的构造函数
class Person { int x; int y; } void main(List<String> args){ var person = new Person(); }
- 构造函数就是一个与类同名的函数,关键字 this 是指当前的,只有在命名冲突时有效,否则dart会忽略处理
-
void main(){ var point = new Point(4, 5); }
class Point { int x; int y;
//自己定义的类名构造函数
Point(int x, int y) {
this.x = x;
this.y = y;
}
} -
在Dart中构造函数的名称可以是类名
ClassName
或者类名和标识符ClassName.identifier
。 其中构造函数名称是“ClassName”的函数叫“类名构造函数”;构造函数名称是“ClassName.identifier”的函数叫“命名构造函数”。
(1)类名构造函数 (ClassName)import 'dart:math'; class Point { int y; int x; // 类名构造函数 Point(num x, num y) { this.x = x; this.y = y; } // ..... }
在构造函数里初始化成员属性是很常见的事情,因此Dart开发了新的语法糖来简化这种操作,比如将Point的类名构造构造函数改写成
class Point { num x, y; // 注意x,y的赋值会在构造函数执行之前完成. Point(this.x, this.y); }
(2)命名构造函数(ClassName.identifie)
使用命名构造函数可以为类提供多个构造函数,按官方的说法就是提供额外的清晰度class Point { num x, y; Point(this.x, this.y); // 命名构造函数 Point.origin() { x = 0; y = 0; } }
调用命名构造函数
在命名构造函数里也可以用新的语法糖来简化这种操作,比如将Point的类名构造构造函数改写成
-
(2)命名构造函数(ClassName.identifie)
使用命名构造函数可以为类提供多个构造函数,按官方的说法就是提供额外的清晰度
class Point { num x, y; Point(this.x, this.y); // 命名构造函数 Point.origin() { x = 0; y = 0; } }
调用命名构造函数
main(List<String> args) { // 调用命名构造函数 Point point1 = Point.origin(); }
在命名构造函数里也可以用新的语法糖来简化这种操作,比如将Point的类名构造构造函数改写成
class Point { num x, y; //类名构造函数 Point(this.x, this.y); // 命名构造函数 Point.origin(this.x,this.y); }
void main(List<String> args){ var point = new Point.Orgin(1,2); print(point.x); print(point.y); }
(3)默认构造函数(前面我我们已经说了,我们放在这里再提一下,方便区分)
如果类中没有声明构造函数,Dart会提供一个默认的构造函数。这个默认的构造函数会调用父类的默认构造函数,并且该构造函数是没有参数的。class Person { int x; int y; } void main(List<String> args){ var person = new Person(); }
-
Dart的第一个版本实例化对象需要new关键字,但在Dart 2之后就去掉了new关键字
main(List<String> args) { // 调用类名构造函数 Point point1 = Point(3,4); print(point1.x); }
-
调用父类非默认的构造函数(类比下面的重定向理解记忆)
在默认情况下,子类可以调用父类的未命名,无参数的构造函数即默认构造函数。父类的构造函数会在子类的构造函数之前开始调用,如果子类中存在需要初始化的成员属性,则可以先初始化子类成员属性,再调用父类的构造函数,执行过程如下
- 初始化子类成员属性
- 调用父类构造函数
- 子类构造函数
如果父类中没有默认的构造函数,你必须手动调用父类的构造函数,在子类的构造函数体之前通过
:
指定调用父类构造函数,示例如下// Person类中没有一个无参数,未命名的构造函数 class Person { String firstName; // 命名构造函数 Person.fromJson(Map data) { print('in Person'); } } class Employee extends Person { // 你必须调用父类的super.fromJson(data). Employee.fromJson(Map data) : super.fromJson(data) { print('in Employee'); } } main() { var emp = new Employee.fromJson({});
}
-
重定向构造函数 (在这里 :有重新指向的含义)
有时构造函数的唯一目的是重定向到同一类中的另一个构造函数。重定向构造函数的主体为空,构造函数调用出现在冒号(
:
)之后 。 大意就是在创建类时,我定义一个命名构造函数,但是这个构造函式的主体我不实现。直接通过:另外一个构造函数。实现对外界传入的参数接收并赋值给内部的变量。class Point { num x, y; //类名构造函数 Point(this.x, this.y); // 命名构造函数 Point.order(this.x,this.y); Point.origin(num a,num b):this.order(a,b); //重定向构造函数, origin构造函数将外界的传值,指向给了order构造函数。 } void main(List<String> args){ var point = new Point.origin(1,2); print(point.x); print(point.y); }
- 常量构造函数
如果你的类创建的对象从不改变,你可以创建一些编译时的常量对象。因此,定义一个const
构造函数,且保证所有的对象变量都是final。
class ImmutablePoint { static final ImmutablePoint origin = const ImmutablePoint(0, 0); final num x, y; const ImmutablePoint(this.x, this.y); }
-
工厂构造函数
在实现一个构造函数时使用
factory
关键字,该构造函数并不总是创建其类的新实例。例如,工厂构造函数可能会从缓存中返回实例,也可能会返回子类型的实例。class Logger { final String name; bool mute = false; // _cache是私有变量 //_在名称前,表示该变量为私有 static final Map<String, Logger> _cache = <String, Logger>{}; factory Logger(String name) { if (_cache.containsKey(name)) { return _cache[name]; } else { final logger = Logger._internal(name); _cache[name] = logger; return logger; } } Logger._internal(this.name);
void log(String msg) { if (!mute) print(msg); } }
注意:工厂构造者对this没有访问权限。
像调用任何其他构造函数一样调用工厂构造函数:
var logger = Logger('UI'); logger.log('Button clicked');
五,方法
方法是为对象提供行为的函数。
- Getters和Setters 方法
(1)Getters和setters是读取和修改对象的特定方法,每次调用对象的属性时,Dart都会隐式的调用一次getter方法,这允许你可以在修改或者读取对象属时做一些操作。
(2)通过get
和set
关键词重写对象的默认行为。
class Rectangle { num left, top, width, height; //类名构造函数 Rectangle(this.left, this.top, this.width, this.height); // 重写right属性(类比oc记忆,这里多了一个set和get的关键字) num get right => left + width; set right(num value) => left = value - width; num get bottom => top + height; set bottom(num value) => top = value - height; } void main() { var rect = Rectangle(3, 4, 20, 15); assert(rect.left == 3); rect.right = 12; assert(rect.left == -8); }
-
实例方法
在对象的实例方法有权限获取对象变量和this
,在接下来的例子里distanceTo
就是一个对象方法:import 'dart:math'; class Point { num x, y; Point(this.x, this.y); num distanceTo(Point other) { var dx = x - other.x; var dy = y - other.y; return sqrt(dx * dx + dy * dy); } }
-
抽象方法
(1) 实例的getter和setter方法就是抽象的方法,定义一个接口,但将其实现留给其他类。
(2)抽象方法使用分号 ; 而不是方法体
(3)抽象方法只存在于抽象类中。abstract class Doer { //...定义实例变量和方法... //定义一个抽象方法 void doSomething(); } class EffectiveDoer extends Doer { void doSomething() { //...实现一个抽象方法... } }
六,抽象类和接口
-
抽象类
使用abstract
修改器可以定义一个抽象类。抽象类是不能被实例化的,但对于定义接口是非常有用的,如果你想实例化抽象类,你必须实现抽象类,才能被实例化
-
// 此对象爱你过是抽象类,因此不能被实例化 abstract class AbstractContainer { // 定义构造函数、字段、方法... void updateChildren(); // 抽象方法 }
-
隐式的接口
每个类都是都是隐式的接口,包括类的方法和属性。如果你想创建一个类A不继承B的实现,可以实现B的接口来创建类A。一个类允许通过implements
关键词可以实现多个接口// 每个类都是一个隐式的接口,所以Person类也是个接口,包括成员属性和方法. class Person { // 可在接口中实现, 但仅对这个库可见. final _name; // 构造函数不能够被接口实现 Person(this._name); // 可在接口中实现. String greet(String who) => 'Hello, $who. I am $_name.'; } // 实现Person接口. class Impostor implements Person { 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())); }
实现多个接口
class Point implements Comparable, Location {...}
七,类的继承
- 使用extends创建子类,super引用父类,子类可以重写实例方法、getter和setter,使用@override注释重写,使用@proxy注释来忽略警告
class Television { void turnOn() { _illuminateDisplay(); _activateIrSensor(); } } class SmartTelevision extends Television { void turnOn(); _bootNetworkInterface(); _initializeMemory(); _upgradeApps(); }
-
重写成员
可以使用
@override
关键字,子类可以重写实例的方法,getters和settersclass SmartTelevision extends Television { @override void turnOn() {...} // ··· }
-
重写操作符
你可以重写以下表中显示的运算符。例如,如果定义Vecor类,则可以定义
+
方法来添加两个vectors。
注意:
你可能已经注意到了!=
不是可重写的运算符。表达式E1!= E2
只是语法上的糖!( E1 = = E2 )
< + | [] > / ^ []= <= ~/ & ~ >= * << == – %
-
可以重写操作符,下面是重写加减操作符的示例
如果你需要重写
==
操作符,请参考操作符教程
-
noSuchMethod()
当用户调用你定义的类中不存在的属性与方法时,可以做出一些响应,通过重写noSuchMethod()
class A { @override void noSuchMethod(Invocation invocation) { print('You tried to use a non-existent member: ' + '${invocation.memberName}'); } }
八,类变量和类方法
使用static
关键字实现类范围的变量和方法。
- 静态变量
静态变量(类变量)对于类范围的状态和常量非常有用:
静态变量在使用之前不会初始化。
class Queue { static const initialCapacity = 16; // ··· } void main() { assert(Queue.initialCapacity == 16); }
- 静态方法
静态方法(类方法)不能在实例操作,因此它没有访问this的权限。
import 'dart:math'; class Point { num x, y; Point(this.x, this.y); static num distanceBetween(Point a, Point b) { var dx = a.x - b.x; var dy = a.y - b.y; return sqrt(dx * dx + dy * dy); } } void main() { var a = Point(2, 2); var b = Point(4, 4); var distance = Point.distanceBetween(a, b); assert(2.8 < distance && distance < 2.9); print(distance); }
注意:
(1)为了常用或广泛使用的实用程序和功能,考虑使用顶层函数,而不是静态方法。import 'dart:math'; class Point { num x, y; Point(this.x, this.y); } // 顶级函数 num distanceBetween(Point a, Point b) { var dx = a.x - b.x; var dy = a.y - b.y; return sqrt(dx * dx + dy * dy); } void main() { var a = Point(2, 2); var b = Point(4, 4); var distance = distanceBetween(a, b); assert(2.8 < distance && distance < 2.9); print(distance); }
(2)可以使用静态方法作为编译时常量。例如,可以将静态方法作为参数传递给常量构造函数。
九,访问控制
- 默认类中的所有属性和方法是public的。在dart中,可以在属性和方法名前添加“_”使私有化。现在让我们使name属性私有化。
main(List<String> args) { Dog d = new Dog('Duffy', 5); print(d.name); //This will throw error } class Dog { String _name; //私有的变量 int age; //公有变量
//类名构造函数 Dog(this.name, this.age);
//setter && getter 方法 String get respectedName { return 'Mr.$name'; }
set respectedName(String newName) { name = newName; }
//命名构造函数 Dog.newBorn() { name = 'Doggy'; age = 0; }
//公有实例方法 bark() { print('Bow Wow'); }
//私有的实例方法 _hiddenMethod() { print('I can only be called internally!'); } }
十,枚举类型
枚举类型,通常称为枚举,是一种特殊类型的类,用于表示固定数量的常量值。
-
使用枚举
使用enum
关键词来声明一个枚举类型enum Color { red, green, blue }
枚举中的每个值都有一个
index
索引,它返回枚举声明中值的从零开始的位置。例如,第一个值具有索引0,第二个值具有索引1。assert(Color.red.index == 0); assert(Color.green.index == 1); assert(Color.blue.index == 2);
若要获取枚举中所有值的列表,请使用枚举的
values
常量。List<Color> colors = Color.values; assert(colors[2] == Color.blue);
你可以在switch语句中使用枚举,如果不处理枚举的所有值,将会收到警告:
var aColor = Color.blue; switch (aColor) { case Color.red: print('Red as roses!'); break; case Color.green: print('Green as grass!'); break; default: // 没有default,将会报错 print(aColor); // 'Color.blue' }
枚举类型有以下限制:
- 你不能子类化、混合或实现枚举。
- 不能显式实例化枚举。
十一,泛型
- 泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。
- 从字面的意思理解来看,泛型,泛就是模糊、暂不确定暂定的意思。可以这样理解,使用泛型就是,定义的一个类型,类型暂不确定,给使用给一个占位符给代替,在使用的时候可以给确定其定义的类型。
- 泛型,基本上强类型语言都支持泛型,比如java,Oc,swift 所以Dart也不例外
- dart全面支持泛型。假设你想在你定义的类中,想持有任意类型的数据。如下是怎样使用泛型定义这样的类。
main(List<String> args) { DataHolder<String> dataHolder = new DataHolder('Some data'); print(dataHolder.getData()); dataHolder.setData('New Data'); print(dataHolder.getData()); } class DataHolder<T> { T data; DataHolder(this.data); getData() { return data; } setData(data) { this.data = data; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)