臭宝们,今天我们来学习ArkTS语言中的类(Class)的概念。类是面向对象编程中的一个核心概念,它允许我们创建具有属性和方法的自定义数据类型。在ArkTS中,类的定义和使用方式与其他主流的面向对象语言类似,但也有一些独特之处需要我们注意。
在以下示例中,定义了Person类,该类具有字段name和surname、构造函数和方法fullName:
class Person {
name: string = '';
surname: string = '';
constructor (n: string, sn: string) {
this.name = n;
this.surname = sn;
}
fullName(): string {
return this.name + ' ' + this.surname;
}
}
要创建Person类的实例,可以使用new关键字:
let person = new Person('John', 'Doe');
console.log(person.fullName()); // 输出: John Doe
在ArkTS中,字段(field)和属性(property)的概念略有不同。字段是类的一部分,而属性则是对象的状态。在上面的示例中,name和surname是Person类的字段,它们可以被视为对象的属性。
其中,字段也分为私有字段和公共字段,私有字段只能在类内部访问,而公共字段可以在类的外部被访问。例如:
class Person {
private name: string = ''; // 私有字段
public surname: string = ''; // 公共字段
}
persion: Person = new Person();
persion.name = 'John'; // 错误,私有字段不能在类外部访问
persion.surname = 'Doe'; // 正确,公共字段可以在类外部访问
除此之外,字段还分为静态字段和实例字段。静态字段属于类本身,而不是类的某个特定对象。例如:
class Person {
static species: string = 'Homo sapiens'; // 静态字段
name: string = ''; // 实例字段
constructor (n: string) {
this.name = n;
}
getname(): string {
return this.name;
}
}
console.log(Person.species); // 输出: Homo sapiens
let person = new Person('John');
console.log(person.getname()); // 输出: John
注意:字段一定要初始化,否则会报错。
以下是正确的初始化方式:
class Person {
name: string = '';
setName(n:string): void {
this.name = n;
}
// 类型为'string',不可能为"null"或者"undefined"
getName(): string {
return this.name;
}
}
let jack = new Person();
// 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"
jack.getName().length; // 0, 没有运行时异常
当字段为可选类型时,是这样的:
class Person {
name?: string; // 可能为`undefined`
setName(n:string): void {
this.name = n;
}
// 编译时错误:name可以是"undefined",所以这个API的返回值类型不能仅定义为string类型
getNameWrong(): string {
return this.name;
}
getName(): string | undefined { // 返回类型匹配name的类型
return this.name;
}
}
let jack = new Person();
// 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"
// 编译时错误:编译器认为下一行代码有可能会访问undefined的属性,报错
jack.getName().length; // 编译失败
jack.getName()?.length; // 编译成功,没有运行时错误
在ArkTS中,可以使用getter和setter方法来访问或修改私有字段。例如:
class Person {
private _name: string = '';
constructor (n: string) {
this._name = n;
}
get name(): string {
return this._name;
}
set name(value: string) {
if (value.length > 0) {
this._name = value;
} else {
console.log('Invalid name');
}
}
}
let person = new Person('John');
person.name = 'Jane'; // 设置新的名字
console.log(person.name); // 获取当前的名字
在ArkTS中,类的方法类似于其他编程语言中的函数。例如:
class Person {
name: string = '';
constructor (n: string) {
this.name = n;
}
greet(): void {
console.log('Hello, my name is ' + this.name);
}
}
let person = new Person('John');
person.greet(); // 输出: Hello, my name is John
//以下是静态方法:
class Greeter {
static greet(name: string): void {
console.log('Hello, ' + name);
}
}
Greeter.greet('John'); // 输出: Hello, John
注意了,在ArkTS中,方法是方法,函数是函数。
继承类继承基类的字段和方法,但不继承构造函数。继承类可以新增定义字段和方法,也可以覆盖其基类定义的方法。
基类也称为“父类”或“超类”。继承类也称为“派生类”或“子类”。
class Animal {
Name: string = '';
Age: number = 0;
get name(): string {
return this.Name;
}
set name(value: string) {
this.Name = value;
}
set age(value: number) {
if (value > 0) {
this.Age = value;
}
}
get age(): number {
return this.Age;
}
constructor (n: string, a: number) {
this.Name = n;
this.Age = a;
}
move(): void {
console.log('Moving...');
}
}
class Dog extends Animal {
constructor (n: string, a: number) {
super(n, a); // 调用基类的构造函数
}
move(): void {
console.log('Running...');
}
}
let dog = new Dog('Rex', 5);
dog.name; // 获取名字
dog.age; // 获取年龄
dog.move(); // 输出: Running...
在ArkTS中,可以使用super关键字访问基类的字段和方法。例如:
class RectangleSize {
protected height: number = 0;
protected width: number = 0;
constructor (h: number, w: number) {
this.height = h;
this.width = w;
}
draw() {
/* 绘制边界 */
}
}
class FilledRectangle extends RectangleSize {
color = ''
constructor (h: number, w: number, c: string) {
super(h, w); // 父类构造函数的调用
this.color = c;
}
draw() {
super.draw(); // 父类方法的调用
// super.height -可在此处使用
/* 填充矩形 */
}
}
子类可以重写其父类中定义的方法的实现。重写的方法必须具有与原始方法相同的参数类型和相同或派生的返回类型。
class RectangleSize {
// ...
area(): number {
// 实现
return 0;
}
}
class Square extends RectangleSize {
private side: number = 0;
area(): number {
return this.side * this.side;
}
}
通过重载签名,指定方法的不同调用。具体方法为,为同一个方法写入多个同名但签名不同的方法头,方法实现紧随其后。
class C {
foo(x: number): void; /* 第一个签名 */
foo(x: string): void; /* 第二个签名 */
foo(x: number | string): void { /* 实现签名 */
}
}
let c = new C();
c.foo(123); // OK,使用第一个签名
c.foo('aa'); // OK,使用第二个签名
关键字super可用于访问父类的实例字段、实例方法和构造函数。在实现子类功能时,可以通过该关键字从父类中获取所需接口:
class RectangleSize {
protected height: number = 0;
protected width: number = 0;
constructor (h: number, w: number) {
this.height = h;
this.width = w;
}
draw() {
/* 绘制边界 */
}
}
class FilledRectangle extends RectangleSize {
color = ''
constructor (h: number, w: number, c: string) {
super(h, w); // 父类构造函数的调用
this.color = c;
}
draw() {
super.draw(); // 父类方法的调用
// super.height -可在此处使用
/* 填充矩形 */
}
}
类声明可以包含用于初始化对象状态的构造函数。
constructor ([parameters]) {
// ...
}
如果未定义构造函数,则会自动创建具有空参数列表的默认构造函数,例如:
class Point {
x: number = 0;
y: number = 0;
}
let p = new Point();
构造函数函数体的第一条语句可以使用关键字super来显式调用直接父类的构造函数。
class RectangleSize {
constructor(width: number, height: number) {
// ...
}
}
class Square extends RectangleSize {
constructor(side: number) {
super(side, side);
}
}
通过重载签名,指定构造函数的不同调用。具体方法为,为同一个构造函数写入多个同名但签名不同的方法头,构造函数实现紧随其后。
class C {
constructor(x: number); /* 第一个签名 */
constructor(x: string); /* 第二个签名 */
constructor(x: number | string) { /* 实现签名 */
}
}
let c = new C(123); // OK,使用第一个签名
c = new C('aa'); // OK,使用第二个签名
注意:如果两个重载签名的名称和参数列表均相同,则为错误。
ArkTS支持四种可见性修饰符:public、protected和private。默认情况下,类成员的访问级别为public。public修饰的类成员(字段、方法、构造函数)在程序的任何可访问该类的地方都是可见的。
class RectangleSize {
// 默认为 public
height: number = 0;
width: number = 0;
}
private修饰的成员不能在声明该成员的类之外访问,例如:
class C {
public x: string = '';
private y: string = '';
set_y (new_y: string) {
this.y = new_y; // OK,因为y在类本身中可以访问
}
get_y (): string {
return this.y; // OK,因为y在类本身中可以访问
}
}
let c = new C();
c.x = 'a'; // OK,该字段是公有的
c.y = 'b'; // 编译时错误:'y'不可见
c.set_y('c'); // OK,因为该方法在类本身中可以访问
c.get_y(); // OK,因为该方法在类本身中可以访问
protected修饰的成员不能在声明该成员的类之外访问,但可以在派生类的构造函数和实例方法中访问。
class Base {
protected x: string = '';
private y: string = '';
}
class Derived extends Base {
foo() {
this.x = 'a'; // OK,访问受保护成员
this.y = 'b'; // 编译时错误,'y'不可见,因为它是私有的
}
}
对象字面量是一个表达式,可用于创建类实例并提供一些初始值。它在某些情况下更方便,可以用来代替new表达式。
对象字面量的表示方式是:封闭在花括号对({})中的’属性名:值’的列表。
class C {
n: number = 0;
s: string = '';
}
let c: C = {n: 42, s: 'foo'};
ArkTS是静态类型语言,如上述示例所示,对象字面量只能在可以推导出该字面量类型的上下文中使用。其他正确的例子:
class C {
n: number = 0;
s: string = '';
}
function foo(c: C) {}
let c: C
c = {n: 42, s: 'foo'}; // 使用变量的类型
foo({n: 42, s: 'foo'}); // 使用参数的类型
function bar(): C {
return {n: 42, s: 'foo'}; // 使用返回类型
}
也可以在数组元素类型或类字段类型中使用:
class C {
n: number = 0;
s: string = '';
}
let cc: C[] = [{n: 1, s: 'a'}, {n: 2, s: 'b'}];
泛型Record
let map: Record = {
'John': 25,
'Mary': 21,
}
map['John']; // 25
map['John'] = 26; // OK
map['John'] = '26'; // 编译时错误,应为number类型
类型K可以是字符串类型或数值类型,而V可以是任何类型。
interface PersonInfo {
age: number;
salary: number;
}
let map: Record = {
'John': { age: 25, salary: 10},
'Mary': { age: 21, salary: 20}
}
带有修饰符abstract的类称为抽象类。抽象类可用于表示一组更具体的概念所共有的概念。
如果尝试创建抽象类的实例,则会发生编译时的错误:
abstract class X {
field: number;
constructor(p: number) {
this.field = p;
}
}
let x = new X(666) //编译时错误:不能创建抽象类的具体实例
抽象类的子类可以是抽象类也可以是非抽象类。抽象父类的非抽象子类可以实例化。因此,执行抽象类的构造函数和该类非静态字段的字段初始化器:
abstract class Base {
field: number;
constructor(p: number) {
this.field = p;
}
}
class Derived extends Base {
constructor(p: number) {
super(p);
}
}
let d = new Derived(666); // OK
抽象方法
带有abstract修饰符的方法称为抽象方法,抽象方法可以被声明但不能被实现。
只有抽象类内才能有抽象方法,如果非抽象类具有抽象方法,则会发生编译时错误:
class Y {
abstract method(p: string) //编译时错误:抽象方法只能在抽象类内。
}
抽象类可以有非抽象方法,反之则不行。
好了,臭宝们,今天的内容就到这里了,我们下期再见!